交换排序-快速排序

  • 时间复杂度:高效的排序算法,比较次数和移动次数都应该尽可能的少。
  • 空间复杂度:算法执行期所需要辅助空间和待排序的数据量无关。理想空间复杂度为O(1)

简述

快速排序可以说算是针对冒泡排序的一种优化,冒泡排序是顺序交换,这样交换次数顺序增长。如果我们做跳跃的交换,那么可以使得交换次数也是跳跃性,会有所降低

算法思想

  1. 找出一个枢轴,用于做比较交换,并记下后用。一般用第一个(用第一、中间、最后三个取中后来效果会更好),定一个高位:high和底位:low
  2. 从表的高位开始,往左进行比较,找到一个关键字小于枢轴,则将high和枢轴交换。否则high--
  3. 从表的低位开始,往右进行比较,找到一个关键字大于等于枢轴,则将low和枢轴交换。否则low++
  4. 重复2和3步,直到low==high位置,此时low和high的位置则是枢轴在本趟排序在表的最终位置,成功将表分成比枢轴小和比枢轴大两个字表在枢轴左右两侧。

以上2和3步骤,每次交换,要做三次记录:

一次移动low到high,一次移动high到low,加中间借助了temp这一类的。

我们可以减少移动记录:

比如一开始记录下枢轴值,在每次移动的时候,只移动要与枢轴交换的记录,移动完成后,最后将开始记录下的枢轴关键字放入最终的枢轴位置。

算法实现

private static class QuickSortClass {

        /**
         * 进行快速排序
         */
        public void quickSort() {
            List<Integer> integers = new ArrayList<>();
            int[] intArray = new int[]{1, 44, 3, 33, 77, 25, 98, 221, 33, 16};

            for (int value : intArray) {
                integers.add(value);
            }
            eSort(integers, 0, intArray.length - 1);
            System.out.print(integers);
        }

        /**
         * 计算枢轴位置,通过进行一轮交换得到新的枢轴位置
         *
         * @param ints
         * @return 返回交换后的枢轴的位置
         */
        private int evaluatePrivotPartition(List<Integer> ints, int l, int h) {

            /*第一步:先选取枢轴记录,使用三者取中原则,我们用第一个、中间一个、最后一个来比较大小,
             取中间大小中间一个为枢轴记录并与第一个交换*/

            int low = l;
            int high = h;

            int oneValue = ints.get(low);
            System.out.println("oneValue=" + oneValue);
            int centerPart = (high + low) / 2;
            int centerValue = ints.get(centerPart);
            System.out.println("centerValue=" + centerValue);
            int lastValue = ints.get(high);
            System.out.println("lastValue=" + lastValue);
            
            int privotDef = oneValue;
            
             //取出中间一个,做中枢记录,如果不用此步骤,那么默认选第一个
            if (oneValue > Math.min(centerValue, lastValue) && oneValue < Math.max(centerValue, lastValue)) {
                privotDef = oneValue;
            } else if (centerValue > Math.min(oneValue, lastValue) && oneValue < Math.max(oneValue, lastValue)) {
                privotDef = centerValue;
                //交换中枢位置
                ints.set(low, centerValue);
                ints.set(centerPart, oneValue);
            } else {
                privotDef = lastValue;
                ints.set(low, lastValue);
                ints.set(high, oneValue);
            }
            System.out.println("枢轴记录:privotDef=" + privotDef);

            /*重复第二三步,直到low==high为止*/
            while (low < high) {
            /*每一轮的交换执行*/
                //第二步:从high往左,如果high位置的值大于等于枢轴记录,那么high--
                while (low < high
                        && ints.get(high) >= privotDef) {
                    high--;
                }
                //小于枢轴值时候,需要和做交换,将high移到low
                if (low < high) {
                    ints.set(low, ints.get(high));
                }

                //第三步:从low往右,如果low位置的值小于等于枢轴记录,那么low++
                while (low < high
                        && ints.get(low) <= privotDef) {
                    low++;
                }
                //找到比枢轴记录大的值,那么low换到high
                if (low < high) {
                    ints.set(high, ints.get(low));
                }
            }

            //将记录下来的枢轴放入low位置,枢轴记录到位
            ints.set(low, privotDef);

            for (Integer value : ints) {
                System.out.print(value + " ");
            }
            System.out.print("\n");
            return low;
        }

        /**
         * 执行快速排序入口
         *
         * @param ints
         * @param low
         * @param high
         */
        private void eSort(List<Integer> ints, int low, int high) {
            //只要low和high不相等,就代表交换后子表长度依旧大于1
            if (low < high) {
                int privotP = evaluatePrivotPartition(ints, low, high);
                eSort(ints, low, privotP - 1);//对左字表进行递归排序
                eSort(ints, privotP + 1, high);//对右字表进行递归排序
            }
        }
    }
  • 客户端调用
public static void main(String[] args) {
        System.out.println("Hello World!");
        new QuickSortClass().quickSort();
    }

可以看到,在evaluatePrivotPartition中有一个,取中间值的步骤,不加那一步也是可以,跑一下你就会发现,本次排序用了6趟。如果不加那一步,则默认使用第一个为枢轴记录,最后排序下来的趟数会是7趟。

不要小看只多了一趟,如果数更大,优势会更明显。

算法分析

  • 时间复杂度

可以看下排序的递归树:

image

大致也能看出,排序的躺数取决于递归树的深度.

  1. 最好的情况:每一趟都能将记录均匀分割成两个长度大致相等的子表,类似折半查找。n个序列,枢轴定位时间O(n),若T(n)是对n个元素进行排序所需对时间,且每次定位,正好把序列划分为长度相等的两个子表,设Cn是一个常数,表示n个元素进行一趟快速排序的时间,则总时间:

T(n)=Cn+2T(n/2)

T(n) <=n+2T(n/2)

继续往下得到T(n)约等于O(nlog2n)

  1. 最坏的情况:带排序序列已经排好的情况下,其递归数为单只树,每次划分得到一个比上一个少的序列,这样必须经过n-1趟才能将所有记录定位,而且第i趟需要经过n-i次比较,比较次数就为n2/2

为了避免最坏情况,所以我们可以使用三者取中原则进行选取枢轴记录,就是前面代码用到的方式

  • 空间复杂度

排序是递归的,所以需要有一个栈来存放相应的数据。最大递归调用次数与递归树的深度一致。为O(log2n),最坏情况下为O(n)

算法特点

  1. 不稳定:记录非顺序次地移动导致
  2. 难用于链式结构:需要定位表的下界和上界,适合顺序结构
  3. 平均来说,快排可以说是内部排序中最快的一种了,在初始无序,n比较大的情况,使用快排是比较合适的。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容