C语言实现快速排序及快速排序的优化和分析

前言:C语言实现快速排序及其系统优化和分析

目录

一.快速排序的实现

1.实现思路

2.QSort实现

3.函数Partition()的实现

4.完整代码

 

 二.对快速排序的系统优化

1.对关键字pivotkey的选择优化

2.优化不必要的交换(把交换变成赋值)

3.优化递归操作

4.!!!!优化后的完整代码!!!!!

 三.结语


一.快速排序的实现

1.实现思路

快速排序的基本思路是:先在待排序数组中随便找一个数(其实找这个数也是有技巧的,后面会讲到),这个数也称作关键字,通过一趟排序将数组分成左右两部分左边的部分都比关键字小,中间是关键字,右边的部分都比关键字大。然后左右两部分又可以继续进行和刚刚一样的排序,直到整个数组有序。

例如

无序数组 26,1,7,56,34,23,77,87,22,43,16,66

1.我们先选出一个关键字,例如选了26

2.经过第一次排序后   就分成左右两部分  左边的部分比关键字小   中间是关键字  右边的部分比关键字大  

即16 1 7 22 23     26      77 87 34 43 56 66

3. 16 1 7 22 23 是左边部分

4.  26是关键字,即中间部分

5. 77 87 34 43 56 66 是右边部分

6.然后我们就可以对左右两部分继续这样的操作,所以快速排序的核心代码,就是如何把数组分成左右两部分!!是不是听起来很简单!!接下来我们就来实现

2.QSort实现

void QSort(int* arr, int low,int high)//low是数组第一个下标,也就是0;high是数组最后一个下标
{
	int pivot;//关键字的下标
	if (low < high)
	{
		pivot = Partition(arr, low, high);//获取关键字下标,目的是为了把数组分成左右两部分
		QSort(arr, low, pivot - 1);//把左边的部分继续排序
		QSort(arr, pivot + 1, high);//把右边的部分继续排序
	}
}



int main()
{
	int arr[] = { 26,1,7,56,34,23,77,87,22,43,16,66 };

	QSort(arr, 0,(sizeof(arr) / sizeof(arr[0])-1));

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
}

这里随便定义了一个数组int arr[] = { 26,1,7,56,34,23,77,87,22,43,16,66 },我们要做的就是在"pivot = Partition(arr, low, high)"中获取关键字的下标,并把数组变成 :

1.关键字在中间

2.关键字的左边均小于等于关键字的数值 

3.关键字的右边均大于等于关键字的数值

这也是函数Partition()的功能,所以接下来我们就要想办法实现函数Partition()。

3.函数Partition()的实现

void swap(int* arr, int x, int y)
{
	int tmp = arr[x];
	arr[x] = arr[y];
	arr[y] = tmp;
}

int Partition(int* arr, int low, int high)
{
	int pivotkey = arr[low];//pivotkey是关键字,这里随便找一个就行,为了方便就取了数组第一个元素
	while (low < high)//low和high相等的时候就说明low和high同时指向了关键字的下标
	{
		while (low < high && arr[high] >= pivotkey)//就是从数组右边向左边开始找有没有比关键字小的,有就跳出循环,并和关键字左边的数交换,确保小的数都在关键字左边。
		{
			high--;
		}
		swap(arr, low, high);//交换数值的函数
		while (low < high && arr[low] <= pivotkey)就是从数组左边向右边开始找有没有比关键字大的,有就跳出循环,并和关键字右边的数交换,确保大的数都在关键字右边。
		{
			low++;
		}
		swap(arr, low, high);
	}
	return low;//返回关键字下标,low和high都可以
}

经过依次Partition()后,数组arr[]就变成了arr= 16 1 7 22 23     26      77 87 34 43 56 66以数组arr首元素26为中心的两部分,26右边的都比26大,26左边的都比26小,接着再次对左右两边继续递归排序就可以了,也就是下图画红线的两句代码

4.完整代码

void swap(int* arr, int x, int y)
{
	int tmp = arr[x];
	arr[x] = arr[y];
	arr[y] = tmp;
}


int Partition(int* arr, int low, int high)
{
	int pivotkey = arr[low];//pivotkey是关键字,这里随便找一个就行,为了方便就取了数组第一个元素
	while (low < high)//low和high相等的时候就说明low和high同时指向了关键字的下标
	{
		while (low < high && arr[high] >= pivotkey)//就是从数组右边向左边开始找有没有比关键字小的,有就跳出循环,并和关键字左边的数交换,确保小的数都在关键字左边。
		{
			high--;
		}
		swap(arr, low, high);//交换数值的函数
		while (low < high && arr[low] <= pivotkey)就是从数组左边向右边开始找有没有比关键字大的,有就跳出循环,并和关键字右边的数交换,确保大的数都在关键字右边。
		{
			low++;
		}
		swap(arr, low, high);
	}
	return low;//返回关键字下标,low和high都可以
}


void QSort(int* arr, int low,int high)//low是数组第一个下标,也就是0;high是数组最后一个下标
{
	int pivot;//关键字的下标
	if (low < high)
	{
		pivot = Partition(arr, low, high);//获取关键字下标,目的是为了把数组分成左右两部分
		QSort(arr, low, pivot - 1);//把左边的部分继续排序
		QSort(arr, pivot + 1, high);//把右边的部分继续排序
	}
}



int main()
{
	int arr[] = { 26,1,7,56,34,23,77,87,22,43,16,66 };

	QSort(arr, 0,(sizeof(arr) / sizeof(arr[0])-1));

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
}

运行效果:

 二.对快速排序的系统优化

1.对关键字pivotkey的选择优化

  前面我们为了方便,都是选择数组的第一个元素当作关键字,这么随意肯定会有不好的地方,例如刚刚的待排序数组变成了arr[] = { 1,26,7,56,34,23,77,87,22,43,16,66 },若我们选择了第一个元素1,和选择其它任何一个数相比,很明显选择1就会显得很不合理,所以我们刚开始选择的关键字对我们排序速度的快慢有很大的影响。

  因此,我们想出了一个办法,在数组的左 中 右分别取一个数,然后再拿这三个数居中的那一个来当关键字pivotkey。在规模较小的排序中,这的确是一个好办法,但是在规模较大的数组中,其作用也是微乎其微,所以在规模较大的排序中,有时也有27数取9再取3再取1,也有9取3再取1的,其道理都一样,就是为了找出一个合适的关键字,加快排序的速度。

因此我们在确定关键字pivotkey前加上这几行代码

    int mid = low + (high - low) / 2;//确定中间元素的下标
	if (arr[low] > arr[high])
	{
		swap(arr, low, high);//交换左右端数据,保持左端较小
	}
	if (arr[mid] > arr[high])
	{
		swap(arr, high, mid);//交换中右端数据,保证中间较小
	}
	if (arr[mid] > arr[low])
	{
		swap(arr, mid, low);//交换左中端数据,保证左端较中间的大,这样左中右三个数中居中的就跑到了最左边了
	}

函数Paritition()就变成了这样

int Partition(int* arr, int low, int high)
{
    int mid = low + (high - low) / 2;//确定中间元素的下标
	if (arr[low] > arr[high])
	{
		swap(arr, low, high);//交换左右端数据,保持左端较小
	}
	if (arr[mid] > arr[high])
	{
		swap(arr, high, mid);//交换中右端数据,保证中间较小
	}
	if (arr[mid] > arr[low])
	{
		swap(arr, mid, low);//交换左中端数据,保证左端较中间的大,这样左中右三个数中居中的就跑到了最左边了
	}
	int pivotkey = arr[low];//pivotkey是关键字,这里随便找一个就行,为了方便就取了数组第一个元素
	while (low < high)//low和high相等的时候就说明low和high同时指向了关键字的下标
	{
		while (low < high && arr[high] >= pivotkey)//就是从数组右边向左边开始找有没有比关键字小的,有就跳出循环,并和关键字左边的数交换,确保小的数都在关键字左边。
		{
			high--;
		}
		swap(arr, low, high);//交换数值的函数
		while (low < high && arr[low] <= pivotkey)就是从数组左边向右边开始找有没有比关键字大的,有就跳出循环,并和关键字右边的数交换,确保大的数都在关键字右边。
		{
			low++;
		}
		swap(arr, low, high);
	}
	return low;//返回关键字下标,low和high都可以
}

2.优化不必要的交换(把交换变成赋值)

我们只需在把数组分成左右两部分之前,将关键字备份一份,然后在排序的过程中,数字间的交换直接改成赋值,会少了很多交换的步骤

	int pivotkey = arr[low];
	int tem = pivotkey;//备份关键字
	while (low < high)
	{
		while (low < high && arr[high] >= pivotkey)
		{
			high--;
		}
		arr[low] = arr[high];//赋值而不是交换
		while (low < high && arr[low] <= pivotkey)
		{
			low++;
		}
		arr[high] = arr[low];//赋值而不是交换
	}
	arr[low] = tem;//把关键字的数值重新归还到它所属的位置

原理也是一样,在右边找到小的数,赋值到左边   在左边找大的数,赋值到右边   反正到最后,low和high都会同时指向关键字的下标,这时再把关键字的值还给它就得了。

经过上面几番操作,我们的Partition()函数变成了这样

int Partition(int* arr, int low, int high)
{
    int mid = low + (high - low) / 2;//确定中间元素的下标
	if (arr[low] > arr[high])
	{
		swap(arr, low, high);//交换左右端数据,保持左端较小
	}
	if (arr[mid] > arr[high])
	{
		swap(arr, high, mid);//交换中右端数据,保证中间较小
	}
	if (arr[mid] > arr[low])
	{
		swap(arr, mid, low);//交换左中端数据,保证左端较中间的大,这样左中右三个数中居中的就跑到了最左边了
	}

	int pivotkey = arr[low];//pivotkey是关键字,这里随便找一个就行,为了方便就取了数组第一个元素
    int tem = pivotkey;//备份关键字

	while (low < high)//low和high相等的时候就说明low和high同时指向了关键字的下标
	{
		while (low < high && arr[high] >= pivotkey)//就是从数组右边向左边开始找有没有比关键字小的,有就跳出循环,并和关键字左边的数交换,确保小的数都在关键字左边。
		{
			high--;
		}
		arr[low] = arr[high];//赋值而不是交换
		while (low < high && arr[low] <= pivotkey)就是从数组左边向右边开始找有没有比关键字大的,有就跳出循环,并和关键字右边的数交换,确保大的数都在关键字右边。
		{
			low++;
		}
		arr[high] = arr[low];//赋值而不是交换
	}
    arr[low] = tem;//把关键字的数值重新归还到它所属的位置
	return low;//返回关键字下标,low和high都可以
}

3.优化递归操作

我们将QSort()的函数改为

void QSort(int* arr, int low,int high)
{
	int pivot;
	while (low < high)//if变成了while
	{
		pivot = Partition(arr, low, high);
		QSort(arr, low, pivot - 1);
		low = pivot + 1;//low用完反正也不用了,就重新变成原来QSort(arr, pivot + 1, high);的pivot+1,继续递归排序
	}
}

这样我们就减少了递归的次数,速度大大提高!!

4.!!!!优化后的完整代码!!!!!

void swap(int* arr, int x, int y)
{
	int tmp = arr[x];
	arr[x] = arr[y];
	arr[y] = tmp;
}


int Partition(int* arr, int low, int high)
{
    int mid = low + (high - low) / 2;//确定中间元素的下标
	if (arr[low] > arr[high])
	{
		swap(arr, low, high);//交换左右端数据,保持左端较小
	}
	if (arr[mid] > arr[high])
	{
		swap(arr, high, mid);//交换中右端数据,保证中间较小
	}
	if (arr[mid] > arr[low])
	{
		swap(arr, mid, low);//交换左中端数据,保证左端较中间的大,这样左中右三个数中居中的就跑到了最左边了
	}

	int pivotkey = arr[low];//pivotkey是关键字,这里随便找一个就行,为了方便就取了数组第一个元素
    int tem = pivotkey;//备份关键字

	while (low < high)//low和high相等的时候就说明low和high同时指向了关键字的下标
	{
		while (low < high && arr[high] >= pivotkey)//就是从数组右边向左边开始找有没有比关键字小的,有就跳出循环,并和关键字左边的数交换,确保小的数都在关键字左边。
		{
			high--;
		}
		arr[low] = arr[high];//赋值而不是交换
		while (low < high && arr[low] <= pivotkey)就是从数组左边向右边开始找有没有比关键字大的,有就跳出循环,并和关键字右边的数交换,确保大的数都在关键字右边。
		{
			low++;
		}
		arr[high] = arr[low];//赋值而不是交换
	}
    arr[low] = tem;//把关键字的数值重新归还到它所属的位置
	return low;//返回关键字下标,low和high都可以
}

void QSort(int* arr, int low,int high)
{
	int pivot;
	while (low < high)//if变成了while
	{
		pivot = Partition(arr, low, high);
		QSort(arr, low, pivot - 1);
		low = pivot + 1;//low用完反正也不用了,就重新变成原来QSort(arr, pivot + 1, high);的pivot+1,继续递归排序
	}
}

int main()
{
	int arr[] = { 26,1,7,56,34,23,77,87,22,43,16,66 };

	QSort(arr, 0,(sizeof(arr) / sizeof(arr[0])-1));

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
}

运行效果

 三.结语

其实很多计算机语言的库函数都有快速排序这个函数,虽然各有不同,但是效果和速度都是差不多的,如果大家对C语言的快速排序库函数qsort感兴趣的话,可以翻阅博主的另一篇关于qsort的讲解,非常详细~最后大家有什么不懂的欢迎留言~


✨喜欢和觉得有用的不妨点个赞和关注吧~❤️

✨您的关注就是博主创作的最大动力~!!!  谢谢阅读!!!!

猜你喜欢

转载自blog.csdn.net/weixin_62244902/article/details/122222511