一、本章重点

  1. 快排的思想

  2. 实现单趟快排的三种方式(hoare、挖坑、前后指针)

  3. 递归实现快排

  4. 快排递归算法时间复杂度计算

  5. 对快排进行优化(三数取中,小区间优化)

  6. 非递归实现快排(栈或队列实现)

二、快排

2.1快排思想

快排本质上是一种交换排序,我们先从单趟的角度来说:快排的单趟排序会让你选择Key放在数组正确的位置,什么是正确的位置?就是你单趟排序后,这个数(Key)就已经排好了,后续不需要改变了,怎么保证它处于正确的位置呢?只要它的左边的所有数都小于等于它,右边的所有数都大于等于它,那么它就处于正确的位置。(升序)。

快排单趟步骤:从数组选择一个Key的数字,一般选择最左边或者最右边,这里我选择数组最左边的数,举例:5 3 2 8 6 1 10 9 3 4 7,这里的Keyi就是0,a[ keyi ]是5

如何进行交换数组元素让5放在正确的位置?

2.2三种单趟排序

这里有三种快排的单趟排序算法:

第一种:hoare,也是最早发明快速排序算法人写的-----托尼·霍尔(Tony Hoare

这种方法是:先选一个Keyi,取两个整形变量left、right,这两个整形变量代表数组下标,初始它们分别指向0和n-1。然后让right先移动,找到大于a[ keyi ]的数,然后right停下来,再让left移动,找到小于a[ keyi ]的数,停下,再交换a[ left ]和a[ right ],如果在right或者left移动的途中,right==left,即right和left相遇的时候,right和left必然指向的是比a[ keyi ]小的数。然后a[keyi]和这个比它小的数交换(相遇点),它们最终a[keyi]处于正确位置,即它的左边所有数都小于等于它,右边所有数都大于等于它。

图示:

需要注意的是:需让right先走,然后left在走。否则它们相遇点可能不是比a[keyi]小的数。

参考代码:

int Q_PartSort1(int* a, int begin, int end)//hoare
{int keyi = begin;int left = begin;int right = end;while (left < right){while (left < right && a[right] >= a[keyi])//右边找小于a[keyi]的数{right--;}while (left < right && a[left] <= a[keyi])//左边找大于a[keyi]的数{left++;}Swap(&a[left], &a[right]);}//将a[keyi]与相遇点交换(要保证相遇点比a[keyi]小,需要让right先走)Swap(&a[keyi], &a[right]);return right;
}

第二种:挖坑法

步骤:先保存a[keyi]的值到int temp上,然后将keyi先作坑,int left=0、int right=n-1

先让right走,找到小于a[keyi]的数,就将它放在a[hole]处,更新hole=right。然后left再走,找到大于a[keyi]的数,然后将它放在a[hole]处,再次更新hole=left,再让right移动,left移动,直到left等于right。此时相遇点必然是一个坑,最后将temp放在a[hole]处。

图示:

与hoare不同的是这里并不需要保证相遇点的值比temp小。

参考代码:

int Q_PartSort2(int* a, int begin, int end)//挖坑法
{int key = a[begin];int hole = begin;int left = begin;int right = end;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;
}

第三种:前后指针法

取最左边的下标左keyi,prev=begin、next=begin+1

next找小,如果找到小于a[ keyi ]的数,就让prev++,然后将a[prev]和a[next]交换。

直到next大于n,结束。

最后再让a[keyi]和a[prev]交换。

图示:

参考代码:

int Q_PartSort3(int* a, int begin, int end)//前后指针法
{int keyi = begin;int prev = begin;int next = begin + 1;while (next <= end){if (a[next] < a[keyi] && ++prev != next){Swap(&a[prev], &a[next]);}next++;}Swap(&a[keyi], &a[prev]);return prev;
}

三种单趟排序,我们都需要掌握,有的时候会考查下面这种题目

需要说明的是:上面这组数据虽然三种单趟排序之后结果是一样的,但这属于巧合情况,增加更多的数据,单趟之后结果可能会不同。

设一组初始记录关键字序列为(65,56,72,99,86,25,34,66),则以第一个关键字65为基准而得到的一趟快速排序结果是()
A 34,56,25,65,86,99,72,66
B 25,34,56,65,99,86,72,66
C 34,56,25,65,66,99,86,72
D 34,56,25,65,99,86,72,66
题目并未说明使用哪种单趟快排,面对这样的题目,你需要用三种单趟都试试。

2.3递归实现快排

先放参考代码,然后我们再画一画递归的过程。

参考代码:

void _QuickSort1(int* a,int begin,int end)//递归
{if (begin >= end){return;}int keyi = Q_PartSort2(a, begin, end);_QuickSort1(a, begin, keyi - 1);_QuickSort1(a, keyi + 1, end);
}

因为空间原因,右半边就不画了。

2.4快排递归算法时间复杂度计算

最坏情况:有序

大概执行次数是T(N) = N+N-1+N-2+.....+3+2+1

时间复杂度是O(N*N)

空间复杂度O(logN)

最好情况:每次取的Key都是中位数

相当于一颗满二叉树,高度为 logN

时间复杂度是N*logN

空间复杂度O(logN)

2.5对快排进行优化

2.5.1优化一:三数取中

我们知道有序数组是对快排不利的,从这个角度出发,我们有了三数取中的优化方式:

即选出mid=(left+right)/2

a[left]、a[right]、a[mid]这三个数中,值为中位数的那个数,然后将它于a[keyi]交换。

参考代码:

int GetMidIndex(int* a, int begin, int end)
{int mid = begin + ((end - begin)>>1);if ((a[mid] >= a[begin] && a[mid] <= a[end])|| (a[mid]>=a[end] && a[mid] <= a[begin])){return mid;}if ((a[begin]<=a[mid] && a[begin]>=a[end])||(a[begin] >= a[mid] && a[begin] <= a[end])){return begin;}return end;
}int Q_PartSort3(int* a, int begin, int end)//前后指针法
{//三数取中优化int ki = GetMidIndex(a, begin, end);Swap(&a[begin], &a[ki]);int keyi = begin;int prev = begin;int next = begin + 1;while (next <= end){if (a[next] < a[keyi] && ++prev != next){Swap(&a[prev], &a[next]);}next++;}Swap(&a[keyi], &a[prev]);return prev;
}

2.5.2小区间优化

当区间很小时,直接采用插入排序,就不用继续递归了。

参考代码:

void _QuickSort1(int* a,int begin,int end)//递归
{//小区间优化if (end - begin + 1 <= 12){InsertSort(a, end - begin + 1);}if (begin >= end){return;}int keyi = Q_PartSort2(a, begin, end);_QuickSort1(a, begin, keyi - 1);_QuickSort1(a, keyi + 1, end);
}

最后,一般的排序都是传a和n,为了不传区间,这里加一层封装。

void QuickSort(int* a, int n)
{_QuickSort1(a, 0, n - 1);//递归
}

2.6非递归的快排

当要排序的数很多时,可能导致栈溢出,因此需要非递归的快排算法。

这里采用栈+循环来模拟递归调用过程,时间效率上和调用递归并无很大差别。

本质上和调用递归的过程一样

参考代码:

void _QuickSort2(int* a, int begin, int end)//非递归
{ST st;STInit(&st);//检查传递的end和beginif (end > begin){STPush(&st, begin);STPush(&st, end);}while (!STEmpty(&st)){int right = STRear(&st);STPop(&st);int left = STRear(&st);STPop(&st);int mid = Q_PartSort2(a, left, right);if(left<mid-1){STPush(&st, left);STPush(&st, mid - 1);}    if(mid+1<right){STPush(&st, mid + 1);STPush(&st, right);}}STDestroy(&st);
}

队列实现快排非递归算法:

参考代码:

void _QuickSort3(int* a, int begin, int end)//非递归
{Queue q;QueueInit(&q);if (end > begin){QueuePush(&q, begin);QueuePush(&q, end);}while (!QueueEmpty(&q)){int left = QueueFront(&q);QueuePop(&q);int right = QueueFront(&q);QueuePop(&q);int keyi = Q_PartSort1(a, left, right);if (left < keyi-1){QueuePush(&q, left);QueuePush(&q, keyi-1);}if (keyi + 1 < right){QueuePush(&q, keyi+1);QueuePush(&q, right);}}QueueDestroy(&q);
}

《排序算法篇》快排的递归与非递归相关推荐

  1. python算法题排序_python-数据结构与算法- 面试常考排序算法题-快排-冒泡-堆排-二分-选择等...

    算法可视化网站推荐---->visualgo 0.面试题中的排序算法 一些排序算法可能在工作中用的会比较少,但是面试却是不得不面对的问题.算法有助于提高我们对数据结构的理解以及提高自己的逻辑能力 ...

  2. Leetcode 912.排序算法(快排)

    难度:中等 频率:179 题目: 给你一个整数数组nums,请你将改数组升序排列. ** PS:快排需要注意的点** 1.快排 平均时间复杂度O(NlogN) 空间复杂度logn 不稳定. 2.基于比 ...

  3. 排序算法杂谈(三) —— 归并排序的非递归实现

    1. 递归 在众多排序算法中,归并排序(Merge Sort)和快速排序(Quick Sort)都是时间复杂度为 O(nlog2n) 的高效排序. 这两种排序有一种共性,就是运用到了递归的思想. 在程 ...

  4. 快速排序的递归和非递归

    快速排序,顾名思义,是一种速度快,效率高的排序算法. 快排原理: 在要排的数(比如数组A)中选择一个中心值key(比如A[0]),通过一趟排序将数组A分成两部分,其中以key为中心,key右边都比ke ...

  5. 全排列(含递归和非递归的解法)

    全排列在近几年各大网络公司的笔试中出现的比较频繁 首先来看看题目是如何要求的(百度迅雷校招笔试题). 用C++写一个函数, 如 Foo(const char *str), 打印出 str 的全排列, ...

  6. 数据结构与算法(五)排序算法篇

    排序算法篇 恭喜各位小伙伴来到最后一部分:排序算法篇,数据结构与算法的学习也接近尾声了,坚持就是胜利啊! 一个数组中的数据原本是凌乱的,但是由于需要,我们需要使其有序排列,要实现对数组进行排序我们之前 ...

  7. 【数据结构与算法】快排、归并 O(nlogn) 基于比较

    冒泡.插入.选择 O(n^2) 基于比较 快排.归并 O(nlogn) 基于比较 计数.基数.桶 O(n) 不基于比较 一.分治思想 1.分治思想:分治,顾明思意,就是分而治之,将一个大问题分解成小的 ...

  8. 快速选择算法-基于快排

    目录 快速选择算法 例题 算法思路 代码实现 快速选择算法 快速选择,就是在一个无需的数组中查找第k个小的数.我们可以很快的想到可以先将数组排序在进行选择,但时间复杂度为O(nlog⁡n)O(n\lo ...

  9. C#实现(递归和非递归)快速排序和简单排序

    C#实现(递归和非递归)快速排序和简单排序 本人因为最近工作用到了一些排序算法,就把几个简单的排序算法,想冒泡排序,选择排序,插入排序,奇偶排序和快速排序等整理了出来,代码用C#代码实现,并且通过了测 ...

  10. 算法之快速排序(递归和非递归)

    快速排序的两种实现方式.递归和非递归 1 package com.ebiz.sort; 2 3 import java.text.SimpleDateFormat; 4 import java.uti ...

最新文章

  1. AJAX技术其实就是多年前我就使用过的XMLHTTP
  2. mybatisdb.sql
  3. 华为交换机在Telnet登录下自动显示接口信息
  4. 客服会话 小程序 如何发起_小程序、公众号、App三者如何融合布局?这里有一份避坑指南...
  5. windows 常用系统变量
  6. 著名开源项目_著名开源项目案例研究
  7. typedef的使用方法
  8. Python格式化字符串f-string常用用法
  9. 用户画像有什么用?怎样用?6个场景案例给你讲明白
  10. 手把手教用XNA开发winphone7游戏(四)
  11. 老话题:自己编写只截窗口客户区的截屏软件(VB2010)
  12. C#利用扩展方法,不引入新的类型,实现矩阵的操作
  13. Latex WinEdt7.0查找替换功能
  14. Log4j不同级别输出到不同文件的几种方式
  15. 随机过程通过线性系统matlab,随机信号分析实验:随机过程通过线性系统的分析.doc...
  16. 艾永亮:2平米的Manner如何战胜200平米的星巴克
  17. python 中英文时间转换
  18. QGIS加载在线地图:高德、天地图等
  19. 某短视频的X-Gorgon,X-Ladon等加密
  20. IE6 PNG 图片问题分析

热门文章

  1. 一加手机高德位置服务器,高德地图在线导航悬浮窗闪亮一加手机
  2. 支持百度网盘同步的专业备份软件
  3. 如何将腾讯视频客户端下载的QLV视频格式转换成MP4格式
  4. 【语义分割数据集】——cityscape数据集类别名称
  5. 浅谈2020年国内第三方支付平台安全性
  6. python查找excel中重复数据_python实现查找excel里某一列重复数据并且剔除后打印的方法...
  7. react hooks子组件向父组件传参
  8. 海康网络摄像机的使用
  9. 【Java工具类】(27)—AES加密工具类
  10. Google Chrome浏览器翻译失败