这篇博客记录一下两种比较快速的排序。
归并排序和快速排序。
归并排序(\(O(nlogn)\))
归并排序,顾名思义就是先递归后合并,这里画了一张图简单理解归并过程:
代码肯定要用到递归啦,递归到不能再细分就可以开始合并了。而合并是通过申请额外的内存空间来完成的,合并时在左右两个区间内进行比较,按照大小依次放入额外的空间中,最后复制回原数组,这样一直递归往上,就实现了排序。
代码:
void merge_sort(int arr[], int a,int b)//递归
{
if (a >= b)
return;
int mid = (a + b) / 2;
merge_sort(arr, a, mid);
merge_sort(arr, mid + 1, b);
merge(arr, a, mid, b);
}
void merge(int arr[], int l, int mid, int r)//合并
{
int *temp=new int[r - l + 1];//申请额外空间
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r)//比较并存放在额外的空间里
{
if (arr[i] <= arr[j])
temp[k++] = arr[i++];
else
temp[k++] = arr[j++];
}
/*将剩下元素的放入额外空间*/
while (i <= mid)
temp[k++] = arr[i++];
while (j <= r)
temp[k++] = arr[j++];
for (int i = l; i <= r; i++)//复制回原空间
arr[i] = temp[i - l];
delete[]temp;
}
时间复杂度推算:
归并排序的一大好处就是不管数据是什么样的,它的时间复杂度都为\(O(nlogn)\)。但它的缺点就是会用额外空间,空间复杂度为\(O(n)\)。
快速排序
快速排序也是利用二分法和递归进行排序。
下面是简单的快排过程:
快排的核心就在\(partition\)这个函数,他的作用就是再区间里随机找一个点(一般找最右边哪个元素),将这个点作为中心点,将比他小的元素放在左边,比他大的元素放在右边(升序),然后不断二分最后就得到一个排列好的数组了。
void quick_sort(int arr[], int a, int b)
{
if (a >= b)
return;
int p = partition(arr, a, b);
quick_sort(arr, a, p - 1);
quick_sort(arr, p + 1, b);//这里一定是p+1,将中心点排除在外,不然会在区间里有重复元素的时候陷入死循环!!!!
}
int partition(int arr[], int a, int b)
{
int i = a, j;
int p = arr[b];//记录中心点
for (j = a; j < b; j++)
{
if (arr[j] < p)//找到比中心点小的元素就交换
{
swap(arr[i], arr[j]);
i++;
}
}
swap(arr[i], arr[b]);//将中心点交换到中间
return i;//返回中心点下标
}
因为快排是个空间复杂度为\(O(lgn)\)的只有递归空间消耗的原地排序,所以在找中心点的时候为了不用额外空间算法设计的很巧妙。
这里将\(i\)作为一个游标,他的左边是已经处理的区间,右边为未处理区间,用\(j\)去找那些比中心点小的元素并和\(i\)交换,最后把选定的中心点和\(i\)交换就完成处理了。
值得注意的是快排是一种时间复杂度不稳定的算法,它一般情况下时间复杂度为\(O(nlogn)\),但在极端条件下会退化为\(O(n^2)\)。
快速排序和归并排序的区别
快速排序和归并的区别就在于它正好和递归的排序反过来。快速排序先排序再递归细分,排序是从上到下的。归并排序先递归细分再排序,排序是从下到上的。