10分鐘詳解:算法面試5大必考排序方式

來源:IT技術(shù)思維原創(chuàng),轉(zhuǎn)載請注明原出處內(nèi)容提供:蘇勇,?Google 資深軟件工程師??

算法學習其實是一種學習和提高思維能力的過程。無論是在面試還是實際的工作和生活中,都會碰見一些從沒遇到過的問題

真正需要熟練掌握的排序算法應該是以下幾種:

1.基本的排序算法:冒泡排序(Bubble Sort)、插入排序(Insertion Sort)

2.??嫉呐判蛩惴?歸并排序(Merge Sort)、快速排序(Quick Sort)、拓撲排序(Topological Sort)

3.其他排序算法:堆排序(Heap Sort)、桶排序(Bucket Sort)

下文將用10min結(jié)合例題和代碼針對排序算法進行探討。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?冒泡排序

1、基本思想

給定一個數(shù)組,把數(shù)組里的元素通通倒入到水池中,這些元素將通過相互之間的比較,按照大小順序一個一個地像氣泡一樣浮出水面。具體的實現(xiàn)方法就是:每一輪,從雜亂無章的數(shù)組頭部開始,每兩個元素比較大小并進行交換,直到這一輪當中最大或最小的元素被放置在數(shù)組的尾部,然后不斷地重復這個過程,直到所有元素都排好位置。元素相互比較的過程就是冒泡排序的核心操作。

01例題分析

給定數(shù)組 [2, 1, 7, 9, 5, 8],要求按照從左到右、從小到大的順序進行排序。

解題思路:可以從左到右依次冒泡,把較大的數(shù)往右邊挪動即可。具體操作如動圖:

數(shù)組已經(jīng)排好序后。可以用算法在進行新一輪的比較中,判斷一下在上一輪比較的過程中有沒有發(fā)生兩兩交換,如果一次交換都沒有發(fā)生,就證明其實數(shù)組已經(jīng)排好序了。

2.代碼示例

冒泡排序的代碼如下:

a.首先我們定義了一個布爾變量hasChange,用來標記每輪遍歷中是否發(fā)生了交換。

b.在每輪遍歷開始的時候,將hasChange設(shè)置為false。

c.接下來,進行兩兩比較,如果發(fā)現(xiàn)當前的數(shù)比下一個數(shù)還大,那么就交換這兩個數(shù),同時記錄一下有交換發(fā)生。

d.不斷地兩兩交換,直到在這一輪,把最大的數(shù)放置到數(shù)組的最末端。

3、算法分析

冒泡排序算法的空間和時間復雜度。假設(shè)數(shù)組的元素個數(shù)是n,由于在整個排序的過程中,是直接在給定的數(shù)組里面進行元素的兩兩交換,空間復雜度是O(1)。至于時間復雜度,有以下幾種情況:

a.給定的數(shù)組按照順序已經(jīng)排好

在這種情況下,只需要進行n - 1次的比較,兩兩交換次數(shù)為0,時間復雜度是O(n),這是最好的情況。

b.給定的數(shù)組按照逆序排列

在這種情況下,需要進行n(n - 1) / 2次比較,時間復雜度是O(n^2),這是最壞的情況。

c.給定的數(shù)組雜亂無章

在這種情況下,平均時間復雜度是O(n^2)

由此可見,冒泡排序的時間復雜度是O(n^2),但它是一種穩(wěn)定的排序算法,所謂穩(wěn)定,也就是說如果數(shù)組里兩個相等的數(shù),那么排序前后這兩個相等的數(shù)的相對位置保持不變。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 插入排序

1、算法思想

在冒泡排序中,經(jīng)過每一輪的排序處理后,數(shù)組后端的數(shù)是排好序的;而對于插入排序來說,經(jīng)過每一輪的排序處理后,數(shù)組前端的數(shù)都是排好序的。

插入排序的基本思想就是不斷地將尚未排好序的數(shù)插入到已經(jīng)排好序的部分。

02例題分析

對數(shù)組 [2, 1, 7, 9, 5, 8] 進行插入排序。

解題思路:將數(shù)組分成左右兩個部分,左邊是已經(jīng)排好序的部分,右邊是還沒有排好序的部分,剛開始,左邊已排好序的部分只有第一個元素2。接下來,我們對右邊的元素一個一個進行處理,將它們放到左邊。具體操作如下動圖:



2、代碼示例

a.首先,我們將數(shù)組的第一個元素看成是已經(jīng)排好序的部分,于是我們從第二個元素開始,即i從1開始遍歷數(shù)組。

b.每次在外圍循環(huán)開始的時候,把當前i 指向的值用current保存起來,以便將來把它插入到合適的位置。

c.接下來,利用指針j進行內(nèi)循環(huán),不斷地拿它和current值進行比較,如果發(fā)現(xiàn)j所指向的值比current值大,那么就把這個數(shù)往右移動一位。

d.當內(nèi)循環(huán)結(jié)束后,j + 1所指向的位置就是我們要把current值插入的位置了。

最后不斷地重復,直到所有的數(shù)都排好。

3、算法分析

插入排序算法的空間和時間復雜度分析如下。

假設(shè)數(shù)組的元素個數(shù)是n,由于在整個排序的過程中,直接在給定的數(shù)組里面進行元素的兩兩交換,空間復雜度是O(1)。至于時間復雜度,有以下幾種情況:

a.給定的數(shù)組按照順序已經(jīng)排好

在這種情況下,我們只需要進行n - 1次的比較,兩兩交換次數(shù)為0,時間復雜度是O(n),這是最好的情況。

b.給定的數(shù)組按照逆序排列

在這種情況下,我們需要進行n(n - 1) / 2次比較,時間復雜度是O(n^2),這是最壞的情況。

c.給定的數(shù)組雜亂無章

在這種情況下,平均時間復雜度是O(n^2)

由此可見,和冒泡排序一樣,插入排序的時間復雜度是O(n^2),并且它也是一種穩(wěn)定的排序算法。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?歸并排序

1、算法思想

歸并排序的核心思想是分治,所謂分治,就是把一個復雜的問題分成兩個或多個相同或相似的子問題,然后把子問題分成更小的子問題,直到子問題可以簡單的直接求解,最原問題的解就是子問題解的合并。

可以說,歸并排序?qū)⒎种蔚乃枷塍w現(xiàn)得淋漓盡致。歸并排序的思路就是,一開始先把數(shù)組從中間劃分成兩個子數(shù)組,一直遞歸地把子數(shù)組劃分成更小的子數(shù)組,直到子

數(shù)組里面只有一個元素,這個時候才開始排序,排序的方法就是按照大小順序合并兩個元素,接著依次按照遞歸的返回順序,不斷地合并排好序的子數(shù)組,直到最后

把整個數(shù)組的順序排好。

2、代碼示例

讓我們看看如何寫歸并排序的代碼,首先看看排序的主體函數(shù):

主體函數(shù)非常簡潔:

1.首先判斷是不是只剩下最后一個元素了。

2.接著從中間將數(shù)組分成兩個部分。

3.接下來,分別遞歸地將左右兩半排好序。

4.最后將排好序的左右兩半合并起來。

接下來看看如何實現(xiàn)歸并操作:?

歸并操作很容易理解。

首先復制一份原來的數(shù)組,之所以要這樣做,是因為我們不僅要比較左右兩半的數(shù)據(jù),而且要修改原來的數(shù)組,如果不復制原來的數(shù)組直接進行修改,那么會導致將來的比較出現(xiàn)錯誤。

接下來,定義一個k指針表示從什么位置開始修改原來的數(shù)組,i指針表示左半邊的起始位置,j表示右半邊的起始位置。

在接下來的比較中,一共可能會出現(xiàn)四種情況:

1.左半邊的數(shù)都處理完畢了,只剩下了右半邊的數(shù),我們只需要將右半邊的數(shù)逐個拷貝過去就好。

2.右半邊的數(shù)都處理完畢了,只剩下了左半邊的數(shù),我們只需要將左半邊的數(shù)逐個拷貝過去就好。

3.右邊的數(shù)小于左邊的數(shù),我們將右邊的數(shù)拷貝到合適的位置,j指針往前移動一位。

4.左邊的數(shù)小于右邊的數(shù),我們將左邊的數(shù)拷貝到合適的位置,i指針往前移動一位。

03例題分析

利用歸并排序算法對數(shù)組[2, 1, 7, 9, 5, 8]進行排序。首先不斷地對數(shù)組進行切分,直到各個子數(shù)組里只包含一個元素。演示如下圖:


合并能成功,先決條件必須是兩個子數(shù)組都已經(jīng)分別排好序了。

3、算法分析

歸并算法是一個不斷遞歸的過程,假設(shè)數(shù)組的元素個數(shù)是n,時間復雜度是T(n)的函數(shù)。把這個規(guī)模為n的問題分成兩個規(guī)模分別為n/2的子問題,每個子問題的時間復雜度就是T(n/2),那么兩個子問題的復雜度就是2*T(n/2),當兩個子問題都得到了解決,即兩個子數(shù)組都排好了序,就需要將它們合并,合并的復雜度是多少呢?很簡單,一共有n個元素,每次都要進行最多n-1次的比較,所以合并的復雜度是O(n),由此我們得到了如下的遞歸復雜度公式:

T(n) = 2*T(n/2) + O(n)

怎么解這個公式呢?需要不斷地把一個規(guī)模為n的問題分解成規(guī)模為n/2的問題,一直分解到規(guī)模大小為1,如果n等于2,我們只需要分一次,如果n等于4,則需要分2次,這里的次數(shù)是按照規(guī)模大小的變化分類的:

以此類推,對于規(guī)模為n的問題,一共要進行l(wèi)og(n)層的大小切分。在每一層里,都要進行合并,所涉及到的元素其實就是數(shù)組里的所有元素,因此,每一層的合并復雜度都是O(n),所以整體的復雜度就是O(nlogn)。

對于空間復雜度,由于合并n個元素需要分配一個大小為n的額外數(shù)組,合并完成之后,這個數(shù)組的空間就會被釋放,所以算法的空間復雜度就是O(n)。最后,歸并排序也是穩(wěn)定的排序算法。

歸并算法的思想很重要,其中對兩個有序數(shù)組合并的操作,在很多面試題里都有用到。建議大家一定要把這個算法練熟。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 快速排序

1、算法思想

快速排序也采用了分治的思想,策略就是:把原始的數(shù)組篩選成較小和較大的兩個子數(shù)組,然后遞歸地排序兩個子數(shù)組。

如,現(xiàn)在要把班里的所有同學按照高矮順序排成一排。老師先隨機地挑選了同學A,讓所有其他同學和A比高矮,比A矮的都站在A的左邊,比A高的都站在A的右邊。接下來,老師分別從左邊和右邊的同學里選擇了同學B和C,然后不斷地篩選和排列下去。

在分成較小和較大的兩個子數(shù)組過程中,如何選定一個基準值(也就是同學A、B、C等)尤為關(guān)鍵。下面讓我們通過例題來深入理解快速排序算法。

04例題分析

再對數(shù)組[2, 1, 7, 9,?5, 8]進行排序,按照快速排序的思想

解題思路:首先把數(shù)組篩選成較小和較大的兩個子數(shù)組,隨機從數(shù)組里選取一個數(shù)作為基準值,比如7好了,于是原始的數(shù)組就被分成了如下兩個子數(shù)組:

這里需要注意的是,快速排序是直接在原始數(shù)組里進行各種交換操作,所以當子數(shù)組被分割出來的時候,原始數(shù)組里的排列也被改變了。

接下來,在較小的子數(shù)組里選2作為基準值,在較大的子數(shù)組里選8作為基準值,繼續(xù)分割子數(shù)組。

繼續(xù)將元素個數(shù)大于1的子數(shù)組進行劃分,得到:

可以看到,當所有子數(shù)組里的元素個數(shù)都為1的時候,原始數(shù)組也被排好序了。

2、代碼示例

先是主體函數(shù):


主函數(shù)非常簡潔:

1.首先判斷是否只剩下了一個元素,如果是,就不需要排序了,直接返回。

2.接著,利用partition函數(shù)找到一個隨機的基準點。這個時候,基準點左邊的數(shù)一定都小于基準值,而右邊的數(shù)一定都大于基準值。

3.遞歸地對基準點左半邊和右半邊的數(shù)進行排序。

接下來看如何實現(xiàn)partition函數(shù)獲得基準值,并將數(shù)組劃分好。


1.首先,為了避免最糟糕的情況出現(xiàn),我們隨機地選擇一個數(shù)作為基準值,將它放置到最右邊,也就是hi指針的位置,所以,nums[hi]就是我們的基準值。

2.接著,從左到右不斷地拿每個數(shù)和基準值比較,如果發(fā)現(xiàn)當前的數(shù)比基準值小,就將它放到指針i所指向的位置。循環(huán)完畢后,i指針之前的數(shù)都比基準值小。

3.最后將一開始放置在末尾的基準值放置到指針i的位置,這樣一來,i 指針之后的數(shù)都比基準值大了。

4.返回指針i,作為基準點的位置。

3、算法分析

在解這道例題的過程中,被選出來的基準值都是當前子數(shù)組的中間數(shù),這是快速排序的最佳情況,通過這樣的分割,能保證對于一個規(guī)模大小為n的問題,能被均勻分解成兩個規(guī)模大小為n/2的子問題,回顧一下,歸并排序也采用了相同的劃分方法。如此一來,快速排序在最優(yōu)情況下的算法時間復雜度就是:

T(n) = 2 * T(n/2) + O(n)

這個O(n)如何得出來的呢?在把規(guī)模大小為n的問題分解成n/2的兩個子問題時,需要判斷是不是和基準值進行了n-1次比較。而這n-1次比較的復雜度就是O(n)。很顯然,在最優(yōu)情況下,快速排序的復雜度也是O(nlogn)。

最壞的情況是如果我們每次在選擇基準值的時候,非常不幸運地選擇了子數(shù)組里的最大或者最小值,也就是說每次都把子數(shù)組分成了兩個更小的子數(shù)組,其中一個的長度為1,另外一個的長度只比原子數(shù)組少1。例如,對于數(shù)組來說,每次我們挑選的基準值分別是9,

8, 7, 5, 2的時候,我們的劃分過程就變成了這樣:

這其實和冒泡排序的過程類似了,意味著快速排序在最壞的情況下算法復雜度也變?yōu)榱薕(n^2)??梢酝ㄟ^隨機地選取基準值就可以避免出現(xiàn)最壞的情況了。

空間復雜度,和歸并排序不同,快速排序在每次遞歸的過程中,只需要開辟O(1)的存儲空間來完成交換操作實現(xiàn)直接對數(shù)組的修改,又因為遞歸次數(shù)為logn,所以它的整體空間復雜度完全取決于壓堆棧的次數(shù),因此它的空間復雜度是O(logn)。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 拓撲排序

和前面介紹的幾種排序不同,拓撲排序應用的場合不再是一個簡單的數(shù)組,拓撲是一種研究,它研究的是圖論里面頂點和頂點連線之間的性質(zhì)。拓撲排序就是要將這些頂點按照相連的性質(zhì)進行排序。要能實現(xiàn)拓撲排序,得有幾個前提:

1.首先這個圖必須是有向圖

2.圖里面沒有環(huán)

拓撲排序一般用來理清具有依賴關(guān)系的任務(wù)。舉個最簡單的例子,假設(shè)有三門課程A、B、C,如果想要學習課程C就必須先把課程B學完,要學習課程B,還得先學習課程A,所以得出課程的學習順序應該是A -> B -> C。

1、算法思想

首先將問題用一個有向無環(huán)圖(DAG, Directed Acyclic Graph)進行抽象表達,定義出哪些是圖的頂點,頂點之間如何互相關(guān)聯(lián)。接下來,可以利用廣度優(yōu)先搜索或深度優(yōu)先搜索來進行拓撲排序。

利用廣度優(yōu)先搜索的思想比較直觀易懂,下面我們就以一道簡單的例題來學習如何對一個有向無環(huán)圖進行拓撲排序。

05例題分析


有一個學生想要修完5門課程的學分,這5門課程分別用1、2、3、4、5來表示,現(xiàn)在已知學習這些課程有如下的要求:

1.課程2和4依賴于課程1

2.課程3依賴于課程2和4

3.課程4依賴于課程1和2

4.課程5依賴于課程3和4

那么這個學生應該按照怎樣的順序來學習這5門課程呢?

解題思路:可以把5門課程看成是一個圖里的5個頂點,用有向線段按照它們的相互關(guān)系連起來,這個有向圖里沒有環(huán),無論從哪個頂點出發(fā),都不會再回到那個頂點。并且,這個圖里并沒有孤島的出現(xiàn),因此,我們可以對它進行拓撲排序,具體事例如動圖:

一般來說,一個有向無環(huán)圖可以有一個或多個拓撲排序的序列。

2、代碼示例

在這里我們運用廣度優(yōu)先搜索的方法對這個圖的結(jié)構(gòu)進行遍歷。在構(gòu)建這個圖的過程中,我們用一個鏈接矩陣adj來表示這個圖的結(jié)構(gòu),用一個indegree的數(shù)組統(tǒng)計每個頂點的入度,由于篇幅有限,我們就不把具體的構(gòu)建過程寫出來了。我們這里重點看如何實現(xiàn)拓撲排序。

1.既然是要做廣度優(yōu)先搜索,我們一開始定義一個隊列q。

2.將所有入度為0的頂點加入到隊列q里。

3.不斷地循環(huán),直到隊列為空。

4.在每次循環(huán)中,從隊列中取出頂點,這個就是按照入度數(shù)目排序中最小的那個頂點。

5.然后將跟這個頂點相連的其他頂點的入度減一,如果發(fā)現(xiàn)那個頂點的入度變成了0,將其加入到隊列的末尾,等待將來的處理。

3、算法分析

對于拓撲排序,統(tǒng)計頂點的入度需要O(n)的時間,接下來每個頂點被遍歷一次,同樣需要O(n)的時間,所以拓撲排序的時間復雜度是O(n)。

建議你利用深度優(yōu)先搜索的方法對這道題實現(xiàn)拓撲排序。


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 結(jié)語

在這些排序算法中,歸并排序和快速排序是最重要的知識點,除了要好好理解它們的思路,還必須要能寫出沒有bug的代碼才行,因此要在課后多加練習呀。LeeCode(力扣)里面有很多關(guān)于拓撲排序的經(jīng)典題目,強烈建議大家去試試。


內(nèi)容摘取自《300分鐘搞定算法面試》第03講:15分鐘搞懂排序主講人:蘇勇,谷歌資深技術(shù)工程師

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 查找和排序算法是算法的入門知識,其經(jīng)典思想可以用于很多算法當中。因為其實現(xiàn)代碼較短,應用較常見。所以在面試中...
    寶塔山上的貓閱讀 1,151評論 1 21
  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    蟻前閱讀 5,298評論 0 52
  • 搞懂基本排序算法 上篇文章寫了關(guān)于 Java 內(nèi)部類的基本知識,感興趣的朋友可以去看一下:搞懂 JAVA 內(nèi)部類;...
    醒著的碼者閱讀 1,310評論 3 4
  • 穩(wěn)定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;不穩(wěn)定:如果a原本在b的前面,而a=b,排序之后a可...
    意識流丶閱讀 3,298評論 2 9
  • 我們認識多久了? 我搖搖頭,誰會記住這種日子? 300天,也是我喜歡你的第173天。你不知道吧,在認識你后,我睡覺...
    我想吃你碗里的雞腿閱讀 256評論 0 0

友情鏈接更多精彩內(nèi)容