数据结构和算法之排序二:快速排序

    上一篇文章我们讲完了归并排序,对于分而治之和递归思想应该都有了一定的理解,这篇文章我们将介绍道被认为是排序算法中最容易出错,但是又是最喜欢使用的一中排序方式,快速排序。对于快速排序而言我们必须抓住几个关键点就是基准值的选取,以及它在递归思想的运用过程中需要注意的事项。我们先看下面的图片了解一下快速排序的过程。

    

      我们可以看出每一次排序过程中都是选取第一个数据作为基准值,然后在前段和末端设置两个指针,指针对应的数据和基准值进行比较,如果大于基准值我们将它放在右边,如果小于基准值,我们就将它放在左边。这样经过N次排序后,我们将会得到一个有序的数组。话不多说,直接上代码,然后我们在进行详细的理解。

public static void quickSort(int arr[],int start,int end){
      int i  = start,j = end;
      //声明一个基准值
      int key;
     //条件判断我们是否继续进行递归
      if(i < j){
          key = arr[start];
          //控制i和j不出现交错现象的条件下进行循环的换值
          while(i < j){
               //从右侧开始与基准值进行比较
               while(arr[j] >= key && i < j){
                   j--;
                }
                //如果当右侧值小于基准值时我们就与右侧i位置的值进行互换
                arr[i] = arr[j];
                while(arr[i] <= key && i < j){
                     i++;
                 }
                //如果左侧的值大于基准值时,我们就进行互换
                arr[j] = arr[i];
          }
          //基准值归位
          arr[i] = key;
         //继续进行递归排序
         quickSort(arr,start,i - 1);
         quickSort(arr,i + 1,end);
      }
}

    有些朋友在看完代码和示意图以后可能还比较蒙圈,这是什么意思呢,或者说还在疑惑某些地方为什么要这儿做呢,在这里我一一给大家诉说。

    一:外层if判断的意义:

      就像我们在代码中看到的一样,这是为了控制最后结束时是否继续再继续进行排序,总不可能无休止的进行下去吧,这个终止的条件就是当我们的开始和结束相等时就该结束了,我们可以画图理解一下,我      们在不断的缩小每一次排序的范围。

    二:为什么基准值的选取会在if条件内进行赋值:

      我们可以假设一下,去过我们将其放在外边进行赋值,当我们遇到你选取的基准值正好是最大的那个数据时,通过第一次排序以后基准值的位置是不是位于最后一个,那么这时我们进行了加一的处理,最后    再一次进行递归排序,你直接用原数组进行赋值,是不是会造成ArrayIndexOutOfBoundsException数组下标越界这个异常,如果可以,最好你亲自演示下,如果想这块进行改变也可以,那么我们就将整个判断if    改为以下代码,避免在赋值的时候出现下标越界。

if(i >= j){
  return;
}

    三:外层while条件的意义在哪里:

      试问一下我们在进行i位置和j位置的数据进行交换难道一次就结束了么,那必然是不现实的,快速排序的核心思想在于经过一次基准的选取以后,那么它左边的数据必然是都比它大,右边的数据都比它小,    那我们一定要对所有的数据都进行一次判断。所以我们在里面会嵌套两个while在右边开始的时候一直找到比基准值小的数为止进行互换,然后左边在开始,如此反复,直到i等于j为止。

    四:我们的顺序是不是必须从右边开始寻找 在值进行交换时没有用到中间值会不会造成数据覆盖:

      这个问题很关键,一定要记住了,必须从右边开始寻找,因为我们的基准值在左边,如果我们从左边开始,那么我们会把第一次覆盖的数据就会不可知,最后造成数据的覆盖。这也是可以解释为什么我们没    有使用中间值也不会造成数据覆盖的原因,试想一下,我们第一次交换值是不是必然是覆盖掉基准值,然后第一个比基准小的数据会有两个,这时候左边开始动作,当它找到比基准值大的数据的时候是不是又会    交换,覆盖掉一个停留在右边的数据,这样结合基准值归位,是不是就能恢复数据,实在不巧左边到遇到右边的游标时也没找到更大的,这时候停下来也要进行基准值归位,也是同一个道理。不理解的朋友可以    在画图理解一下。

    五:这么多的i < j是不是会显得多余:

      一点不会,因为我们第一次判断i<j是用来断定是否还需要进行递归,实在里层while循环i < j跳出以后进行的 。那么这时候我们会问,那么里层while循环的i<j是否又可以省略,答案很显然是不行,这样造成    的结果可以想象是不是会让i 和  j 一直不停地寻找下去。那么最里层的i < j 又是否可以省略,这可以思考一下,我们最后会进行基准值归位,如果省略了这个i < j 会不会造成i 和 j 最后停留的位置相差一位,真个    算法又会崩溃。

    如果都理解以后我们考虑一下会不会这个算法有问题,我们每次都选取第一个数据作为我们的基准值,那么如果哪个数据正好是最大值,或者是最小值,那么这样算法设计的意义何在?我们有什么办法可以去避  免,有人说我们可以取随机数,但是值得我们思考的是算法的核心很大意义上是基于时间复杂度和空间复杂度的考虑上进行的,如果每次都需要进行随机选取一个数作为基准值是否会影响我们算法的效率。在这里我  介绍一中公认的比较获取认可的办法,那就是三元取中法:

    其实这个名字或许听上去还不错,很高大上的感觉,其实那都是错觉,所谓的三元取中法就是将开始数据,结尾数据,中间值取出,选取 三个中的中间值作为基准值,这就是所谓的三元取中法。代码我就不演示了!

    时间复杂度的计算:

      1:如上所诉,时间复杂度会有最优秀的情况,就是恰好选取到了中间值

      在最优情况下,Partition每次都划分得很均匀,如果排序n个关键字,其递归树的深度就为 [log2n]+1( [x] 表示不大于 x 的最大整数),即仅需递归 log2n 次,需要时间为T(n)的话,第一次Partiation   应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以平分两半)。于是不断地划分下去,就有了下面的不等式推断:

      

      这说明,在最优的情况下,快速排序算法的时间复杂度为O(nlogn)。

             2:最糟糕的情况,就是我上边诉说的选取到了最大值和最小值:

        然后再来看最糟糕情况下的快排,当待排序的序列为正序或逆序排列时,且每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执     行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为

     

     最终其时间复杂度为O(n^2)。

     3:一般情况:    

        最后来看一下一般情况,平均的情况,设枢轴的关键字应该在第k的位置(1≤k≤n),那么:

        

    风萧萧兮易水寒,学会算法也不难。万道艰辛只为阻,破釜沉舟方可为。学习算法务必要多思考为什么,也许你遇到的问题不会就我说的那几点,也或许你没有遇到这样的问题,业精于勤荒于嬉,有事无事写一    遍,养成无聊就默写或者想想一下算法的历程,我相信没有什么可以阻挡你,就怕你认真。

时间: 2024-08-08 04:46:39

数据结构和算法之排序二:快速排序的相关文章

javascript数据结构与算法--高级排序算法

高级排序算法是处理大型数据集的最高效排序算法,它是处理的数据集可以达到上百万个元素,而不仅仅是几百个或者几千个.现在我们来学习下2种高级排序算法---- 希尔排序和快速排序. 一:希尔排序: 希尔排序的核心理念是:首先比较距离较远的元素,而非相邻的元素. 基本原理:通过定义一个间隔序列来表示在排序过程中进行比较的元素之间有多远的间隔. 下面我们来看看数组[0,9,1,8,7,6,2,3,5,4] 来使用希尔排序的原理:如下图: 代码分析如下: 1. 执行 "间隔序列=3的步骤" A.

Java数据结构与算法之排序

排序从大体上来讲,做了两件事情: 1.比较两个数据项: 2.交换两个数据项,或复制其中一项 一.冒泡排序 大O表示法:交换次数和比较次数都为O(N*N). 算法原理: 1.比较相邻的元素.如果第一个比第二个大,就交换他们两个. 2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.在这一点,最后的元素应该会是最大的数. 3.针对所有的元素重复以上的步骤,除了最后一个. 4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较. /** * 冒泡排序 demo * */

(转载)排序二 快速排序

排序二 快速排序 目录 要点 算法分析 快速排序算法的性能 时间复杂度 空间复杂度 算法稳定性 完整参考代码 JAVA版本 参考资料 相关阅读 要点 快速排序是一种交换排序. 快速排序由C. A. R. Hoare在1962年提出. 它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数. 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列. 详细的图解往往比大堆的文字更有说明力,所以直接上图:

在Object-C中学习数据结构与算法之排序算法

笔者在学习数据结构与算法时,尝试着将排序算法以动画的形式呈现出来更加方便理解记忆,本文配合Demo 在Object-C中学习数据结构与算法之排序算法阅读更佳. 目录 选择排序 冒泡排序 插入排序 快速排序 双路快速排序 三路快速排序 堆排序 总结与收获 参考与阅读 选择排序 选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n2) 的时间复杂度.所以用到它的时候,数据规模越小越好.唯一的好处可能就是不占用额外的内存空间了吧. 1.算法步骤 首先在未排序序列中找到最小(大)元素,存放到排

算法: 排序: 快速排序

1. Algrithom ?Given an array of values, pick a value as a pivot value ?Check each value against the pivot value and - bring each value higher than the pivot value to the right of the pivot value: GreaterList - bring each value lower than or equal to

python 数据结构与算法之排序(冒泡,选择,插入)

目录 数据结构与算法之排序(冒泡,选择,插入) 为什么学习数据结构与算法: 数据结构与算法: 算法: 数据结构 冒泡排序法 选择排序法 插入排序法 数据结构与算法之排序(冒泡,选择,插入) 为什么学习数据结构与算法: 计算机重要的几门课: 1.数据结构和算法 2.网络 3.操作系统 4.计算组成原理 数据结构与算法: 算法: 衡量算法的标准: 时间复杂度:就是程序代码执行的大概次数 小结: 时间复杂度是用来估计算法运行时间的一个式子(单位) 一般来说,时间复杂度高的算法比复杂度低的算法慢 常见的

算法之排序二

算法之排序二 四.冒泡排序与插入排序       为何在实际中倾向于使用插入排序而不是冒泡排序,尽管它们的时间复杂度都是O(n2),而且也都是稳定的.看一下两个算法在交换元素数值的处理上就知道了.对于冒泡排序,交换两个元素时需要引入中间变量,也就是如果需要交换 A 和 B,我们需要让 A 赋值给 C,然后让 A 等于 B,再让 B 等于 C.而插入排序在每次比较时会把大的元素往后移,要插入的时候直接插入,所以更加的直接,在实际应用时更常用.在 Python 上测试一下也可以知道,冒泡排序比插入排

数据结构与算法之排序(归纳总结二)

交换类排序主要是通过两两比较待排元素的关键字,若发现与排序要求相逆,则“交换”之.在这类排序方法中最常见的是起泡排序和快速排序,其中快速排序是一种在实际应用中具有很好表现的算法. 1.冒泡排序 a.算法描述 起泡排序的思想非常简单.首先,将n个元素中的第一个和第二个进行比较,如果两个元素的位置为逆序,则交换两个元素的位置:进而比较第二个和第三个元素关键字,如此类推,直到比较第n-1个元素和第n个元素为止:上述过程描述了起泡排序的第一趟排序过程,在第一趟排序过程中,我们将关键字最大的元素通过交换操

数据结构和算法之排序一:归并排序

我们不得不承认一个事实,java学习过程中如果我们掌握了各种编程手段和工具,确实可以做一些开发,这就是一些培训机构敢告诉你几个月就能掌握一门语言的原因.但是随着时间的发展,我们总会感觉,这一类人如果不提升自己,最后也只会是一个码农.技术会日新月异,随时在发展更新换代,但是这几十年,有谁说过算法会过时,如果我们说java语言的发动机是各种开发手段和技术,那么我们可以毫不客气的说算法会是他的灵魂.一个程序员的提升和拔高一定是万丈高楼平地起,那么我希望这个地基一定是数据结构和算法,掌握这些原理以后其实