一.概述

  • 快速排序是冒泡排序的改进算法。它也是通过不断比较和移动交换来实现排序的,只不过它的实现增大了记录的比较和移动的距离,将关键字较大的元素从前面直接放到后面,关键字较小的元素直接从后面放到前面,从而减小了比较次数和交换次数。

二.原理

  • 通过一趟排序将待排序数据分割成独立的两个子序列,其中左边的子序列均比右边的子序列中的元素小,然后分别对左右两个子序列继续进行排序,达到整个序列有序的目的。下面主要做两件事情,第一:从什么位置划分原始序列,将其变成左右两个子序列,即确定主元pivotkey的位置pivot。第二:分别对左右两个子序列进行递归操作,继续分割使其变得有序**(即大于主元的元素都放在主元的右边,小于主元的元素都放在主元的左边),整个过程如下图所示,其中主元的位置选的是待排序数组中的第一个元素50**。

三.快速排序的优化

  • 3.1优化主元的选择

    • 快速排序算法的快慢取决于主元元素的选择。上面的例子中,选择的是待排序数组中的第一个元素。事实上,固定选择第一个元素作为主元是不合理的,如下图中的例子。选择9为主元后,进行一次交换操作后,整个序列没有发生实质性的操作。最坏情况下,待排序的数组是正序或逆序时,每次划分只得到一个比上次少一个元素的子序列,另一个子序列为空。此时快速排序的时间复杂度就变成了0(n**2)。一般选择主元的方法是:三数取中法。即取三个元素(三个元素分别是待排数组的左端点、中间端点、右端点)先进行从小到大的排序,然后选择中间端点的元素作为主元,如下图中的例子。当待排序数组是非常大时,采用九数取中法,即从数组中分三次采样,每次取三个数,三个数各取出中间的元素,总共得到三个中间元素。接着,从这三个中间元素中再取一个中间数作为主元。
  • 3.2优化不必要的交换
    • 增加一个哨兵位置,暂存主元的值。然后每次当小于主元的元素出现在右边或大于主元的元素出现在左边时,进行直接替换操作,不进行交换,如下图所示。

四.对小数组的优化

  • 当待排序的数组元素个数很少时,采用快速排序有点大材小用。所以,设置一个阈值,用来判断是否采用快速排序。当待排序的数组中元素非常少时,使用直接插入排序算法;超过设定的阈值后,采用快速排序算法,如下面的代码所示:

        // 因为快速排序适合由于待排序的序列很大的情况,所以设置一个阈值void Qsort1(Sqlist *L, int low, int high){int pivot;if ((high - low) > MAX_LENGTH_INSERT_SORT){pivot = Partition1(L, low, high);Qsort1(L, low, pivot - 1);Qsort1(L, pivot + 1, high);}else{InsertSort(L);}}
    
  • 上述Qsort1()函数进行快速排序时,在函数尾部有两次递归操作,这两次递归操作对性能有一定的影响,使用尾递归可以减少递归次数,提高性能。因为第二次执行递归操作针对的是右边的子序列,所以Qsort1(L, pivot + 1, high);可以替换为low = pivot+1;同时,增加一个while(low < high)的循环,采用迭代的方式不是递归的方法,缩减了堆栈的深度,从而提高了整体性能,具体见下面的代码:
        // 由于Qsort1()函数在其尾部有两次的递归操作,如果待排序的序列划分极不平衡时,递归深度将趋于n,浪费栈空间// 使用尾递归void Qsort2(Sqlist *L, int low, int high){int pivot;if ((high - low) > MAX_LENGTH_INSERT_SORT){while (low < high){pivot = Partition1(L, low, high);Qsort2(L, low, pivot - 1);low = pivot + 1;  // 尾递归}}else{InsertSort(L);}}
    

五.快速排序完整代码

    #include <iostream>#include <malloc.h>using namespace std;#define MAXSIZE 10#define N 9#define MAX_LENGTH_INSERT_SORT 7 // 快速排序设定的阈值typedef struct{int r[MAXSIZE + 1];int len;} Sqlist;void show(Sqlist L){for (int i = 1; i <= L.len; i++)cout << L.r[i] << " ";cout << endl;}void Swap(Sqlist *L, int i, int j){int temp = L->r[i];L->r[i] = L->r[j];L->r[j] = temp;}// 确定主元int Partition(Sqlist *L, int low, int high){int pivotkey; // 主元pivotkey = L->r[low];while (low < high){while (low < high && L->r[high] >= pivotkey) // 比主元大的放在主元的右边,不交换high--;Swap(L, low, high);                         // 比主元小的放在主元的右边,进行交换while (low < high && L->r[low] <= pivotkey) // 比主元小的放在主元的左边,不交换low++;Swap(L, low, high); // 比主元大的放在主元的左边,进行交换}return low;}// 快速排序void Qsort(Sqlist *L, int low, int high){int pivot; // 主元位置if (low < high){pivot = Partition(L, low, high);Qsort(L, low, pivot - 1);Qsort(L, pivot + 1, high);}}// 统一函数接口void QuickSort(Sqlist *L){Qsort(L, 1, L->len);}// 快速排序的优化int Partition1(Sqlist *L, int low, int high){// 确定主元位置---三数取中法int pivotkey;int center = low + (high - low) / 2; // 计算中间元素的下标// 对左 中 右三个数进行排序if (L->r[low] > L->r[high])    // 如果左边的数大于右边的数,进行交换操作Swap(L, low, high);        // 保证左边的数较小if (L->r[center] > L->r[high]) // 如果中间的数大于右边的数,进行交换操作Swap(L, center, high);     // 保证中间的值较小if (L->r[center] > L->r[low])  // 如果中间的数大于左边的数,进行交换操作Swap(L, center, low);      // 保证左边的值较小pivotkey = L->r[low];          // L->r[low]已经成为整个序列左中右三个关键字的中间值L->r[0] = pivotkey;while (low < high){while (low < high && L->r[high] >= pivotkey)high--;L->r[low] = L->r[high]; // 使用的是替换不是交换while (low < high && L->r[low] <= pivotkey)low++;L->r[high] = L->r[low]; // 使用的是替换不是交换}L->r[low] = L->r[0]; // 将暂存的主元替换回L->r[row]return low;          // 返回主元的位置}// 直接插入排序void InsertSort(Sqlist *L){int i, j;for (i = 2; i <= L->len; i++){ // 插入的元素从下标为2开始if (L->r[i] < L->r[i - 1]){                      // 插入的元素比之前的元素值小,就进行交换操作L->r[0] = L->r[i]; // 下标为0的位置存放的是哨兵for (j = i - 1; L->r[j] > L->r[0]; j--)L->r[j + 1] = L->r[j]; // 进行移动操作L->r[j + 1] = L->r[0];     // 插入新的元素到正确的位置}}}// 因为快速排序适合由于待排序的序列很大的情况,所以设置一个阈值void Qsort1(Sqlist *L, int low, int high){int pivot;if ((high - low) > MAX_LENGTH_INSERT_SORT){pivot = Partition1(L, low, high);Qsort1(L, low, pivot - 1);Qsort1(L, pivot + 1, high);}else{InsertSort(L);}}// 由于Qsort1()函数在其尾部有两次的递归操作,如果待排序的序列划分极不平衡时,递归深度将趋于n,浪费栈空间// 使用尾递归void Qsort2(Sqlist *L, int low, int high){int pivot;if ((high - low) > MAX_LENGTH_INSERT_SORT){while (low < high){pivot = Partition1(L, low, high);Qsort2(L, low, pivot - 1);low = pivot + 1;  // 尾递归}}else{InsertSort(L);}}// 统一函数接口:改进后的快速排序void QuickSort1(Sqlist *L){Qsort1(L, 1, L->len);}// 统一函数接口:改进后的快速排序使用尾递归void QuickSort2(Sqlist *L){Qsort2(L, 1, L->len);}int main(){Sqlist L;int d[N] = {50, 10, 90, 30, 70, 40, 80, 60, 20};for (int i = 0; i < N; i++)L.r[i + 1] = d[i];L.len = N;cout << "快速排序前: ";show(L);cout << "快速排序后: ";QuickSort(&L);show(L);cout << "改进的快速排序后: ";QuickSort1(&L);show(L);cout << "改进的快速排序(尾递归)后: ";QuickSort2(&L);show(L);return 0;}
  • STL实现
    #include <iostream>#include <algorithm>#include <vector>using namespace std;// (小数,基准元素,大数)。在区间中随机挑选一个元素作基准,将小于基准的元素放在基准之前,大于基准的元素放在基准之后,再分别对小数区与大数区进行排序。// 快速排序思路:// 1. 选取第一个数为基准// 2. 将比基准小的数交换到前面,比基准大的数交换到后面// 3. 对左右区间重复第二步,直到各区间只有一个数// 递归方法实现void QuickSort(vector<int>& v, int low, int high){if(low >= high)return;int first = low;int last = high;int key = v[first];   // 第一个元素作为主元while(first < last){while(first < last && v[last] >= key){last--;}if(first < last){  // 不符合条件时,即后面元素比前面的元素小时,进行交换操作!v[first++] = v[last];}while(first < last && v[first] <= key){first++;}if(first < last){v[last--] = v[first];}}// 基准位置v[first] = key;QuickSort(v, low, first-1);  // 前半部分递归QuickSort(v, first+1, high);  // 后半部分递归}// 最后一个元素作为主元template<typename T>void QuickSortRecursiveCore(T arr[], int start, int end){if (start >= end)return;T mid = arr[end];int left = start, right = end-1;while(left < right){while(arr[left] < mid && left < right){left++;}while(arr[right] >= mid && left < right){right--;}swap(arr[left], arr[right]);}if(arr[left] >= arr[end]){swap(arr[left], arr[end]);}else{left++;}QuickSortRecursiveCore(arr, start, left-1);QuickSortRecursiveCore(arr, left+1, end);}template<typename T>void QuickSortRecursive(T arr[], int len){QuickSortRecursiveCore(arr, 0, len-1);}

六、快速排序时间复杂度总结:

  • 平均时间复杂度:O(nlogn)
  • 最好情况:O(nlogn)
  • 最坏情况:O(n**2)
  • 空间复杂度:O(nlogn)——O(n)
  • 稳定性:不稳定

快速排序算法实现思想个人理解相关推荐

  1. 选择排序算法实现思想个人理解

    一.选择排序算法个人理解 如果有N个元素需要排序,首先从N个元素中找到最小的那个元素,然后与索引ix=0位置上的元素进行交换(如果没有比原来索引ix=0位置上的元素小就不用交换),接着再从剩下的N-1 ...

  2. 冒泡排序算法实现思想个人理解

    一.冒泡排序算法个人理解 主要是以两个形成嵌套的for循环来完成的.外层的for循环以索引ix的值来逐个访问序列中的每个元素,ix的值由0开始增加到size(sequence) - 1,当外部的for ...

  3. 堆排序算法实现思想个人理解

    一.概述 堆排序是简单选择排序的改进算法,简单选择排序在待排序的个数据中选择一个最小的元素需要进行n-1次的比较,但是并没有将每一次循环的结果保存下来,在下一次循环中,有很多比较已经在上一次的循环中做 ...

  4. 希尔排序算法实现思想个人理解

    一.原理 希尔排序是对直接插入排序的改进,建立在直接排序的基础上实现的.因为直接插入排序适合那些数据本身就是基本有序的或者数据量比较小的情况.但是,实际中数据量小或数据基本有序属于特殊情况,这就是直接 ...

  5. 直接插入排序算法实现思想个人理解

    一.原理 由于是直接插入排序,下面假设原始数组中已经有一个元素是5,待插入的元素是3,所以下面的程序中待插入元素的下标i从2开始.数组下标0的位置作为哨兵,暂存待插入的元素.直接插入算法的主要步骤分3 ...

  6. 快速排序算法_Python实现快速排序算法

    排序是算法的入门知识,应用广泛,且在程序员面试中,经常被提及,其中最常考的两大排序算法为快速排序与归并排序,本篇将使用Python语言来分析了解快速排序算法. 思想 快速排序是一种非常高效的排序算法, ...

  7. c语言数字排列和算法思路,冒泡排序、快速排序算法理解及C程序实现

    前言:关于 快速排序算法的相关理解,本文借鉴了 啊哈磊 老师的<常用排序--快速排序> ,在此向作者 致敬,写的挺好. 目录 一.冒泡排序 二.快速排序 三.小结 一.冒泡排序 冒泡排序是 ...

  8. 卡尔曼算法笔记---思想和实际应用物理含义的理解

    此片blog的目的是理解卡尔曼算法的思想和实际应用的物理含义,想法很好,却只能理解冰山一角,先记下这一角 另本blog参考卡尔曼滤波 -- 从推导到应用和徐亦达卡尔曼推导视频 首先认识卡尔曼算法在数学 ...

  9. 视频教程-快速排序算法-算法思想-Java

    快速排序算法-算法思想 十三年软件互联网从业经验,使用JDK从1.4到1.8,从传统系统开发到互联网架构,从Struts使用到spring Cloud,拿过2次极客软件创意比赛大奖.曾经摆过地摊,卖过 ...

最新文章

  1. iOS12 UITabbar Item 向上漂移错位的bug
  2. 我发现一条惊人规律,年后跳槽BATJ,都是这种人....
  3. php找不到指定的模块,php中的dll“无法找到指定的模块”
  4. BeetleX之简单HTTP/HTTPS实现
  5. ExtJS和AngularJS比较
  6. 冇内容管理系统JS分析资料 一. attachEvent 的用法
  7. 混淆矩阵评价指标_机器学习:模型训练和评估——分类效果的评价
  8. 2评分标准多少分_突发!财政部刚刚通知!2020年中级考试题目分值及评分标准大变!...
  9. Android代码优化,主界面卡住
  10. 项目规划管理 - 5
  11. 帆软帮助文档_帆软:像阿甘一样,奔跑在商业智能的赛道上
  12. python使用selenium调用edge浏览器webdriver
  13. php 制作网站地图,网站地图怎么做,制作网站地图的三种实用方法
  14. html caption 靠左,HTML caption align 属性 | Paoo教程
  15. 谷歌身份验证器(Google Authenticator)的使用详情
  16. 小米手机linux驱动下载,小米手机驱动下载
  17. 四川托普计算机学校官网,四川中等职业技术学院
  18. Android系统 小米/三星/索尼 应用启动图标未读消息数(BadgeNumber)动态提醒
  19. GNSS中DCB的使用
  20. 《Java核心技术面试精讲--杨晓峰》学习笔记目录

热门文章

  1. 【整合篇】Activiti业务与流程的整合
  2. 抢车位中的排名bug(比較使用了无符号数)
  3. 程序员Web面试之前端框架等知识
  4. 免费ASP,PHP空间
  5. 考考你:输入数字,判定空格和回车
  6. MTD的坏块管理(一)-快速了解MTD的坏块管理
  7. 快手二面:Java 里的 for (;;) 与 while (true),哪个更快?
  8. 系统由单体架构到微服务架构到底是如何演进的?
  9. 再见!Kafka决定弃用Zookeeper...
  10. 我说 SELECT COUNT(*) 会造成全表扫描,面试官让我回去等通知