目录
交换排序
基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
QuickSort快速排序
快速排序:是Hoare于1962年提出的一种二叉树结构的交换排序方法。我们今天学习的就是基于Hoare思想的一种排序方式。
快速排序的特性总结:
1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)3. 空间复杂度:O(logN)
4. 稳定性:不稳定
单趟基本思想(选取分界线位置keyi):
- 任取待排序元素序列中的某元素(最左边/最右边的数)作为基准值a[keyi]
- 按照该排序码将待排序集合分割成两子序列
- 左子序列中所有元素均小于基准值
- 右子序列中所有元素均大于基准值
整体基本思想:
- 然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
- 核心是区间
简单来说,整体思路是比较简单的,单趟基本思路有3中方式去实现:
- hoare版本1
- 挖坑版本2
- 前后指针版3
//有n个数----[0 , n-1]
//假设按照升序对array数组中[begin, end]区间中的元素进行排序
0 n-1
void QuickSort(int array[], int begin, int end)
{
//区间只有一个值/没有值(区间)返回
if (begin >= end)
return;
// 按照keyi基准值对array数组的 [begin, end]区间中的元素进行划分
// 有三种方式去找到keyi
//PartSort1 PartSort2 PartSort3
int keyi = PartSort1/2/3(array, begin, end);
// 划分成功后以div为边界形成了左中右三部分
//[begin, keyi-1] keyi [keyi+1, end)
//keyi的位置的值 已经排好了
// 递归排[begin, keyi-1]
QuickSort(array, begin, keyi - 1);
// 递归排[keyi+1, right]
QuickSort(array, keyi + 1, end);
}
上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,大家在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。
Hoare整体思路
单趟基本思想(选取分界线位置keyi):
- 任取待排序元素序列中的某元素(最左边/最右边的数)作为基准值a[keyi]
- 按照该排序码将待排序集合分割成两子序列
- 左子序列中所有元素均小于基准值
- 右子序列中所有元素均大于基准值
- 目的:让keyi对应位置的值排到该排到的位置
值的设置
- 区间是[begin,end] (注意是下标)
选取关键字的下标keyi a[keyi]=key
设置两个下标用来值得比较:left right
循环放值
若left所对应的值小于<keyi所对应的值,符合要求,过掉
若left所对应的值大于>keyi所对应的值,不符合要求,停下
若right所对应的值大于>keyi所对应的值,符合要求,过掉
若right所对应的值小于<keyi所对应的值,不符合要求,停下
循环之后left 和 right找到各自符合的值,交换即可
把比a[keyi]小的值放到左边,比a[keyi]大的值放到右边
相遇中线
left和right相遇,停止循环
相遇的位置,即使中线位置
与a[keyi]交换即可,则a[keyi]放到该放到的位置,a[keyi]已经排好了
整体基本思想:
- 然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
- 单趟把key放到该放到的位置
- 左边有序+key有序+右边有序就整体有序
- 分治问题:左边有序+key有序+右边有序
- 返回条件
- 递归到只有一个元素的区间
- 没有元素的区间。
重点突破:
- left和right相遇的位置对应的数值总是比keyi所对应的值小(谁先走问题)
- 相遇的三种情况分析
- 选key值三种方式
- 直接选取最左边/右边
- 选取序列中的随机值key,和最左边/最右边的交换
- 三数取中法选key,和最左边/最右边的交换
- 递归的条件
易错点突破:
- 比较起始位置从begin // begin+1 (顺序结构)
- 取左端的值,先走右边;取右端的值,先走左边。
- 相遇点的处理(错位)
- 等于=的问题
- 局部变量的问题
图解分析
【以最左边的元素为key>>>>>>单趟】
【以最右边的元素为key>>>>>>>>完整版】
【递归分解图】
Hoare版本代码
// 0 n-1
int PartSort1(int* a, int begin, int end)//返回分割线
{
int left = begin;
int right = end;
int keyi = begin;
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]);
keyi = left;
return keyi;
//分割 [begin,keyi-1] keyi [keyi+1,end]
}
总代码
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void QuickSort(int* a, int begin,int end)
{
if (begin >= end)//只有1个元素 没有区间
{
return;
}
int keyi = PartSort1(a, begin, end);
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
// 0 n-1
int PartSort1(int* a, int begin, int end)//返回分割线
{
int left = begin;
int right = end;
int keyi = begin;
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]);
keyi = left;
return keyi;
//分割 [begin,keyi-1] keyi [keyi+1,end]
}
重点突破
Q1递归结束条件
- 子问题分下来最后只有一个元素
- 子问题分下来没有元素
if (begin >= end)//只有1个元素 没有区间
{
return;
}
begin > end //没有元素 [1,0]
begin = end //只有1个元素
Q2选key值(下篇细讲)
- 直接选取最左边/右边
- 选取序列中的随机值key,和最左边/最右边的交换
- 三数取中法选key,和最左边/最右边的交换(主流的解决办法)
Q3相遇情况3种
假设:取最左边的数为key值。从右边找小开始走。
- 情况1left遇right:right先走,right找到小的,right停止。left没有遇到大值就与right相遇了。相遇位置是right,比key小。
- 情况2right遇到left:right先走,遇到了小的。left再走,遇到了大的。交换第一轮。(则小的交换到了left的位置,大的交换到了right的位置)right再走,一直没有遇到小的,直接遇到left,这里的数值比key小。
- 情况3顺序结构:全是比key大的值,left不走,right直接到key值的位置。
那么取最右边的数为key值。从最左边找小开始走。自己分析下
Q4why与相遇位置总比key小
根据上诉的三种情况来看
- left遇到right
- right遇到left
- 顺序结构
明确的知道:
- 取最左端的值做key,则右边先走
- 取最右端的值做key,则左边先走
易错点突破(图解)
Q1死循环
遇到与key值相等的数。
//死循环=问题
int PartSort1(int* a, int begin, int end)
{
int left = begin;
int right = end;
int keyi = begin;
while (left < right)
{
while (a[right] > a[keyi]) //while (a[right] >= a[keyi])
right--;
while (a[left] < a[keyi])//while (a[right] 《= a[keyi])
left++;
Swap(&a[right], &a[left]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
return keyi;
}
Q2相遇错位问题
偶数个数的序列,left < right 还满足循环条件,进入循环,继续走错位。
//相遇错位问题
int PartSort1(int* a, int begin, int end)
{
int left = begin;
int right = end;
int keyi = begin;
while (left < right)
{
while (a[right] >= a[keyi])// while (left < right && a[right] >= a[keyi])
right--;
while (a[left] <= a[keyi])// while (left < right && a[right] <= a[keyi])
left++;
Swap(&a[right], &a[left]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
return keyi;
}
Q3局部变量(一趟为例)
- 要修改的是数组序列的值,而不是局部变量。
- 局部变量除了函数栈帧就销毁了。
- 所以要修改数组序列的值,必须&a[i]中的值,所以数组序列的值最好用下标表示。
//局部变量问题
void QuickSort1(int* a, int begin, int end)
{
int left = begin;
int right = end;
//int keyi = begin;
int key = a[begin];
while (left < right)
{
while (left < right && a[right] >= key)
//while (left < right && a[right] >= a[keyi])
{
right--;
}
while (left < right && a[left] <= key)
//while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[right], &a[left]);
}
Swap(&a[left], &key);
//Swap(&a[left], &a[keyi]);
}
【正确的结果】
【错误❌的结果】
Q4left起始位置
- left从a[0]开始查找
- left从a[1]开始查找>>>>>>>>>>遇到顺序结构就会问题
//left起始位置
void QuickSort1(int* a, int begin, int end)
{
int left = begin+1;
int right = end;
int keyi = begin;
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]);
}
时间复杂度
- 最好情况:O(N*logN)
- 最坏情况(顺序结构):O(N^2)
- >>>>>>>>解决方法1:随机数选key
- >>>>>>>>解决方法2:三数取中选key
🙂感谢大家的阅读,若有错误和不足,欢迎指正。