目录
- 快速排序
- ①hoare版本
- 思路分析
- 代码实现
- 时间复杂度
- <整体优化>
- <局部优化>
- ②挖坑法
- 思路分析
- 代码实现
- ③前后指针法
- 思路分析
- 代码实现
- ④非递归快排
- 思路分析
- 代码实现
- 以上代码汇总
快速排序
①hoare版本
思路分析
以上图为例:
指定一个数为a[key] = 6,a[right] 从尾出发找比 6 小的数,找到后停下,接着a[left] 从头出发找比 6 大的数,找到后停下 → Swap(&a[left], &a[right])
,最终 a[right] 和 a[left] 相遇 → Swap(&a[key], &a[right]);//left==right
- 这样就分割出了左右区间,左区间的数都比 6 小,右区间的数都比 6 大
- 6 已经落到正确的位置
- 解下来就是以左右区间为新数列,重复上述操作
Swap(&a[key], &a[right]);//left==right
分析:
- right 停住,left 往后走 与 right 相遇 ⇨ 相遇的位置是right 停住的位置 👉 right 找到要找的数才会停下(始终在 left < right 的约束下),此时相遇点就是 right 找到的比 a[key] 小的数
- left 停住,right 往前走 与 left 相遇 ⇨ 相遇的位置是 left 停住的位置 👉 right 比 left 先走,right 找到了 ,left 才会动,left停住之后,则会Swap(&a[left], &a[right]) ,Swap 之后此时a[left] < a[key] ,left 停住,right 往前走(如果一直未找到要找的数)与 left 相遇,此时相遇点就是比 a[key] 小的数
⭐左边做 key,右边先走;
⭐右边做 key,左边先走;
- 一些bug代码分析:
while (left < right)
{
while (a[right] > a[key])
{
--right;
}
while (a[left] < a[key])
{
++left;
}
Swap(&a[left], &a[right]);
}
Swap(&a[key], &a[right]);//left==right
- 相等的情况:
6 | 6 | 6 | ||||||
---|---|---|---|---|---|---|---|---|
a[key] | a[left] | a[right] |
Swap之后:
6 | 6 | 6 | ||||||
---|---|---|---|---|---|---|---|---|
a[key] | a[left] | a[right] |
这样循环就会卡住 👇
while (left < right)
{
while (a[right] > a[key]){}//循环不进入
while (a[left] < a[key]){}//循环不进入
Swap(&a[left], &a[right]);//一直无限循环Swap
}
- 越界
while (left < right)
{
while (a[right] >= a[key]){
--right;
}
while (a[left] <= a[key]){
++left;
}
Swap(&a[left], &a[right]);
}
right 飞出去的情况:
6 | 7 | 8 | 9 | 6 | 10 | 6 | 13 | 14 |
---|---|---|---|---|---|---|---|---|
a[key] |
left 飞出去的情况:
6 | 1 | 6 | 4 | 5 | 3 | 6 | 2 | 3 |
---|---|---|---|---|---|---|---|---|
a[key] |
因此,right 和 left 每次改变之后都需要判断是否 left < right
代码实现
int hoareSort1(int* a, int left, int right)
{
assert(a);
if (left >= right)
return;
int key = left, begin = left, end = right;
while (left < right)
{
while (left < right && a[right] >= a[key])
{
--right;
}
while (left < right && a[left] <= a[key])
{
++left;
}
Swap(&a[left], &a[right]);
}
Swap(&a[key], &a[right]);//left==right
//[begin,left-1] [right+1,end]
hoareSort1(a, begin, left - 1);
hoareSort1(a, right + 1, end);
return right;
}
时间复杂度
- 最好的情况(像二叉树一样):如下图 时间复杂度为O(N*logN)
- 比较一般的情况:
- 最坏的情况:有序
Σ = n + (n-1) + (n-2) + …… + 2 + 1 = (n+1)*n/2
∴ 时间复杂度为 O(N²)
<整体优化>
- 三数取中(begin、middle、end)
- 例如:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|
[begin] | [middle] | [end] |
∵ 1 < 6 < 9
∴ 选择 a[middle] 作为 a[key]
// 三数取中
int GetMidIndex(int* a, int left, int right)
{
int begin = left, end = right;
int middle = (left + right) / 2;
if (a[begin] < a[end])
{
if (a[middle] < a[begin])
return begin;
else if (a[middle] > a[end])
return end;
else
return middle;
}
else
{
if (a[middle] < a[end])
return end;
else if (a[middle] > a[begin])
return begin;
else
return middle;
}
}
// 快速排序hoare版本
int hoareSort1(int* a, int left, int right)
{
assert(a);
if (left >= right)
return;
int key = left, begin = left, end = right;
int mid = GetMidIndex(a, left, right);
Swap(&a[mid], &a[key]);
while (left < right)
{
while (left < right && a[right] >= a[key])
{
--right;
}
while (left < right && a[left] <= a[key])
{
++left;
}
Swap(&a[left], &a[right]);
}
Swap(&a[key], &a[right]);//left==right
//[begin,left-1] [right+1,end]
hoareSort1(a, begin, left - 1);
hoareSort1(a, right + 1, end);
return right;
}
⭐int mid = GetMidIndex(a, left, right);
⭐Swap(&a[mid], &a[key]);
- 为什么要Swap? 如果不Swap↓
因为,最后 a[key] 会和 left 与 right 相遇的数交换,所以 [key] 必须在头部位置。
时间复杂度分析:针对局部有序或有序的情况效率明显提升
时间复杂度为 O(N) = N*logN
<局部优化>
递归到小区间的时候用插入排序
int hoareSort1(int* a, int left, int right)
{
assert(a);
if (left >= right)
return;
if ((right - left + 1) < 5)
{
InsertSort(a + left, right - left + 1);
}
else
{
int key = left, begin = left, end = right;
int mid = GetMidIndex(a, left, right);
Swap(&a[mid], &a[key]);
while (left < right)
{
while (left < right && a[right] >= a[key])
{
--right;
}
while (left < right && a[left] <= a[key])
{
++left;
}
Swap(&a[left], &a[right]);
}
Swap(&a[key], &a[right]);//left==right
//[begin,left-1] [right+1,end]
hoareSort1(a, begin, left - 1);
hoareSort1(a, right + 1, end);
}
return right;
}
②挖坑法
思路分析
把选定做 key 的数存储在新变量中。left 和 right 找到 数 填坑里,原位置变新坑。
代码实现
// 快速排序挖坑法
int HoleSort2(int* a, int left, int right)
{
if (left >= right)
return;
int key = a[left];
int hole = left, begin = left, end = right;
while (left < right)
{
while (left < right && a[right] >= key)
{
--right;
}
a[hole] = a[right];
hole = right;
while (left < right && a[left] <= key)
{
++left;
}
a[hole] = a[left];
hole = left;
}
a[right] = key;
//[begin,left-1] [right+1,end]
HoleSort2(a, begin, left - 1);
HoleSort2(a, right + 1, end);
return right;
}
③前后指针法
思路分析
- cur 找比 key所指向 小的数
- 找到后,++prev,交换 cur 和 prev 指向的内容
- cur 走到尽头后,最后交换 key 和 prev
- 小的数往前←,大的数往后→
cur 找到 比 data 小的数时的情况:👇
循环结束的情况:👇
- 示例:
代码实现
// 快速排序前后指针法
int PointSort3(int* a, int left, int right)
{
assert(a);
if (left >= right)
return;
int keyi = left;
int prev = left, cur = left;
while (cur <= right)
{
if (a[cur] < a[keyi] && cur != prev)
Swap(&a[++prev], &a[cur]);
++cur;
}
Swap(&a[keyi], &a[prev]);
//[left,prev-1][prev+1,right]
PointSort3(a, left, prev - 1);
PointSort3(a, prev + 1, right);
return prev;
}
④非递归快排
思路分析
利用数据结构栈 → 广度遍历
递归→ 深度遍历
- 将左右区间
[begin,end]
存入栈中 - 循环:
- 1.取出栈中的左右区间
[begin,end]
,在这个区间内进行快排 - 2.一次快排之后,得到两个新的左右区间
[begin,end] → [begin,keyi-1] keyi [keyi+1,end]
,将其存入栈中(注意顺序) - 1.取出栈中的左右区间
[keyi+1,end]
,在在这个区间内进行快排 - 2.一次快排之后,得到两个新的左右区间……
- ……
- 1.取出栈中的左右区间
代码实现
// 快速排序 非递归实现
#include "Stack.h"
void QuickSort(int* a, int left, int right)
{
assert(a);
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (!StackEmpty(&st))
{
int end = StackTop(&st);
StackPop(&st);
int begin = StackTop(&st);
StackPop(&st);
int keyi = hoareSort1(a, begin, end);//快排
//[begin,keyi-1] keyi [keyi+1,end]
if (keyi + 1 < end)
{
StackPush(&st, keyi + 1);
StackPush(&st, end);
}
if (begin < keyi - 1)
{
StackPush(&st, begin);
StackPush(&st, keyi - 1);
}
}
StackDestroy(&st);
}
以上代码汇总
// 快速排序递归实现
// 三数取中
int GetMidIndex(int* a, int left, int right)
{
int begin = left, end = right;
int middle = (left + right) / 2;
if (a[begin] < a[end])
{
if (a[middle] < a[begin])
return begin;
else if (a[middle] > a[end])
return end;
else
return middle;
}
else
{
if (a[middle] < a[end])
return end;
else if (a[middle] > a[begin])
return begin;
else
return middle;
}
}
// 快速排序hoare版本
int hoareSort1(int* a, int left, int right)
{
assert(a);
if (left >= right)
return;
if ((right - left + 1) < 5)
{
InsertSort(a + left, right - left + 1);
}
else
{
int key = left, begin = left, end = right;
int mid = GetMidIndex(a, left, right);
Swap(&a[mid], &a[key]);
while (left < right)
{
while (left < right && a[right] >= a[key])
{
--right;
}
while (left < right && a[left] <= a[key])
{
++left;
}
Swap(&a[left], &a[right]);
}
Swap(&a[key], &a[right]);//left==right
//[begin,left-1] [right+1,end]
hoareSort1(a, begin, left - 1);
hoareSort1(a, right + 1, end);
}
return right;
}
// 快速排序挖坑法
int HoleSort2(int* a, int left, int right)
{
if (left >= right)
return;
int key = a[left];
int hole = left, begin = left, end = right;
while (left < right)
{
while (left < right && a[right] >= key)
{
--right;
}
a[hole] = a[right];
hole = right;
while (left < right && a[left] <= key)
{
++left;
}
a[hole] = a[left];
hole = left;
}
a[right] = key;
//[begin,left-1] [right+1,end]
HoleSort2(a, begin, left - 1);
HoleSort2(a, right + 1, end);
return right;
}
// 快速排序前后指针法
int PointSort3(int* a, int left, int right)
{
assert(a);
if (left >= right)
return;
int keyi = left;
int prev = left, cur = left;
while (cur <= right)
{
if (a[cur] < a[keyi] && cur != prev)
Swap(&a[++prev], &a[cur]);
++cur;
}
Swap(&a[keyi], &a[prev]);
//[left,prev-1][prev+1,right]
PointSort3(a, left, prev - 1);
PointSort3(a, prev + 1, right);
return prev;
}
// 快速排序 非递归实现
void QuickSort(int* a, int left, int right)
{
assert(a);
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (!StackEmpty(&st))
{
int end = StackTop(&st);
StackPop(&st);
int begin = StackTop(&st);
StackPop(&st);
int keyi = hoareSort1(a, begin, end);
//[begin,keyi-1] keyi [keyi+1,end]
if (keyi + 1 < end)
{
StackPush(&st, keyi + 1);
StackPush(&st, end);
}
if (begin < keyi - 1)
{
StackPush(&st, begin);
StackPush(&st, keyi - 1);
}
}
StackDestroy(&st);
}