快速排序(英语:Quicksort),又称分区交换排序,简称「快排」,是一种被广泛运用的排序算法。
快速排序的工作原理是通过分治的方式来将一个数组排序。
快速排序分为三个过程:
- 将数列划分为两部分(要求保证相对大小关系);
- 递归到两个子序列中分别进行快速排序;
- 不用合并,因为此时数列已经完全有序。
对于完成一个快排函数,需要传入的参数分别是需要排序的数组a,最左边数据的下标以及最右边数据的下标
第一种方法:交换法
而实现交换的函数比较简单,这里不过多赘述。
void Swap(int* a,int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
这一方法的过程是:
先right从右向左开始遍历,right向左走,如果遇到比a[key]更加小的值就停下来;
再left从左向右开始遍历,left开始向右走,相反在遇到比a[key]更加大的值就停下来;
将数组中的两个值作交换;
right再重新开始继续向左走,不断重复这一过程直到二者相遇;
相遇后将相遇处数组的值与a[key]交换,同时将key的值改为相遇处的下标;
向下递归。
如图,举个简单的例子。
void QuickSort(int* a, int left, int right) {
if (left >= right) {//放在最前
return;
}
int begin = left, end = right;//left与right 会被改变,用两个变量保留它们的值用于接下来的递归
int key = left;
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[left], &a[key]);
key = left;
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
在测试一下结果:
没有问题。
然而,以这种方法在进行对一个十分接近有序的数组进行排序时,会出现一个问题。
即因为 key 值很小 right 一直向右 直接与left相遇 每次递归都将全部 n 个数据遍历 并且一次也只排好了一个值 。 如此一来大大降低了效率。
对于这种情况,我们可以进行一些优化。
一种方法是找随机值
取随机值法让key取随机值,再把随机值交换到首位
通过 rand()% (right - left + 1) 取到在left 到 right 的一个随机值
//取随机值法让key取随机值,再把随机值交换到首位
int random = rand()% (right - left + 1);
random = random + left;
Swap(&a[left], &a[random]);
random = random + left的原因在递归时是存在left不为0的情况。
另一种方法是三值取中
int Getmid(int* arr,int l,int r) {//需要return 的是下标
int m = (r + l) / 2;
int a = arr[l];
int b = arr[r];
int c = arr[m];
if (a >= b && b > c ) {//注意a,b,c中存在相等的情况
return r;
}
else if (a >= c && c > b) {
return m;
}
else if (c >= a && a > b) {
return l;
}
else if (b >= a && a > c) {
return l;
}
else if (b >= c && c > a) {
return m;
}
else if (c >= b && b > a) {
return r;
}
}
在数组的第一个,最后一个与中间的一个数据中取出中值,再把其交换到首位
优化后如下
//交换法的快速排序
void QuickSort(int* a, int left, int right) {
if (left >= right) {//放在最前
return;
}
//小区间优化,区间较小时,使用插入排序减少递归次数
int begin = left, end = right;//left与right 会被改变,用两个变量保留它们的值用于接下来的递归
//取随机值法让key取随机值,再把随机值交换到首位
//int random = rand()% (right - left + 1);
//random = random + left;
//Swap(&a[left], &a[random]);
//三值取中法
int mid = Getmid(a,left,right);
//printf("%d\n", a[mid]);
Swap(&a[left], &a[mid]);
int key = left;
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[left], &a[key]);
key = left;
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
第二种方法:挖坑法
代码如下:
//挖坑法
void QuickSort2(int* a, int left, int right) {
if (left >= right) {//放在最前
return;
}
int begin = left, end = right;//left与right 会被改变,用两个变量保留它们的值用于接下来的递归
int key = left;
int tmp = a[key];//a[left]在第一次放入左边的坑时发生改变
while (left < right) {
while (left < right && a[right] >= tmp) {
right--;
}
a[left] = a[right];//从右边开始 找比a[key]小的值,找到了之后 把值放进前一个坑 在此位置在挖一个坑
while (left < right && a[left] <= tmp) {
left++;
}
a[right] = a[left]; //从左边开始 找比a[key]大的值,找到了之后 把值放进放进前一个坑 在此位置在挖一个坑
}
a[left] = tmp;
key = left;
QuickSort2(a, begin, key - 1);
QuickSort2(a, key + 1, end);
}
第三种方法:双指针法
双指针法总体上是:当cur 指针指向的数 小于a[key] 时先prev++,再把cur与prev指向的数交换,cur再++;当cur 指针指向的数 小于a[key] 时,cur++。
这种方法较前两种更为简单。
//双指针法的快速排序 cur >= key cur++ ; cur < key , prev++ cur++ ;
void QuickSort3(int* a, int left, int right) {
if (left >= right) {
return;
}
int key = left;
int prev = left, cur = left + 1 ;
while (cur <= right) {
if (a[cur] < a[key]) {
++prev;
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[key], &a[prev]);
key = prev;
QuickSort3(a, left, key - 1);
QuickSort3(a, key + 1, right);
}
(本人资历尚浅,如果文中有任何问题,劳请指出。)