本期是对排序的深入讲解,排序能让我对循环和递归的第二次理解,让我们重新感受到循环和递归的强大!学完排序感觉脑子在燃烧,这可能就是对编程的热爱吧。
快排(Quicksort)
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快排的思想:其核心思想是 "分治法"------ 通过 "选基准、分区、递归排序" 三步,将大问题拆解为小问题解决。
如下图:

先设立一个对比关键,先让右边先走,找到比key小的值停下来,然后再让左边走,找到比key大的值停下来,然后让小值和大值进行交换(直到他们相遇),最后肯定是碰到小于key的值停下来(因为是右边先找小),相遇之后在与key交换位置。最后在重新设定key。
代码实现
c
// 快速排序hoare版本
void QuickSort(int* a, int left, int right)
{
int ki = left;
int begin=left, end=right;
while (begin < end)
{
while (begin < end&&a[end] >= a[ki])
{
end--;
}
while (begin < end && a[begin] <= a[ki]) {
begin++;
}
Swap(&a[end], &a[begin]);
}
Swap(&a[ki], &a[right]);
ki = begin;
QuickSort(a, left, ki-1);
QuickSort(a, ki+1, right)
}
这是霍尔版本的快排
快排前后指针法

这是用两个指针实现的快排,其中cur找到比key小的 找到比key小的值就让prev指针++(往后走),cur指针++(也往后走)。cur遇到比key大的也往后走,直到遇到比key小的(prev++),然后再让prev(此时它指向的是大于key的值)和cur(指向的是小于key的值),并进行交换。
前后指针法实现
c
void QuickSort(int* a, int left, int right)
{
int keyi = left;
int perv = left;
int cur = perv + 1;
while (cur <= right)
{
if (a[cur] < a[keyi]&&++perv!=cur)
Swap(&a[cur], &a[perv]);
cur++;
}
Swap(&a[perv], &a[keyi]);
int ki=perv;
QuickSort(a, left, ki - 1);
QuickSort(a, ki + 1, right);
}
快速排序非递归版本
非递归版本想起来比较繁琐,最好要用栈(数据结构)实现,当然也可以用队列(但是比较麻烦)区别就是一个深度优先遍历( DFS),一个广度优先遍历(BFS)。
首先,我用栈保存所用区间。通过向栈中添加数据(所用区间),取数据。利用循环的方式,来实现非递归的实现原理。
快速排序非递归版本代码实现
c
void QuickSortNonR(int* a, int left, int right)
{
Stack sl;
StackInit(&sl);
StackPush(&sl, right);
StackPush(&sl, left);
while (!StackEmpty(&sl))
{
int begin = StackTop(&sl);
StackPop(&sl);
int end = StackTop(&sl);
StackPop(&sl);
int keyi = PartSort3(a, begin, end);
if (keyi + 1 < end)
{
StackPush(&sl, end);
StackPush(&sl, keyi+1);
}
if (keyi - 1 > begin)
{
StackPush(&sl, keyi - 1);
StackPush(&sl, begin);
}
}
}
快速排序挖坑法
这是关于快速排序的另外一种写法,就是根据为什么左边设关键字,右边先走而不是左边先走设计而来的。
核心思想:就是如果左边是坑,就让右边先走,如果右边是坑就让左边先走,同时将符合的数据移到坑中,在更换坑的位置。
如下图:

就比如说先把6给key,左边为坑,右边先走,找小,找到之后放到坑里,同时更换坑的位置在右边,让左边找大, 找到之后放到坑里,更换坑的位置,在依次下去;
挖坑法代码实现
c
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
int ki = a[left];
int begin = left;//坑位
while (left<right)
{
while (a[right] >= ki&& left < right)
{
right--;
}
a[begin] = a[right];
begin = right;
while (a[left] <= ki && left < right)
{
left++;
}
a[begin] = a[left];
begin = left;
}
a[begin] = ki;
return begin;
}
void QuickSortPartSort2(int *a,int left,int right)
{
if (left >= right)
return;
int pi = PartSort2(a, left, right);
QuickSortPartSort2(a, left, pi - 1);
QuickSortPartSort2(a, pi + 1, right);
}
快排优化
最后快排还可以优化
选出key不大不小的值 这是三数取中法,能更快确定中间值
还可以经行小区间优化,当剩下10个数的时候,就不要去递归了,直接用插入排序排完返回即可。
快排优化代码
c
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
//三数选一
int midi = GetMidi(a,left,right);
Swap(&a[left], &a[midi]);
//小区间优化
if ((right - left + 1) < 10)
{
InsertSort(a+left, (right - left + 1));
}
else
{
/*int ki = left;
int begin = left, end = right;
while (begin < end)
{
while (begin < end && a[end] >= a[ki])
{
end--;
}
while (begin < end && a[begin] <= a[ki])
{
begin++;
}
Swap(&a[end], &a[begin]);
}*/
//Swap(&a[ki], &a[begin]);
int ki = PartSort1(a,left,right);
QuickSort(a, left, ki - 1);
QuickSort(a, ki + 1, right);
}
}
最后快排的时间复杂度是O(N*logN)。