交换排序
基本思想:所谓交换,就是根据序列中两个 记录键值 的比较结果来对换这两个记录在序列中的位置。交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
冒泡排序的特性总结:
- 冒泡排序是一种非常容易理解的排序,实践意义十分小。
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
快速排序
快速排序是
一种类似于二叉树结构的交换排序方法,其基本思想为:
任取待排序元素序列中
的某元素(key)作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右
子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
。
基本框架为
假设按照升序对 array 数组中 [left, right) 区间中的元素进行排序按照基准值(div)对 array 数组的 [left, right) 区间中的元素进行划分划分成功后以 div 为边界形成了左右两部分 [left, div) 和 [div+1, right)递归排 [left, div)递归排 [div+1, right)
可以发现,运行规则与二叉树的遍历十分相近,因此在写这个框架时;可以想想二叉树遍历的框架。之后可以发现找到如何按照基准值(div)对array数组的 [left, right)区间中的元素进行划分即可。
常见方法
第一种hoare版本
基本思想
以按小序为例
先找key值,左边和右边各向中间找;右边先向中间找小于key的值,左边在向中间找大于key的值,都找到后,交换左边和右边的值;然后重复此过程,知道右边与左边相遇,然后让相遇位置与key交换。至此完成一次循环。
图片解析
到这里,就是与二叉树的遍历类似。从相遇位置分开,然后递归。
当然如果认真思考和观察,一定会想到,
为什么相遇位置一定比key小(相遇位置一定与key交换)或者说达到这个目的有什么要求?
以上述数据为例子
左边做key,右边先走,可以保证相遇位置比key小
右边做key,左边先走,可以保证相遇位置比key大
以左边做key为例子
分析:一共有两种情况
- 左边移动与右边相遇,右边先停下找到了比key小的,然后左边与右边相遇(就是上述图片的情况)
- 右边移动与左边相遇,一定是之前左边与右边交换完,左边变成比key小的数后,右边移动与左边相遇。
因此无论哪种情况,都能要求的情况。
参考代码
void QuickSort(int* a, int left, int right)
{
if ( left >= right)
return;
int keyi = Mid(a,left,right);
int begin = left, end = right;
while (begin < end)
{
while (a[end] >= a[keyi])
{
end--;
if (begin >= end)
break;
}
if (begin >= end)
break;
while (a[begin] <= a[keyi])
{
begin++;
if (begin >= end)
break;
}
Swap(&a[begin], &a[end]);
}
Swap(&a[keyi], &a[begin]);
int left1 = left, right1 = begin - 1;
int left2 = begin + 1, right2 = right;
QuickSort(a, left1, right1);
QuickSort(a, left2, right2);
}
挖坑法
挖坑法是对上述方法的一个进阶;虽然
没有效率上的提升,但是能忽略为什么左边做key,右边先走的问题 和 为什么 相遇位置比key小的问题。
基本思想
先弄一个数据存放在一个临时变量 key= 中。然后就形成了第一个坑位;之后与上面的方法差不多,右边先向中找,找到比key小的,补到第一个坑位,然后又形成了一个新坑位;然后左边先向中间找比key 大的,补到新的坑位,并产生一个新坑位。重复此操作知道左边与右边相遇,把存到key的数,补到相遇位置的坑位。
图片解析













代码参考
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = Mid(a, left, right);
int begin = left, end = right;
int key = a[keyi];
while (begin < end)
{
while (a[end] >= a[keyi])
{
end--;
if (begin >= end)
break;
}
a[keyi] = a[end];
keyi = end;
if (begin >= end)
break;
while (a[begin] <= a[keyi])
{
begin++;
if (begin >= end)
break;
}
a[keyi] = a[begin];
keyi = begin;
}
a[keyi] = key;
int left1 = left, right1 = begin - 1;
int left2 = begin + 1, right2 = right;
QuickSort(a, left1, right1);
QuickSort(a, left2, right2);
}
对快速排序的优化
key的寻找
如果key为最大或者最小时,会导致程序的运行很缓慢,因此设计Mid函数 来寻找key值
int Mid(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[mid]> a[left])
{
if (a[right] > a[mid])
{
return mid;
}
if (a[left] > a[right])
{
return left;
}
else {
return right;
}
}
else if (a[mid] < a[left])
{
if (a[right] < a[mid])
{
return mid;
}
if (a[left] < a[right])
{
return left;
}
else {
return right;
}
}
}
小区间优化
经过使用,运算后可以知道,快速排序,在元素数量十分少的情况下运行与其他排序相比是十分缓慢的。因此小区间优化可以大大提升快排的效率
一般会在上述代码中添加
if (right - left + 1<= 10) { InertSort(a, right - left + 1); }
其中其他排序可以在其他文章中找到;
快速排序的特性总结:
- 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排
- 时间复杂度:O(N*logN)
通过这个图片可知,最坏每组会循环 N 次 一共 有 log N 组 ( 与二叉树 层数 算 个数 一样 的算法 不知道的 可以 翻我以前的 文章)
- 空间复杂度:O(logN) 递归层数 log N 则会有 log N 的空间复杂度;
- 稳定性:不稳定