进阶排序算法:快速排序与归并排序
快速排序
过程演示
快速排序利用的是递归的思想,递归每一次调用都将数组分成一个小于选定值的左子数组和大于选定值的右子数组,而选定值放置在左子数组和右子数组的过渡位置。再分别对左子数组和右子数组进行同样的快速排序。同时这里还利用了哨兵的思想——用一个变量保存特定值(看到很多教程都是用的数组的第一个位置即a[0]作为哨兵位置来存储特定值,个人觉得调用的时候可能会有点复杂,因为一般输入的数组第一个位置都是存在元素的吧。然后自己想了一下就可以用一个变量储存或许会更好一点)。通常选取数组第一个元素为为选定值。
代码及其结果
#include <stdio.h>
/*
建议把找选定值位置的函数单独写出来,如果直接写在排序函数里,递归调用的时候low和high就被改变了。
*/
int partion_arr(int a[],int low,int high) //找选定值位置函数
{
int val = a[low];
while (low != high)
{
while (low != high && a[high] > val)
{
high--;
}
a[low] = a[high]; //出循环了说明找到了比目标值小的元素,说明要移到左子数组
while (low != high && a[low] < val)
{
low++;
}
a[high] = a[low]; //出循环了说明找到了比目标值大的元素,说明要移到右子数组
}
a[low] = val; //在正确位置上放上指定值
return low; //放回下一次递归调用的位置
}
void quick_sort(int a[], int low, int high)
{
int position;
if(low<high) //递归结束的条件
{
position = partion_arr(a, low, high);//找到选定值的位置
quick_sort(a, low, position - 1); //递归快排左子数组
quick_sort(a, position + 1, high); //递归快排右子数组
}
}
int main()
{
int i;
int a[6] = {6,7,3,8,9,5};
printf("before sorting:");
for (i = 0; i < 6; i++)
{
printf("%d\t", a[i]);
}
quick_sort(a,0,5);
printf("\n");
printf("after sorting: ");
for (i = 0; i < 6; i++)
{
printf("%d\t", a[i]);
}
return 0;
}
排序结果如下
复杂度分析
由于这里是递归调用,所以复杂度并不好看出来。再对于递归调用的复杂度分析上我个人使用的是一种找出调用的栈的深度,分析一次递归调用的时间复杂度,两者乘积就是算法的时间复杂度。
栈的深度:每次递归调用都将数组分成左子数组和右子数组两部分,然后再把左子数组和右子数组分别再分成左子数组的子数组,右子数组的子数组。所以栈顶深度应该是log2 n
一次递归调用的时间:从快速排序的递归算法可以看出来,每次快速排序的时间耗费在寻找选定值应该插入的位置上,而我们寻找选定值应该插入的位置需要遍历整个数组,所以应该是n
综上快速排序的时间复杂度应该是O(nlog2 n)
归并排序
过程演示
归并排序是一个递归回溯的典型例子,它每次都将数组一分为二,最后分到不可分的时候进行合并,归可以理解成归类分类,并可以理解成合并
下面是归并排序的归
下面是归并排序的并
并的方法过程
代码及其结果
#include <stdio.h>
void mergeinto_arr(int a[], int low, int high,int mid)
{
int p = low;
int q = mid+1;
int i= 0;
int b[100] = {0};
while(p<=mid&&q<=high) //合并结束的标志是两个标志都遍历完了各自的子数组
{
if(a[p]<=a[q])
{
b[i++] = a[p++]; //a[p]更小,把a[p]放入临时数组
}else if(a[p]>a[q])
{
b[i++] = a[q++]; //a[q]更小,把a[p]放入临时数组
}
if(q==high+1) //能进行到这里说明p还没到底但是q已经到底
{
for (;p<=mid;) //如果一个数组已经结束,但是另一个数组还没遍历完
b[i++] = a[p++]; //就把没遍历完的数组全部加入临时数组
}else if(p==mid+1) //同上
for (; q <=high ;)
b[i++] = a[q++];
}
for (i = low; i < high + 1; i++) //传入的low和high,就是a中目前要排序的元素的位置.
{
a[i] = b[i - low];
}
}
void merge_sort(int a[], int low, int high)
{
int mid;
if (low < high)
{
mid = (high + low) / 2;
merge_sort(a, low, mid); //数组分成左半边
merge_sort(a, mid + 1, high); //数组分成右半边
mergeinto_arr(a, low, high, mid);//把数组合并
}
}
int main()
{
int i;
int a[6] = {6, 7, 3, 8, 9, 5};
printf("before sorting:");
for (i = 0; i < 6; i++)
{
printf("%d\t", a[i]);
}
merge_sort(a, 0, 5);
printf("\n");
printf("after sorting: ");
for (i = 0; i < 6; i++)
{
printf("%d\t", a[i]);
}
return 0;
}
结果如下
复杂度分析
由于这里是递归调用,所以复杂度并不好看出来。采取跟快速排序一样的复杂度分析方法
栈的深度:每次递归调用都将数组一分为二,所以栈顶深度应该是log2 n
一次递归调用的时间:从快速排序的递归算法可以看出来,每次归并排序的时间耗费在合并子数组中,而我们
合并子数组需要遍历整个子数组,所以应该是n
综上合并排序的时间复杂度也是O(nlog2 n)