快速排序 2019年8月23日17:45:21
之前我们已经分析过 冒泡排序,我建议:当你在看这篇博客的时候,请先回顾 冒泡排序:详见DSA之十大排序算法第一种:Bubble sort

文章目录

  • 一次划分的三种实现方式:
    • 第一种 挖坑填补法:
    • 第二种 左右交换法:
    • 第三种 双指针前移法:
  • 关于基准数pivotkey 的选取方式:
    • 第一种 固定基准数的选取
    • 第二种 随机基准数的选取
    • 第三种 三数取中 基准数选取
  • 快排算法的优化
    • 第一种 当待子序列的长度 被划分到较小数值时,采用插入排序
    • 第二种 聚集相同基准法

冒泡排序的时间复杂度在最差的情况下:达到了平方级。这当然不符合我们的要求,今天主要详解 最优秀的内部排序中的 Quick Sort。虽然我经常用,说实话 我是怎么用的呢? sort方法!!! C++中的sort 泛型算法就是基于快排实现(这样说 不是很严谨! 因为STL中的sort并非只是普通的快速排序,除了对普通的快速排序进行优化,它还结合了插入排序和堆排序。根据不同的数量级别以及不同情况,能自动选用合适的排序方法。当数据量较大时采用快速排序,分段递归。一旦分段后的数据量小于某个阀值,为避免递归调用带来过大的额外负荷,便会改用插入排序。而如果递归层次过深,有出现最坏情况的倾向,还会改用堆排序。快速排序最关键的地方在于枢轴的选择,最坏的情况发生在分割时产生了一个空的区间,这样就完全没有达到分割的效果。STL采用的做法称为median-of-three,即取整个序列的首、尾、中央三个地方的元素,以其中值作为枢轴。 这个以后再详谈 STL中的sort实现)

说起快速排序:人家这个名字起的就很简单粗暴,因为一听到这个名字,我们就知道它存在的意义:快,而且效率高!快速排序是我们处理大量数据 最快的排序算法之一了。虽然 在最差情况下的时间复杂度也达到了 O(n²),但是在大多数情况下作为一个时间复杂度为O(n logn)的算法竟然都比很多平均时间复杂度为 O(n logn) 的排序算法表现要更好。原因就在于:快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

好吧 路子走偏了,咱赶紧回来。快速排序使用Divide and conquer的策略来把一个数列分为两个子数列,因此快速排序也是一种分而治之思想在排序算法上的典型应用。但从本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法,因此也可以称为是冒泡的一种优化算是。

具体做法就是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以通过递归进行,以此达到整个数据变成有序序列,当然在这里我也提供一下非递归的方式实现版本。

排序步骤就是:

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,把元素值比基准值小的放在基准前面,元素值比基准值大的放在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就数处于这个数列的中间位置。这个过程被称为分区(partition)操作,也有人把它称为:一次划分算法。
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。(当然这里也提供一下 非递归版本。)

一次划分的三种实现方式:

对于分区操作(一次划分)而言,通常有3种实现方式:挖坑填补法、左右交换法、双指针前移法。

分析具体实现:挖坑填补法

第一种 挖坑填补法:

基本思路就是:

  1. 设置两个变量l和h,分别接收传入的一次划分one_Partition的low和high。在最开始的时候 low=0 high=size-1。
  2. 然后我们这里把 第一个元素src【0】作为 基准值pivotkey的开始值。注:当以数组第一个数值为pivotkey时,搜索必须先从后向前进行,当以最后一个数组元素为基准数时,搜索必须先从前向后进行。
  3. 首先 h从后向前 扫描,一直到 遇见第一个小于pivotkey的值 src【h】,将此时的src【l】和src【h】的值进行交换。这个过程即:h - -
  4. 接着l从前向后 扫描,一直到 遇见第一个大于pivotkey的值 src【l】,将此时的src【l】和src【h】的值进行交换。这个过程即:l + +
  5. 继续循环上面两个步骤,一直到 l=h的时候,循环即可结束。这个时候(把pivotkey 填入src【l】或者src【h】当中)。
  6. 然后把此时的 l 和 h 返回回去,下面接着去递归这个下标左面的部分和右面的部分。

一次划分one_Partition的具体操作如下:

此时的 pivotkey 49的左边数据都严格比它小,右边的数据不小于它。于是就可以使用递归的方式,依次处理 左边的{27,38,13} 和 右边的{76,97,65,49};如下所示:

算法测试打印:挖坑填补法

这里使用递归实现挖坑填补法的快排:

#include <iostream>
#include <vector>
#include <iomanip>
#include <algorithm>
using namespace std;int one_Partition(vector<int>& src, int low, int high)
{static int n = 1;//记录次数的int l = low, h = high;int pivotkey = src[l];//把这段数据的第一个数据 作为基准值while (l < h){while (l < h  && src[h]>= pivotkey){h--;}//把pivotkey填到src【h】里面,把src【l】这个坑挖好了swap(src[l], src[h]);while (l < h && src[l] <= pivotkey){l++;}//把pivotkey填到src【l】里面,把src【h】这个坑挖好了swap(src[l], src[h]);}//src[l] = pivotkey;//这个是把pivotkey 放到最后一个位置
//但是我感觉上面的这句代码 也可以不用写。因为我做的是 交换数据,本来就在那里了cout << "第" << n++ << "次调整结束后,结果:";for (int val : src){cout << setw(2) << val << " ";}cout << endl;return l;//把这个下标 返回,作为下一次递归的base中间线
}void Quick_sort(vector<int>& src,int low,int high)
{int base = one_Partition(src, low, high);if (low + 1 < base)Quick_sort(src, low, base - 1);//递归左边if (base + 1 < high)Quick_sort(src, base + 1, high);//递归右边
}int main()
{int Array[] = { 1,50,38,5,47,15,36,26,27,2,46,4,19,44,48 };int Array2[] = { 49,38,65,97,76,13,27,49 };
#if 0vector<int>myvec(begin(Array), end(Array));Quick_sort(myvec, 0, myvec.size() - 1);cout << "排序Quick_sort结束后,最终结果:";for (int val : myvec){cout << setw(2) << val << " ";}cout << endl;cout << "**********************************" << endl;
#endifvector<int>myvec2(begin(Array2), end(Array2));Quick_sort(myvec2, 0, myvec2.size() - 1);cout << "排序Quick_sort结束后,最终结果:";for (int val : myvec2){cout << setw(2) << val << " ";}cout << endl;return 0;
}


以上 红色方框里面就是pivotkey值的调整。

对于分区操作(一次划分)而言,通常有3种实现方式:挖坑填补法、左右交换法、双指针前移法。

分析具体实现:左右交换法

第二种 左右交换法:

基本思路就是:

  1. 设置两个变量l和h,分别接收传入的一次划分one_Partition的low和high。在最开始的时候 low=0 high=size-1。
  2. 然后我们这里把 第一个元素src【0】作为 基准值pivotkey的开始值。(这个时候 也需要把这个基准值的下标保存下来val_key 值为 l )注:当以数组第一个数值为pivotkey时,搜索必须先从后向前进行,当以最后一个数组元素为基准数时,搜索必须先从前向后进行。
  3. 首先 h从后向前 扫描,一直到 遇见第一个小于pivotkey的值 src【h】,然后停止。这个过程即:h - -
  4. 接着l从前向后 扫描,一直到 遇见第一个大于pivotkey的值 src【l】,然后停止。这个过程即:l + +
  5. 接下来:把pivotkey值 两边的数据进行 swap。
  6. 继续循环上面三个步骤,一直到 l=h的时候,循环即可结束。这个时候(最后把pivotkey值 和中间集合下标 l或者h 数据 进行交换)。(这个地方就用到了上面刚刚保存的 基准值的下标val_key )
  7. 然后把此时的 l 和 h 返回回去,下面接着去递归这个下标左面的部分和右面的部分。
算法测试打印:左右交换法
#include <iostream>
#include <vector>
#include <iomanip>
#include <algorithm>
using namespace std;int one_Partition(vector<int>& src, int low, int high)
{int l = low, h = high;int pivotkey = src[l];//把这段数据的第一个数据 作为基准值int val_key = l;while (l < h){//这种情况下 必须先得从最右边开始while (l < h && src[h] >= pivotkey){h--;}//在这里 就找到了 自最右边开始的 第一个小于pivotkey值的 数据//然后接下来 就在给定数列 最左边开始找带一个大于 pivotkey值的 数据while (l < h && src[l] <= pivotkey){l++;}swap(src[l], src[h]);//把pivotkey值 两边的数据进行 swap}//最后把pivotkey值 和中间集合下标数据 进行交换swap(src[l], src[val_key]);return l;//把这个下标 返回,作为下一次递归的base中间线
}void Quick_sort(vector<int>& src, int low, int high)
{int base = one_Partition(src, low, high);//左右交换法 实现if (low + 1 < base)Quick_sort(src, low, base - 1);//递归左边if (base + 1 < high)Quick_sort(src, base + 1, high);//递归右边
}int main()
{int Array[] = { 1,50,38,5,47,15,36,26,27,2,46,4,19,44,48 };int Array2[] = { 49,38,65,97,76,13,27,49 };vector<int>myvec(begin(Array), end(Array));Quick_sort(myvec, 0, myvec.size() - 1);cout << "排序Quick_sort结束后,最终结果:";for (int val : myvec){cout << setw(2) << val << " ";}cout << endl;cout << "**********************************" << endl;vector<int>myvec2(begin(Array2), end(Array2));Quick_sort(myvec2, 0, myvec2.size() - 1);cout << "排序Quick_sort结束后,最终结果:";for (int val : myvec2){cout << setw(2) << val << " ";}cout << endl;return 0;
}

第三种 双指针前移法:

基本思路就是:

  1. 设置两个前后指针 slow和fast,分别在最开始的时候 fast=high - 1,而slow=fast - 1.slow被称为是 慢指针;fast被称为是快指针。整个过程由fast指针进行移动,搜寻。注:快指针去搜索比基准值pivotkey 小的元素,直到fast=high处 结束。当前外层循环 控制:while (fast < high)
  2. 然后我们这里把 最后一个元素src【high】作为 基准值pivotkey的开始值。
  3. 首先 fast指针从前向后 扫描,一直到 遇见第一个小于pivotkey的值 src【fast】,然后停止。这个过程即:while (src[fast] < pivotkey && (++slow) != fast) 找到第一个小于pivotkey的值的 src【fast】。
  4. 找到之后,若是slow的下一个位置 不是fast的情况下:swap(src[slow], src[fast]);
  5. 接下来:fast继续向后搜索。
  6. 继续循环上面三个步骤,一直到 fast=high的时候,循环即可结束。这个时候(此时的fast到达了high,这一轮的一次划分到此 循环结束)。接下来 把基准值和 slow位置的下一个元素值进行 交换。此时那个最初设置的基准值pivotkey就到达了其位置。(此时其左边数据 严格比它小,右边数据严格 大于等于它)。
  7. 然后此时,把这个slow的下一个位置的下标值 返回,作为下一次递归的base中间线,下面接着去递归这个下标(基准值pivotkey)左面的部分和右面的部分。
算法测试打印:双指针前移法
#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include <algorithm>
using namespace std;static int count_k = 1;//作为一次划分的次数
int one_Partition(vector<int>& src, int low, int high)// 前后指针 前移法
{int fast = low;int slow = fast - 1;int pivotkey = src[high];//这次把最右边的数据 作为基准数while (fast < high){while (src[fast] < pivotkey && (++slow) != fast){swap(src[slow], src[fast]);}++fast;}//此时的fast到达了high//接下来 把基准值和 slow位置的下一个值进行 交换swap(src[slow + 1], src[fast]);//打印一下每一次的  一次划分结束时的结果:cout << "第" << count_k++ << "次的一次划分 结束结果为:";for (int val : src)cout << val << " ";cout << endl;//把这个slow的下一个位置的下标 返回,作为下一次递归的base中间线return slow + 1;
}void Quick_sort(vector<int>& src, int low, int high)
{int base = one_Partition(src, low, high);//双指针前移法 实现if (low + 1 < base)Quick_sort(src, low, base - 1);//递归左边if (base + 1 < high)Quick_sort(src, base + 1, high);//递归右边
}int main()
{int Array[] = { 1,50,38,5,47,15,36,26,27,2,46,4,19,44,48 };int Array2[] = { 49,38,65,97,76,13,27,49 };
#if 0vector<int>myvec(begin(Array), end(Array));Quick_sort(myvec, 0, myvec.size() - 1);cout << "排序Quick_sort结束后,最终结果:";for (int val : myvec){cout << setw(2) << val << " ";}cout << endl;cout << "**********************************" << endl;
#endifvector<int>myvec2(begin(Array2), end(Array2));Quick_sort(myvec2, 0, myvec2.size() - 1);cout << "排序Quick_sort结束后,最终结果:";for (int val : myvec2){cout << setw(2) << val << " ";}cout << endl;  return 0;
}


到此时为止:一次划分的三种实现方式 都描述清楚了。主要的函数接口:void Quick_sort(vector& src, int low, int high)是一致的,仅仅是在底层 一次划分上进行了调整。而且主要的函数 使用递归的方式实现的快速排序算法。接下来 我们要讨论的就是 关于基准数pivotkey 的选取方式:

关于基准数pivotkey 的选取方式:

从网上找到的图片:

第一种 固定基准数的选取

如上的三种 一次划分算法实现上都是采取这种方式 选取基准数:
通常情况下的基本的快速排序选取第一个或最后一个元素作为基准。但是,这是一直很不好的处理方法。因为其基本思想就是:选取第一个或最后一个元素作为基准值,而这样做 不是比较好的处理。算法代码 上面就写过了,不再赘述了。

//这个是 上面挖坑填补法的实现 一个典型的固定位置 选取基准值的实现
int one_Partition(vector<int>& src, int low, int high)
{int l = low, h = high;int pivotkey = src[l];//把这段数据的第一个数据 作为基准值while (l < h){while (l < h  && src[h]>= pivotkey){h--;}//把pivotkey填到src【h】里面,把src【l】这个坑挖好了swap(src[l], src[h]);while (l < h && src[l] <= pivotkey){l++;}//把pivotkey填到src【l】里面,把src【h】这个坑挖好了swap(src[l], src[h]);}//src[l] = pivotkey;//这个是把pivotkey 放到最后一个位置
//但是我感觉上面的这句代码 也可以不用写。因为我做的是 交换数据,本来就在那里了return l;//把这个下标 返回,作为下一次递归的base中间线
}

第二种 随机基准数的选取

其基本思想:选取待排序列【low,high】中共high-low+1 个数据中的任意一个数作为基准值,选出的任意值与low下标的元素交换,可继续使用上面已经实现过的 一次划分算法代码。这样 反其道而行之,同样到达了pivotkey值的随机性。

//这个是 上面挖坑填补法的实现  随机选取基准值的实现
int one_Partition(vector<int>& src, int low, int high)
{swap(src[low],src[low + rand() % (high-low+1)]);//做到基准值随机的效果int l = low, h = high;int pivotkey = src[l];//把这段数据的第一个数据 作为基准值while (l < h){while (l < h  && src[h]>= pivotkey){h--;}//把pivotkey填到src【h】里面,把src【l】这个坑挖好了swap(src[l], src[h]);while (l < h && src[l] <= pivotkey){l++;}//把pivotkey填到src【l】里面,把src【h】这个坑挖好了swap(src[l], src[h]);}//src[l] = pivotkey;//这个是把pivotkey 放到最后一个位置
//但是我感觉上面的这句代码 也可以不用写。因为我做的是 交换数据,本来就在那里了return l;//把这个下标 返回,作为下一次递归的base中间线
}

第三种 三数取中 基准数选取

因为虽然快速排序整体的效率可观,但是当最坏情况发生时它的效率就会降低,为了降低最坏情况发生的概率,我们要尽量在最坏情况下的选取合适的基准数。当我们每次划分的时候选择的基准数接近于整组数据的最大值或者最小值时,快速排序就会发生最坏的情况,但是每次选择的基准数都接近于最大数或者最小数的概率随着排序元素的增多就会越来越小,我们完全可以忽略这种情况。但是在数组有序的情况下,它也会发生最坏的情况,为了避免这种情况,我们在选择基准数的时候可以采用三数取中法来选择基准数。

  1. 首先:看一下 固定基准值的情况下的 交换情况:
static int k = 1;int one_Partition(vector<int>& src, int low, int high)// 左右交换法
{int l = low, h = high;int pivotkey = src[low];//这次把最左边的数据 作为基准数int val_key = l;int count_k = 0;while (l < h){while (l<h && src[h] >= pivotkey){h--;}//在这里 就找到了 自最右边开始的 第一个小于pivotkey值的 数据//然后接下来 就在给定数列 最左边开始找带一个大于 pivotkey值的 数据while (l < h && src[l] <= pivotkey){l++;}count_k++;swap(src[l], src[h]);}//最后把pivotkey值 和中间集合下标数据 进行交换swap(src[l], src[val_key]);count_k++;//打印一下每一次的  一次划分结束时的结果:cout << "第" << k++ << "次的一次划分,经过了" << count_k << "次的交换。 结束结果为:";for (int val : src)cout << val << " ";cout << endl;return l;//把这个下标 返回,作为下一次递归的base中间线
}


2. 接下来 看一下随机基准值的交换情况:

static int k = 1;int one_Partition(vector<int>& src, int low, int high)// 左右交换法
{srand(unsigned (time(nullptr)));swap(src[low], src[low + rand() % (high - low + 1)]);//做到基准值随机的效果int l = low, h = high;int pivotkey = src[low];//这次把最左边的数据 作为基准数int val_key = l;int count_k = 0;while (l < h){while (l<h && src[h] >= pivotkey){h--;}//在这里 就找到了 自最右边开始的 第一个小于pivotkey值的 数据//然后接下来 就在给定数列 最左边开始找带一个大于 pivotkey值的 数据while (l < h && src[l] <= pivotkey){l++;}count_k++;swap(src[l], src[h]);}//最后把pivotkey值 和中间集合下标数据 进行交换swap(src[l], src[val_key]);count_k++;//打印一下每一次的  一次划分结束时的结果:cout << "第" << k++ << "次的一次划分,经过了" << count_k << "次的交换。 结束结果为:";for (int val : src)cout << setw(2) << val << " ";cout << endl;return l;//把这个下标 返回,作为下一次递归的base中间线
}


所谓的三数取中法: 其基本思想就是选择这组数据的第一个元素、中间的元素、最后一个元素,这三个元素里面值大小 居中的那个元素作为基准数。即:调整成为src[mid] <= src[low] <= src[high],使得arr[low]处于三数中的中间值,这样的话,我们就可以去继续使用上面的一次划分函数。

static int k = 1;
// src[mid] <= src[low] <= src[high]
void resetlow_mid_high(vector<int>& src, int low, int high)//重置 三数取中
{int mid = (low + high) >> 1;if (src[mid] > src[low]){swap(src[mid], src[low]);}if (src[low] > src[high]){swap(src[low], src[high]);}if (src[mid] > src[high]){swap(src[mid], src[high]);}
}int one_Partition(vector<int>& src, int low, int high)// 左右交换法
{resetlow_mid_high(src, low, high);//三数取中int l = low, h = high;int pivotkey = src[low];//这次把最左边的数据 作为基准数int val_key = l;int count_k = 0;while (l < h){while (l<h && src[h] >= pivotkey){h--;}//在这里 就找到了 自最右边开始的 第一个小于pivotkey值的 数据//然后接下来 就在给定数列 最左边开始找带一个大于 pivotkey值的 数据while (l < h && src[l] <= pivotkey){l++;}count_k++;swap(src[l], src[h]);}//最后把pivotkey值 和中间集合下标数据 进行交换swap(src[l], src[val_key]);count_k++;//打印一下每一次的  一次划分结束时的结果:cout << "第" << k++ << "次的一次划分,经过了" << count_k << "次的交换。 结束结果为:";for (int val : src)cout << setw(2) << val << " ";cout << endl;return l;//把这个下标 返回,作为下一次递归的base中间线
}

快排算法的优化

第一种 当待子序列的长度 被划分到较小数值时,采用插入排序

对于待排序的序列长度很小或是基本趋于有序时,快排的效率还是没有插入排序的好。也即:当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插入排序。所有我们在这里自定义这个 子序列长度:序列长度N=5。当待排序的序列长度被分割到5时,采用插入排序 而非继续分割快速排序。

void Straight_Insertion_Sort2(vector<int>& src)//使用的是两两交换前进
{int size = src.size();for (int i = 0; i < size - 1; ++i){int j = i + 1;//取出无序序列的第一个元素int k = i;while (k >= 0 && src[j] < src[k])//这个k的比较 必须放前面{swap(src[k], src[j]);k--;j--;}}cout << "第" << k++ << "次的, 结束结果为:";for (int val : src)cout << setw(2) << val << " ";cout << endl;
}int one_Partition(vector<int>& src, int low, int high)// 左右交换法
{int l = low, h = high;int pivotkey = src[low];//这次把最左边的数据 作为基准数int val_key = l;int count_k = 0;while (l < h){while (l<h && src[h] >= pivotkey){h--;}//在这里 就找到了 自最右边开始的 第一个小于pivotkey值的 数据//然后接下来 就在给定数列 最左边开始找带一个大于 pivotkey值的 数据while (l < h && src[l] <= pivotkey){l++;}count_k++;swap(src[l], src[h]);}//最后把pivotkey值 和中间集合下标数据 进行交换swap(src[l], src[val_key]);count_k++;//打印一下每一次的  一次划分结束时的结果:cout << "第" << k++ << "次的一次划分,经过了" << count_k << "次的交换。 结束结果为:";for (int val : src)cout << setw(2) << val << " ";cout << endl;return l;//把这个下标 返回,作为下一次递归的base中间线
}void Quick_sort(vector<int>& src, int low, int high)
{int base = one_Partition(src, low, high);//双指针前移法 实现if (high - low + 1 < 5)  // 当整个序列的大小high-low+1<5时,采用直接插入排序{Straight_Insertion_Sort2(src);return;}else{if (low + 1 < base)Quick_sort(src, low, base - 1);//递归左边if (base + 1 < high)Quick_sort(src, base + 1, high);//递归右边}
}

第二种 聚集相同基准法

对于数列中 存在着大量的重复的数据时,可以考虑使用: 在一次划分后,把与基准值pivotkey相等的元素gather在一起,可以减少大量的迭代次数,从而大大地提升排序效率。
排序步骤就是:

  1. 在一次划分的过程中,把与基准值pivotkey相等元素放入数列的两端
  2. 划分结束后,把与基准值pivotkey相等的元素移到枢轴周围。
  3. 对重复数列的左右两部分 分别进行排序。

例如:待排序序列 1 4 6 7 6 6 7 6 8 6(这里选取基准值的方式:三数取中法)

  1. 三数取中选取枢轴:下标为4的数6
  2. 三数调整后,待分割序列:6 4 6 7 1 6 7 6 8 6。其基准值pivotkey:6
  3. 在一次划分的过程中,把与pivotkey相等元素放入数列的两端
    如下为:6 4 1 6(枢轴) 7 8 7 6 6 6。此时,与6相等的元素全放入在两端了
  4. 本次的一次划分结束后,把与基准值pivotkey相等的元素移到枢轴的周围。结果如下为:1 4 6 6(枢轴) 6 6 6 7 8 7。此时,与6相等的元素全移到枢轴周围了。
  5. 接下来:在1 4 和 7 8 7两个子序列继续进行快速排序(即 重复上面四个步骤)。
#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include <algorithm>
#include <time.h>
using namespace std;static int k = 1;
// src[mid] <= src[low] <= src[high]
void resetlow_mid_high(vector<int>& src, int low, int high)//重置 三数取中
{int mid = (low + high) >> 1;if (src[mid] > src[low]){swap(src[mid], src[low]);}if (src[low] > src[high]){swap(src[low], src[high]);}if (src[mid] > src[high]){swap(src[mid], src[high]);}
}
void Straight_Insertion_Sort2(vector<int>& src)//使用的是两两交换前进
{int size = src.size();for (int i = 0; i < size - 1; ++i){int j = i + 1;//取出无序序列的第一个元素int k = i;while (k >= 0 && src[j] < src[k])//这个k的比较 必须放前面{swap(src[k], src[j]);k--;j--;}}cout << "第" << k++ << "次的, 结束结果为(直接插入排序):         ";for (int val : src)cout << setw(2) << val << " ";cout << endl;
}//下面是 一次划分 处理和基准值pivotkey重复数据的
int one_Partition(vector<int>& src, int low, int high)// 左右交换法
{int l = low, h = high;resetlow_mid_high(src, low, high);//三数取中 得到基准数int pivotkey = src[low];int val_key = l;int count_k = 0;while (l < h){while (l < h && src[h] >= pivotkey){h--;}//在这里 就找到了 自最右边开始的 第一个小于pivotkey值的 数据//然后接下来 就在给定数列 最左边开始找带一个大于 pivotkey值的 数据while (l < h && src[l] <= pivotkey){l++;}count_k++;swap(src[l], src[h]);}//最后把pivotkey值 和中间集合下标数据 进行交换swap(src[l], src[val_key]);count_k++;//打印一下每一次的  一次划分结束时的结果:cout << "第" << k++ << "次的一次划分,经过了" << count_k << "次的交换。 结束结果为:";for (int val : src)cout << setw(2) << val << " ";cout << endl;return l;//把这个下标 返回,作为下一次递归的base中间线
}void gather_same_pivotkey(vector<int>& src, int low, int base, int high,int* same_pivotkey_left, int* same_pivotkey_right)
{int this_left = base - 1;//base 是上一次 一次划分返还的中间线位置下标if (low < high){for (int i = base - 1; i >= low; --i){if (src[i] == src[base] && i != this_left){swap(src[i], src[this_left]);this_left--;}}if (this_left >= low && src[this_left] == src[base])* same_pivotkey_left = this_left;else *same_pivotkey_left = this_left + 1;////接下来 处理base的 右边数据int this_right = base + 1;for (int i = base + 1; i <= high; ++i){if (src[i] == src[base] && i != this_right){swap(src[i], src[this_right]);this_right++;}}if (this_right <= high && src[this_right] == src[base])* same_pivotkey_right = this_right;else *same_pivotkey_right = this_right - 1;}
}
void Quick_sort(vector<int>& src, int low, int high)
{#if 0if (high - low + 1 < 10)  // 当整个序列的大小high-low+1<10时,采用直接插入排序{Straight_Insertion_Sort2(src);return;}
#endifint base = one_Partition(src, low, high);//和基准数相同的 数值区间的左右下标int same_pivotkey_left = 0, same_pivotkey_right = 0;//将一次划分之后的  和pivotkey相同的数据 都集中到base的附近//即:【same_pivotkey_left,same_pivotkey_right】gather_same_pivotkey(src, low, base, high, &same_pivotkey_left, &same_pivotkey_right);//接下来 处理的时候:只需要从same_pivotkey_left的左边开始if (low + 1 < same_pivotkey_left)Quick_sort(src, low, same_pivotkey_left - 1);//递归左边//和只需要从same_pivotkey_right的右边开始if (same_pivotkey_right + 1 < high)Quick_sort(src, same_pivotkey_right + 1, high);//递归右边
}int main()
{int Array[] = { 1, 4, 6 ,7 ,6, 6, 7, 6, 8, 6 };vector<int>myvec(begin(Array), end(Array));cout << "排序Quick_sort结束前,初始状态:              ";for (int val : myvec){cout << setw(2) << val << " ";}cout << endl;Quick_sort(myvec, 0, myvec.size() - 1);cout << "排序Quick_sort结束后,最终结果:              ";for (int val : myvec){cout << setw(2) << val << " ";}cout << endl;return 0;
}

注 :我这里是闭区间 【same_pivotkey_left,same_pivotkey_right】

注:2019年8月30日20:28:18 此次快速排序算法的研究到此为止。总共花了 大概3天时间,看了很多比较厉害的博客。这几天的成长 比在图论课堂上学到的东西 要多得多。是事必躬亲,才能有所成长。

DSA之十大排序算法第六种:Quick Sort相关推荐

  1. DSA之十大排序算法第二种:Straight Selection Sort

    直接选择排序 直接选择排序在我看来,是一种非常简单粗暴的方法.注:我们这里实现的所有排序算法,统一由小到大 C++实现. 排序步骤就是: 在这个无序的数列里面,每次都从中选择 或者 挑选最小的那个元素 ...

  2. python实现排序算法_python实现·十大排序算法之插入排序(Insertion Sort)

    简介 插入排序(Insertion Sort)是一种简单直观的排序算法.它的工作原理是:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入. 算法实现步骤 从第一个元素开 ...

  3. python sort 逆序_python实现·十大排序算法之插入排序(Insertion Sort)

    简介 插入排序(Insertion Sort)是一种简单直观的排序算法.它的工作原理是:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入. 算法实现步骤 从第一个元素开 ...

  4. 这或许是东半球分析十大排序算法最好的一篇文章

    作者 | 不该相遇在秋天 转载自五分钟学算法(ID:CXYxiaowu) 前言 本文全长 14237 字,配有 70 张图片和动画,和你一起一步步看懂排序算法的运行过程. 预计阅读时间 47 分钟,强 ...

  5. 「干货总结」程序员必知必会的十大排序算法

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招! 个人原创100W+访问量博客:点击前往,查看更多 绪论 身 ...

  6. 「归纳|总结」程序员必知必会的十大排序算法

    微信搜一搜「bigsai」关注这个有趣的程序员 新人原创公众号,求支持一下!你的点赞三连肯定对我至关重要! 文章已收录在 我的Github bigsai-algorithm 欢迎star 本文目录 绪 ...

  7. 归并排序执行次数_十大排序算法,看这篇就够了

    排序算法分类[1][2] 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序. 非比较类排序:不通过比较来决定元素间的相对次序,它可以 ...

  8. 八十八、Python | 十大排序算法系列(下篇)

    @Author:Runsen @Date:2020/7/10 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏 ...

  9. 八十七、Python | 十大排序算法系列(上篇)

    @Author:Runsen @Date:2020/7/10 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏 ...

最新文章

  1. Flutter环境搭建(Windows)
  2. tenjin - 号称全球最快的模板引擎
  3. delphi 中listview的右键菜单处理
  4. 机器人学习--室内定位方法综述
  5. Kafka基本知识整理
  6. 合并 多个dataframe_什么是Pandas的DataFrame?
  7. 错误记录,找不到sqlite dll
  8. 红橙Darren视频笔记 Activity启动流程(API28)
  9. Linux上FTP服务的相关配置2:搭建FTPs及虚拟账号
  10. “互联网+”从业务本质重构业务形态
  11. dispatcherServlet源码分析之doDispatch
  12. redhat7 配置xmanager登陆
  13. 用户使用计算机首要考虑因素,工业设计心理学试题(新整理有答案参考)
  14. Alexander Tropsha:AI从零自学设计新型药物分子,研究登Science子刊|42问AI与机器人未来...
  15. python高德地图api调用实例_Python玩转高德地图API(二)
  16. 阿里云OSS远程连接
  17. CAS详解,如何理解CAS!
  18. Aspose.Words模板创建Word【一】
  19. 排序算法-6-归并排序
  20. Jest 常用匹配器

热门文章

  1. 手把手教你实现window图片爬虫(一)
  2. 5g网速用什么软件测试手机,5G时代到底有多快?测速软件谁最靠谱?
  3. 数据是啥?数据都去哪儿了?
  4. rk3399 rkmpp 在ffmpeg上实现硬编解码
  5. 台式计算机噪声,台式电脑有噪音怎么办
  6. 【论文精读】Single-Perspective Warps in Natural Image Stitching
  7. 云服务器配置jupyter
  8. matlab dwtest,MATLAB机考样题(带答案)
  9. CoOS使用教程——中断、裁剪、移植
  10. U盘非物理损坏如何修复