前言:C语言实现快速排序及其系统优化和分析
目录
一.快速排序的实现
1.实现思路
2.QSort实现
3.函数Partition()的实现
4.完整代码
二.对快速排序的系统优化
1.对关键字pivotkey的选择优化
2.优化不必要的交换(把交换变成赋值)
3.优化递归操作
4.!!!!优化后的完整代码!!!!!
三.结语
快速排序的基本思路是:先在待排序数组中随便找一个数(其实找这个数也是有技巧的,后面会讲到),这个数也称作关键字,通过一趟排序将数组分成左右两部分,左边的部分都比关键字小,中间是关键字,右边的部分都比关键字大。然后左右两部分又可以继续进行和刚刚一样的排序,直到整个数组有序。
例如
无序数组 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.然后我们就可以对左右两部分继续这样的操作,所以快速排序的核心代码,就是如何把数组分成左右两部分!!是不是听起来很简单!!接下来我们就来实现
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()。
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小,接着再次对左右两边继续递归排序就可以了,也就是下图画红线的两句代码
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]);
}
}
运行效果:
前面我们为了方便,都是选择数组的第一个元素当作关键字,这么随意肯定会有不好的地方,例如刚刚的待排序数组变成了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都可以
}
我们只需在把数组分成左右两部分之前,将关键字备份一份,然后在排序的过程中,数字间的交换直接改成赋值,会少了很多交换的步骤
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都可以
}
我们将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,继续递归排序
}
}
这样我们就减少了递归的次数,速度大大提高!!
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的讲解,非常详细~最后大家有什么不懂的欢迎留言~
✨喜欢和觉得有用的不妨点个赞和关注吧~❤️
✨您的关注就是博主创作的最大动力~!!! 谢谢阅读!!!!