排序(中_對(duì)數(shù)階)

2018年12月23日

歸并排序

1会涎,算法思想

遞歸法(自上而下)

  1. 申請(qǐng)空間,使其大小為兩個(gè)已經(jīng)排序序列之和瑞凑,該空間用來存放合并后的序列
  2. 設(shè)定兩個(gè)指針末秃,最初位置分別為兩個(gè)已經(jīng)排序序列的起始位置
  3. 比較兩個(gè)指針?biāo)赶虻脑兀x擇相對(duì)小的元素放入到合并空間籽御,并移動(dòng)指針到下一位置
  4. 重復(fù)步驟3直到某一指針到達(dá)序列尾
  5. 將另一序列剩下的所有元素直接復(fù)制到合并序列尾

迭代法(自下而上)

  1. 將序列每相鄰兩個(gè)數(shù)字進(jìn)行歸并操作练慕,形成ceil(\frac{n}{2})(向上取整)個(gè)序列,排序后每個(gè)序列包含兩/一個(gè)元素
  2. 若此時(shí)序列數(shù)不是1個(gè)則將上述序列再次歸并技掏,形成ceil(\frac{n}{4})個(gè)序列铃将,每個(gè)序列包含四/三個(gè)元素
  3. 重復(fù)步驟2,直到所有元素排序完畢哑梳,即序列數(shù)為1

2劲阎,算法圖解

3,算法實(shí)現(xiàn)

public class Merge<AnyType extends Comparable<? super AnyType>> {
    private AnyType[] aux;

    /**
     * 自上向下
     */
    public void sort(AnyType[] a) {
        aux = (AnyType[]) new Comparable[a.length];
        sort(a, 0, a.length - 1);
    }

    public void sort(AnyType[] a, int lo, int hi) {
        if (lo >= hi) {
            return;
        }
        int mid = lo + (hi - lo) / 2;
        sort(a, lo, mid);
        sort(a, mid + 1, hi);
        /**
         * 因?yàn)樽笥也糠指髯杂行?         * 則只需a[mid]>a[mid+1], 才進(jìn)行排序
         */
        if (a[mid].compareTo(a[mid + 1]) > 0) {
            merge(a, lo, mid, hi);
        }
    }

    public void merge(AnyType[] a, int lo, int mid, int hi) {
        /**
         * i從左半部分開始
         * j從右半部分開始
         */
        int i = lo;
        int j = mid + 1;
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }
        /**
         * 左右兩半部分各自有序
         * 若左半部分用盡涧衙,則直接取右半部分元素
         * 若右半部分用盡哪工,則直接去左半部分元素
         * 若右半部分當(dāng)前元素小于左半部分當(dāng)前元素奥此,則取右半部分元素
         * 若右半部分當(dāng)前元素大于左半部分當(dāng)前元素弧哎,則取左半部分元素
         */
        for (int k = lo; k <= hi; k++) {
            if (i > mid) {
                a[k] = aux[j++];
            } else if (j > hi) {
                a[k] = aux[i++];
            } else if (aux[j].compareTo(aux[i]) < 0) {
                a[k] = aux[j++];
            } else {
                a[k] = aux[i++];
            }
        }
    }

    /**
     * 自下向上
     * 比較適合用鏈表組織的數(shù)據(jù)
     */
    public void sortBU(AnyType[] a) {
        int N = a.length;
        aux = (AnyType[]) new Comparable[a.length];
        for (int sz = 1; sz < N; sz *= 2) {
            for (int lo = 0; lo < N - sz; lo += 2 * sz) {
                /**
                 * mid=lo+((lo+sz+sz-1)-lo)/2=lo+(sz+sz-1)/2=lo+sz-1
                 * 之所以使用Math.min, 是因?yàn)楫?dāng)N為奇數(shù)時(shí)的情況
                 */
                merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
            }

        }
    }
}

采用自上而下的方法其圖解:序列個(gè)數(shù)為偶數(shù)


當(dāng)序列的個(gè)數(shù)為奇數(shù)時(shí):


采用自下而上的方法其圖解:當(dāng)序列個(gè)數(shù)為偶數(shù)時(shí)


當(dāng)序列個(gè)數(shù)為奇數(shù)時(shí):


4,復(fù)雜度分析

  • merge方法在將兩個(gè)有序數(shù)組合并為一個(gè)有序數(shù)組時(shí)稚虎,需要借助額外的存儲(chǔ)空間撤嫩,其空間復(fù)雜度為O(N)
  • merge方法在合并數(shù)組時(shí)蠢终,不改變相同元素間的前后順序序攘,因此,歸并排序時(shí)穩(wěn)定的寻拂。
    對(duì)N個(gè)數(shù)歸并排序的用時(shí)等于完成兩個(gè)大小為\frac{N}{2}的遞歸排序所用時(shí)間加上合并所用的時(shí)間程奠,對(duì)于merge方法,其時(shí)間復(fù)雜度為O(N)祭钉,當(dāng)N=1時(shí)瞄沙,歸并排序所用時(shí)間為常數(shù),記為1慌核,那么有:
    T(1)=1
    T(N)=2T(\frac{N}{2})+N
    N=\frac{N}{2}距境,再將兩邊乘2,那么:
    2T(\frac{N}{2})=2(2T(\frac{N}{4})+\frac{N}{2})=4T(\frac{N}{4})+N
    因此可以得到:
    T(N)=4T(\frac{N}{4})+2N
    同理垮卓,令N=\frac{N}{4}垫桂,并在兩邊乘4,那么:
    4T(\frac{N}{4})=4(2T(\frac{N}{8})+\frac{N}{4})=8T(\frac{N}{8})+N
    又可以得到:
    T(N)=8T(\frac{N}{8})+3N
    總結(jié)規(guī)律有:
    T(N)=2^kT(\frac{N}{2^k})+kN
    其中k=logN
    最后有:
    T(N)=NT(1)+NlogN=NlogN+N
    因?yàn)闅w并排序的執(zhí)行效率與待排序數(shù)組的有序程度無關(guān)粟按,所以其時(shí)間復(fù)雜度是非常穩(wěn)定的诬滩,因此霹粥,無論是最好、最壞還是平均情況下的時(shí)間復(fù)雜度都為O(NlogN)疼鸟。

快速排序

1蒙挑,算法思想

快速排序使用分治法(Divide and conquer)策略來把一個(gè)序列(list)分為兩個(gè)子序列(sub-lists)。

步驟為:

  1. 從數(shù)列中挑出一個(gè)元素愚臀,稱為“基準(zhǔn)”(pivot)忆蚀,
  2. 重新排序數(shù)列,所有比基準(zhǔn)值小的元素?cái)[放在基準(zhǔn)前面姑裂,所有比基準(zhǔn)值大的元素?cái)[在基準(zhǔn)后面(相同的數(shù)可以到任何一邊)馋袜。在這個(gè)分割結(jié)束之后,該基準(zhǔn)就處于數(shù)列的中間位置舶斧。這個(gè)稱為分割(partition)操作欣鳖。
  3. 遞歸地(recursively)把小于基準(zhǔn)值元素的子數(shù)列和大于基準(zhǔn)值元素的子數(shù)列排序。
    遞歸到最底部時(shí)茴厉,數(shù)列的大小是零或一泽台,也就是已經(jīng)排序好了。這個(gè)算法一定會(huì)結(jié)束矾缓,因?yàn)樵诿看蔚牡╥teration)中怀酷,它至少會(huì)把一個(gè)元素?cái)[到屬于它的位置去。

2嗜闻,算法圖解

3蜕依,算法實(shí)現(xiàn)

public class Quick<AnyType extends Comparable<? super AnyType>> {
    public void sort(AnyType[] a) {
        sort(a, 0, a.length - 1);
    }

    private void sort(AnyType[] a, int lo, int hi) {
        if (lo >= hi) {
            return;
        }
        int j = partition(a, lo, hi);
        sort(a, lo, j - 1);
        sort(a, j + 1, hi);
    }

    private int partition(AnyType[] a, int lo, int hi) {
        int i = lo;
        int j = hi + 1;
        AnyType pivot = a[lo];
        int size = hi - lo + 1;
        if (size >= 3) {
            pivot = medianOfThree(a, lo, hi);
        }
        while (true) {
            while (a[++i].compareTo(pivot) < 0) {
                if (i == hi) {
                    break;
                }
            }
            while (pivot.compareTo(a[--j]) < 0) {
                if (j == lo) {
                    break;
                }
            }
            if (i >= j) {
                break;
            }
            swap(a, i, j);
        }
        swap(a, lo, j);
        return j;
    }

    private AnyType medianOfThree(AnyType[] a, int lo, int hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[lo].compareTo(a[hi]) > 0) {
            swap(a, lo, hi);
        }
        if (a[mid].compareTo(a[hi]) > 0) {
            swap(a, mid, hi);
        }
        if (a[mid].compareTo(a[lo]) > 0) {
            swap(a, mid, lo);
        }
        return a[lo];
    }

    private void swap(AnyType[] a, int i, int j) {
        AnyType temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

4,復(fù)雜度分析

  • 最好的情況最多需要 O(logN)次的嵌套遞歸調(diào)用琉雳,所以它需要 O(logN)的空間样眠。最壞情況下需要 O(N)次嵌套遞歸調(diào)用,因此需要 O(N)的空間翠肘。
  • 如圖檐束,以3位基準(zhǔn),兩個(gè)1前后順序改變了束倍,因此快速排序時(shí)不穩(wěn)定的

快速排序的運(yùn)行時(shí)間等于兩個(gè)遞歸調(diào)用的運(yùn)行時(shí)間加上partition方法運(yùn)行時(shí)間:
T(N)=T(i)+T(N-i-1)+N
令T(0)=T(1)=1

  • 最好情況下被丧,基準(zhǔn)pivot正好位于中間,那么T(N)=2T(\frac{N}{2})+N肌幽,該情況與上面歸并排序分析相同晚碾,那么快速排序在最好情況下的時(shí)間復(fù)雜度為O(NlogN)
  • 最壞情況下喂急,每次選取的基準(zhǔn)pivot為最小元素格嘁,此時(shí)i=0
    T(N)=T(N-1)+N
    T(N-1)=T(N-2)+N-1
    T(N-2)=T(N-3)+N-3
    ……
    T(2)=T(1)+2
    將兩邊相加后得到:
    T(N)=\sum_{i=1}^{N}i=\frac{N(N-1)}{2}
    因此,最壞情況下快速排序的時(shí)間復(fù)雜度為O(N^2)廊移。為了避免這種極端情況糕簿,盡量避免基準(zhǔn)pivot為最小元素探入,可以采用三數(shù)取中來選取pivot,即上述medianOfThree方法懂诗,該方法取頭中尾三個(gè)數(shù)之間的中位數(shù)蜂嗽。
  • 平均情況下,選取基準(zhǔn)大小概率為\frac{1}{N},那么T(i)的平均值為\frac{1}{N}\sum_{j=0}^{N-1}T(j)殃恒,那么
    T(N)=T(i)+T(N-i-1)+N=\frac{2}{N}\sum_{j=0}^{N-1}T(j)+N
    那么:
    NT(N)=2\sum_{j=0}^{N-1}T(j)+N^2
    (N-1)T(N-1)==2\sum_{j=0}^{N-2}T(j)+(N-1)^2
    由上面式子可得:
    NT(N)-(N-1)T(N-1)=2T(N-1)+2N-1
    NT(N)=(N+1)T(N-1)+2N
    又可以得到:
    \frac{T(N)}{N+1}=\frac{T(N-1)}{N}+\frac{2}{N+1}
    \frac{T(N-1)}{N}=\frac{T(N-2)}{N-1}+\frac{2}{N}
    \frac{T(N-2)}{N-1}=\frac{T(N-3)}{N-2}+\frac{2}{N-1}
    ……
    \frac{T(2)}{3}=\frac{T(1)}{2}+\frac{2}{3}
    最終可得:
    \frac{T(N)}{N+1}=\frac{T(1)}{2}+2\sum_{i=3}^{N+1}\frac{1}{i}=ln(N+1)+\gamma-\frac{3}{2}
    其中\gamma\approx0.577叫做歐拉常數(shù)植旧,于是
    \frac{T(N)}{N+1}=O(logN)
    T(N)=O(NlogN)
    所以快速排序的平均時(shí)間復(fù)雜度為O(NlogN)

5离唐,應(yīng)用

LeetCode 215.數(shù)組中第K個(gè)最大元素 
在未排序的數(shù)組中找到第 k 個(gè)最大的元素病附。
請(qǐng)注意,你需要找的是數(shù)組排序后的第 k 個(gè)最大的元素亥鬓,而不是第 k 個(gè)不同的元素完沪。

示例 1:
輸入: [3,2,1,5,6,4] 和 k = 2
輸出: 5

示例 2:
輸入: [3,2,3,1,2,4,5,5,6] 和 k = 4
輸出: 4

使用快速排序思想,將K左右兩邊分區(qū)嵌戈,大的在左邊覆积,小的在右邊

public class FindKthLargest {
    public static int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return Integer.MAX_VALUE;
        }
        return findKthLargest(nums, 0, nums.length - 1, k-1);
    }

    private static int findKthLargest(int[] nums, int start, int end, int k) {
        if (start > end) {
            return Integer.MAX_VALUE;
        }
       
        int pivot = nums[end];
        int left = start;
        for (int i = start; i < end; i++) {
            if (nums[i] > pivot) {
                swap(nums, left++, i);
            }
        }
        swap(nums, left, end);

        if (left == k) {
            return nums[left];
        } else if (left < k) {
            return findKthLargest(nums, left + 1, end, k);
        } else {
            return findKthLargest(nums, start, left - 1, k);
        }

    }

    private static void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

第一次分區(qū)查找,需要對(duì)大小為N的數(shù)組進(jìn)行分區(qū)熟呛,即需要遍歷n個(gè)元素宽档,平均情況下,在第二次分區(qū)查找時(shí)惰拱,需要對(duì)大小為\frac{N}{2}的數(shù)組進(jìn)行分區(qū)雌贱,以此類推,分區(qū)遍歷元素的個(gè)數(shù)分別為N偿短、\frac{N}{2}\frac{N}{4}……1馋没,我們知道等比數(shù)列求和公式:
S_n=\frac{a_1(1-q^n)}{1-q}
N=2^k昔逗,那么:
N+\frac{N}{2}+\frac{N}{4}+……+1
=2^k+\frac{2^k}{2}+\frac{2^k}{4}+……+1
=2^k+2^{k-1}+2^{k-2}+……+1
=2^{k+1}-1=2N-1
所以該方法的時(shí)間復(fù)雜度為O(2N-1)=O(N)

總結(jié)

排序算法 空間復(fù)雜度 穩(wěn)定性 最好時(shí)間復(fù)雜度 最壞時(shí)間復(fù)雜度 平均時(shí)間復(fù)雜度
歸并 O(N) O(NlogN) O(NlogN) O(NlogN)
快速 O(logN)~O(N) × O(NlogN) O(N^2) O(NlogN)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末篷朵,一起剝皮案震驚了整個(gè)濱河市勾怒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌声旺,老刑警劉巖笔链,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腮猖,居然都是意外死亡鉴扫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門澈缺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坪创,“玉大人炕婶,你說我怎么就攤上這事±吃ぃ” “怎么了柠掂?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長依沮。 經(jīng)常有香客問我涯贞,道長,這世上最難降的妖魔是什么危喉? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任肩狂,我火速辦了婚禮,結(jié)果婚禮上姥饰,老公的妹妹穿的比我還像新娘傻谁。我一直安慰自己,他們只是感情好列粪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布审磁。 她就那樣靜靜地躺著,像睡著了一般岂座。 火紅的嫁衣襯著肌膚如雪态蒂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天费什,我揣著相機(jī)與錄音钾恢,去河邊找鬼。 笑死鸳址,一個(gè)胖子當(dāng)著我的面吹牛瘩蚪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播稿黍,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼疹瘦,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了巡球?” 一聲冷哼從身側(cè)響起言沐,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酣栈,沒想到半個(gè)月后险胰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矿筝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年起便,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缨睡,死狀恐怖鸟悴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奖年,我是刑警寧澤细诸,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站陋守,受9級(jí)特大地震影響震贵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜水评,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一猩系、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧中燥,春花似錦寇甸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至咱扣,卻和暖如春绽淘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闹伪。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工沪铭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人偏瓤。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓杀怠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親硼补。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驮肉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 簡單排序 插入排序 想象一下插隊(duì)的過程... 一個(gè)人通過插隊(duì)的方式排到前面褪储,而將原本在他前面的人擠到了后面的位置。...
    Kasheem_Lew閱讀 1,502評(píng)論 0 4
  • 概述 排序有內(nèi)部排序和外部排序慧域,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序鲤竹,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    蟻前閱讀 5,168評(píng)論 0 52
  • 穩(wěn)定:如果a原本在b前面,而a=b辛藻,排序之后a仍然在b的前面碘橘;不穩(wěn)定:如果a原本在b的前面,而a=b吱肌,排序之后a可...
    意識(shí)流丶閱讀 3,153評(píng)論 2 9
  • 綾跑起來痘拆,四周靜悄悄,空無一人的走廊蕩起她的腳步聲氮墨。 再也出不去了吧纺蛆,她心想。 凌晨12點(diǎn)规揪,教學(xué)樓里異常安靜桥氏,只聽...
    草莓小甜餅閱讀 408評(píng)論 0 0
  • 只愿歲月太深沉閱讀 115評(píng)論 0 1