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

先设立一个对比关键,先让右边先走,找到比key小的值停下来,然后再让左边走,找到比key大的值停下来,然后让小值和大值进行交换(直到他们相遇),最后肯定是碰到小于key的值停下来(因为是右边先找小),相遇之后在与key交换位置。最后在重新设定key。
代码实现
// 快速排序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的值),并进行交换。
前后指针法实现
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)。
首先,我用栈保存所用区间。通过向栈中添加数据(所用区间),取数据。利用循环的方式,来实现非递归的实现原理。
快速排序非递归版本代码实现
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,左边为坑,右边先走,找小,找到之后放到坑里,同时更换坑的位置在右边,让左边找大, 找到之后放到坑里,更换坑的位置,在依次下去;
挖坑法代码实现
// 快速排序挖坑法
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个数的时候,就不要去递归了,直接用插入排序排完返回即可。
快排优化代码
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)。
