一、 交换排序
1、基本思想
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
2、常见的交换排序
1、冒泡排序
2、快速排序
二、冒泡排序
1、基本思想
从左到右,相邻元素进行比较。每次比较一轮,就会找到序列中最大或者最小的那一个数。这个数就会从序列的最右边冒出来。
以从小到大排序为例:第一轮比较后,所有的数中最大的那个数就会浮到最右边;第二轮比较后,所有数中第二大的数就会浮到倒数第二个位置上.......就这样一轮一轮地比较,最后实现从小到大排序。
2、冒泡排序的特性总结
- 冒泡排序是一种非常容易理解的排序
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
3、冒泡排序
3.1、实现思想
3.2、具体实现
//冒泡排序
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n - 1; j++)
{
int exchange = 0;
for (int i = 0; i < n - 1 - j; i++)
{
if (a[i] > a[i + 1])
{
Swap(&a[i], &a[i + 1]);
exchange = 1;
}
}
if (exchange == 0)
{
break;
}
}
}
三、快速排序
1、基本思想
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
2、快速排序的特性总结
- 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
- 时间复杂度:O(N*logN)
- 空间复杂度:O(logN)
- 稳定性:不稳定
3、快速排序的不同版本
3.1 hoare版
分别定义两个下标 left 和 right,让他们一个从左往右开始遍历数组,另一个从右往左开始遍历数组,直到它们相遇后就结束遍历。在这个过程中,我们首先要定义一个基准值。为了方便就把left指向的值定为基准值。随后,在遍历数组的过程中如果下标 left 所指向的数大于基准值且下标 right 所指向的数小于基准值,那就互相交换数据。如果没有那两个下标就一直遍历数组直到相遇,两个下标相遇后,将基准值与此时在相遇位置的下标 left (right)的值进行交换,这样基准值就归位了。
int PartSort1(int* a, int left, int right)
{
int keyi = left;
while (left < right)
{
//keyi在左边,right先走
//keyi在右边,left先走
//找小
while (left < right && a[right] >= a[keyi])
{
right--;
}
//找大
while (left < right && a[left] <= a[keyi])
{
left++;
}
Swap(&a[left], &a[right]);
}
Swap(&a[keyi], &a[left]);
return left;
}
//快速排序
void QuickSort1(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
int keyi = PartSort1(a, begin, end);
QuickSort1(a, begin, keyi - 1);
QuickSort1(a, keyi+1, end);
}
3.2 挖坑版
先设立基准值和一个临时变量,这个临时变量我们称为 坑位。接下来的操作跟hore版本有些类似,唯一不同的是我们需要把遍历出来的数据放入坑位,而因为数据被转移出去形成的空位就自然而然的变成新的坑位。最后,当left 和 right 相遇时,把基准值放入最后一个空出来的位置中。
//快排--单次排序(挖坑法)
int PartSort3(int* a, int left, int right)
{
int key = a[left];
// 保存key值以后,左边形成第一个坑
int hole = left;
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[hole] = key;
return hole;
}
//快速排序
void QuickSort1(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
int keyi = PartSort3(a, begin, end);
QuickSort1(a, begin, keyi - 1);
QuickSort1(a, keyi+1, end);
}
3.3 前后指针版
//快排--单次排序(前后指针)
int PartSort4(int* a, int left, int right)
{
int keyi = left;
int prev = keyi;
int cur = prev + 1;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)
{
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[prev], &a[keyi]);
return prev;
}
//快速排序
void QuickSort1(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
int keyi = PartSort4(a, begin, end);
QuickSort1(a, begin, keyi - 1);
QuickSort1(a, keyi+1, end);
}
4、 快速排序的优化
4.1 三数取中来选取基准值key
从待排序数组中,选取开头、结尾和中间的数来进行比较。选择这三个数中不大不小的那一个并将其设为基准值key。
那么为什么要取中间呢?我们可以假设待排序的数列是一组高度有序的数列,显然首极大可能是最小值,尾极大可能是最大值,此时如果我们选取一个排在中间的值,哪怕是在最坏的情况下,begin和end只需要走到中间位置,那么这个中间值的位置也就确定下来,而不需要begin或end指针要把整个数列遍历一遍,从而大大提高快速排序的效率。
//三数取中 -- 取三个数中不大也不小的那个数
int GetMidi(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] < a[mid])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] > a[right]) //a[mid]最大
{
return left;
}
else
{
return right;
}
}
else
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] > a[right]) //a[mid]最小
{
return right;
}
else
{
return left;
}
}
}
4.2 当待排序的数据量较小时,可以使用直接插入排序来代替快排
当快速排序递归到只剩较小的区间时,我们就可以使用直接插入排序。这样子,小区间就不用递归分割排序,降低递归次数。
void QuickSort2(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
// 小区间优化,小区间不再递归分割排序,降低递归次数
if ((end - begin + 1) > 10)
{
int keyi = PartSort4(a, begin, end);
// [begin, keyi-1] keyi [keyi+1, end]
QuickSort2(a, begin, keyi - 1);
QuickSort2(a, keyi + 1, end);
}
//当数组中数据量较小时,可直接用插入排序
else
{
InsertSort(a + begin, end - begin + 1);
}
}
4.3 快速排序的非递归写法
非递归写法理解:
//快排非递归写法
void QuickSortNonR(int* a, int begin, int end)
{
ST st;
STInit(&st);
//先放入数组中最后一个数的下标,再放入第一个数的下标(也就是右边界和左边界)
STPush(&st, end);
STPush(&st, begin);
while (!STEmpty(&st))
{
int left = STTop(&st);
STPop(&st);
int right = STTop(&st);
STPop(&st);
int keyi = PartSort2(a, left, right);
if (keyi + 1 < right)
{
STPush(&st, right);
STPush(&st, keyi + 1);
}
if (left < keyi - 1)
{
STPush(&st, keyi - 1);
STPush(&st, left);
}
}
STDestroy(&st);
}