一、算法出现的背景算法
以前讲的,当咱们排序的是一个近乎有序的序列时,快速排序会退化到一个O(n^2)级别的排序算法,数组
而对此的改进就是引入了随机化快速排序算法;可是当咱们排序的是一个数值重复率很是高的序列时,函数
此时随机化快速排序算法就再也不起做用了,而将会再次退化为一个O(n^2)级别的排序算法,那为何性能
会出现这种状况呢?且听下面的分析:测试
如上图所示就是以前分析的快速排序算法的partition的操做原理,咱们经过判断此时i索引指向的数组ui
元素e>v仍是<v,将他放在橙色或者是紫色两个不一样的位置,而后将整个数组分红两个部分递归下去;spa
可是这里其实咱们是没有考虑=v的状况,其实隐含的意思就是下面的两种状况:3d
其实从这里就能够看出来了,无论是>=v仍是<=v,当咱们的序列中存在大量重复的元素时,code
排序完成以后就会将整个数组序列分红两个极度不平衡的部分,因此又退化到了O(n^2)级别blog
的时间复杂度,这是由于对于每个"基准"元素来讲,重复的元素太多了,若是咱们选的"基准"
元素稍微有一点的不平衡,那么就会致使两部分的差距很是大;即时咱们的"基准"元素选在了
一个平衡的位置,可是因为等于"基准"元素的元素也很是多,也会使得序列被分红两个及其不平
衡的部分,那么在这种状况下快速排序就又会退化成O(n^2)级别的排序算法。如何解决呢?
这就要用到今天讲的双路快速排序算法的原理了。
二、双路快速排序算法的原理
以前说的快速排序算法是将>v和<v两个部分元素都放在索引值i所指向的位置的左边部分,而咱们
的双路快速排序算法则不一样,他使用两个索引值(i、j)用来遍历咱们的序列,将<v的元素放在索
引i所指向位置的左边,而将>v的元素放在索引j所指向位置的右边,这也正是双路排序算法的
partition原理:
基本思想:
首先从左边的i索引往右边遍历,若是i指向的元素<v,
那直接将i++移动到下一个位置,直道i指向的元素>=v则中止
而后使用j索引从右边开始往左边遍历,若是j指向的元素>v,
那直接将j--移动到下一个位置,直道j指向的元素<=v则中止
此时i以前的元素都已经归并为<v的部分了,而j以后的元素也
都已经归并为>v的部分了,此时只须要将arr[i]和arr[j]交换位置便可
这样就能够避免出现=v的元素所有集中在某一个部分,这正
是双路排序算法的一个核心
将i++,j--开始遍历后后面的元素
三、双路快速排序算法的实现(基于C++)
1 /******************************** 双路快速排序算法实现 ***************************************/ 2 template<typename T> 3 T __partition2 (T arr[], int left, int right) 4 { 5 std::swap(arr[left], arr[std::rand() % (right - left + 1) + left]); // 随机产生"基准"元素所在位置,并与第一个元素交换位置 6 T v = arr[left]; // 将第一个元素做为"基准"元素 7 8 // 使用i索引从左到右遍历,使用j索引从右到左遍历 9 int i = left + 1; // 索引值i初始化为第二个元素位置 10 int j = right; // 索引值j初始化为最后一个元素位置 11 while (true) { 12 while ((i < right) && (arr[i] < v)) i++; // 使用索引i从左往右遍历直到 arr[i] < v 13 while ((j > left + 1) && (arr[j] > v)) j--; // 使用索引j从右往左遍历直到 arr[j] > v 14 if (i >= j) break; // 退出循环的条件 15 std::swap(arr[i], arr[j]); // 将 arr[i] 与 arr[j] 交换位置 16 i++; // i++ j-- 17 j--; 18 } 19 20 std::swap(arr[left], arr[j]); // 最后将"基准"元素v放置到合适的位置 21 22 return j; 23 } 24 25 template<typename T> 26 void __quickSort2 (T arr[], int left, int right) 27 { 28 if (right - left <= 40) { // 当递归到序列数据量较小时使用插入排序算法 29 __insertSortMG<T>(arr, left, right); 30 return; 31 } 32 33 int p = __partition2<T>(arr, left, right); // 对arr[left...right]区间元素进行partition操做,找到"基准"元素 34 __quickSort2<T>(arr, left, p - 1); // 对基准元素以前的序列递归调用__quickSort函数 35 __quickSort2<T>(arr, p + 1, right); // 对基准元素以后的序列递归调用__quickSort函数 36 } 37 38 template<typename T> 39 void quickSort2 (T arr[], int count) 40 { 41 std::srand(std::time(NULL)); // 种下随机种子 42 __quickSort2<T>(arr, 0, count - 1); // 调用__quickSort函数进行快速排序 43 } 44 /*********************************************************************************************/
四、性能测试
通常序列:
近乎有序的序列:
重复率很是高的序列: