一.快速排序简介
快速排序是由东尼·霍尔所发展的一种排序算法。其本质是二分思想,假设一维数组,存储数据为n个,快排每次单层排时间复杂度为O(N)(注意是一层排),每次排好一个数的位置然后二分,将递归深度降为接近完全二叉树的数高:logN,总的时间复杂度为O(N*logN)。
二.快速排序核心逻辑
快速排序是通过每次选择基准值 key ,然后排 key 的位置,使左边小于等于key,右边大于等于key,通过key分割出左右区间,[left,keyi-1 ]和[keyi+1,right];再用这种方式对左右区间进行分割。
关键:先对排好一个数,分割出左右区间,如果左区间有序,右区间有序,那么整体有序;
左右区间如何有序?
对左右区间进行快速排序(即递归调用),终止条件:区间长度为1,或区间不存在,即 left>=right。
三.快速排序单趟排序算法
(1)东尼·霍尔方法
假设一维数组a, 对其区间 [begin,end] 排升序,选择 a[begin] 为 key,end 找比 key 小的值,begin找比 key 大的值,然后 a[begin] 和 a[end] 交换;重复上述步骤,直至 begin 和 end 相遇。
逻辑代码如下:
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
int keyi = left;
int begin = left, end = right;
while (left < right)
{
//找小
while (left<right && a[right]>=a[keyi])
{
right--;
}
//找大
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[right], &a[left]);
}
Swap(&a[left], &a[keyi]);
//返回相遇位置的下标
return left;
}
(2)挖坑法
假设一维数组a, 假设一维数组a, 对其区间 [begin,end] 排升序,选择 a[begin] 为 key,begin位置形成坑,end 找比 key 小的值,begin找比 key 大的值。从end(非坑)开始找,找到比 key 小的值,填到 begin(坑) 中,此时,end 形成坑,begin(非坑)找小;循环往复,直至 begin 和 end 相遇,相遇位置为 坑,将 key 放进去,返回相遇位置的下标。
逻辑代码如下
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
int key = a[left];
int begin = left, end = right;
while (left < right)
{
while (left < right && a[right] >= key)
{
right--;
}
a[left] = a[right];
while (left<right && a[left]<=key)
{
left++;
}
a[right] = a[left];
}
a[left] = key;
//返回相遇位置的下标
return left;
}
(3)前后指针法
选择 left 位置值为key,prev 从left 开始,cur 从 left+1 开始(保证区间存在),cur 找比key 小的,找到了后,先++prev,然后交换;结束条件,cur 超出了区间范围,此时prev 的位置就是key的最终位置,将 prev 的位置与初始 key 的位置交换。
逻辑代码如下:
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
int key = a[left];
int prev = left, cur = left+1;
while (cur<=right)
{
if (a[cur] < key)
{
++prev;
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[left], &a[prev]);
return prev;
}
四.整体方法(递归)
逻辑代码如下:
void QuickSort(int* a, int left, int right)
{
//区间不存在 或 区间长度为1,返回
if (left >= right)
return;
int pos = PartSort1(a, left, right);
QuickSort(a, left, pos - 1);
QuickSort(a, pos + 1, right);
}
五.快速排序(非递归)
如何写非递归,首先来分析一下,快速排序的核心是什么?
答:核心思想:单趟排序和二分。核心数据:区间
那么非递归就很明确了,通过数据结构栈,模拟函数栈帧的压栈和出栈,存的值是区间。
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
//右先入栈,取的时候左先出
StackPush(&st, right);
StackPush(&st, left);
while (!StackEmpty(&st))
{
int begin = StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
//区间不存在 或 只剩一个值已经有序
if (begin >= end)
continue;
//区间[begin,pos-1] pos [pos+1,end]
int pos = PartSort1(a, begin, end);
//压入[pos+1,end]区间
StackPush(&st, end);
StackPush(&st, pos + 1);
//压入[begin,pos-1]区间
StackPush(&st, pos - 1);
StackPush(&st, begin);
}
StackDestroy(&st);
}
其中 Stack 为手动实现的栈
源文件链接:挺6的还/数据结构初阶 (gitee.com)
以上代码为完全模拟递归的过程,实现中可以优化,如:区间不存在 或 区间长度等于1不入栈。