數據結構和算法(二):遞歸、排序、通用排序算法

從廣義上來講:數據結構就是一組數據的存儲結構 , 算法就是操作數據的方法

數據結構是為算法服務的,算法是要作用在特定的數據結構上的。

10個最常用的數據結構:數組、鏈表、棧、隊列、散列表、二叉樹、堆、跳表、圖、Trie樹

10個最常用的算法:遞歸、排序、二分查找、搜索、哈希算法、貪心算法、分治算法、回溯算法、動態規劃、字符串匹配算法

本文總結了20個最常用的數據結構和算法,不管是應付面試還是工作需要,只要集中精力攻克這20個知識點就足夠了。

數據結構和算法(一):復雜度、數組、鏈表、棧、隊列的傳送門

數據結構和算法(二):遞歸、排序、通用排序算法的傳送門

數據結構和算法(三):二分查找、跳表、散列表、哈希算法的傳送門

數據結構和算法(四):二叉樹、紅黑樹、遞歸樹、堆和堆排序、堆的應用的傳送門

數據結構和算法(五):圖、深度優先搜索和廣度優先搜索、字符串匹配算法、Trie樹、AC自動機的傳送門

數據結構和算法(六):貪心算法、分治算法、回溯算法、動態規劃、拓撲排序的傳送門

第六章 遞歸

一、如何理解遞歸?
    1. 遞歸是一種應用非常廣泛的算法,之后我們要講的很多數據結構和算法的編碼實現都要用到遞歸,例如DFS深度優先搜索、前中后序二叉樹遍歷等等,所以搞懂遞歸非常重要。
    1. 一個例子幫你搞懂遞歸:周末帶女朋友去看電影,女朋友問你咱們現在是第幾排?電影院里太黑沒法數,你該怎么辦?這個時候遞歸就派上用場了,于是,你問前邊一排的人他是第幾排,你想只要在他的數字上加1,就可以知道自己是第幾排,但是前邊的人也看不清啊,所以他也問前邊的人,就這樣一排一排的往前傳,直到問到第一排,告訴說我是第一排,然后再這樣一排一排的把數字傳遞回來,這樣你就知道答案了。
    1. 這就是一個標準的遞歸求解問題分解過程,去的過程叫做“遞”,回來的過程叫做“歸”。解決遞歸問題,需要寫出遞推公示,剛才這個問題的遞推公示是這樣的:
剛才這個問題的遞推公示:
f(n) = f(n - 1) + 1 ,   其中f(1) = 1  //f(n)表示你想知道自己在哪一排,f(n-1)代表你前邊的一排的排數
    1. 有了遞推公示,我們很方便可以寫出遞歸代碼
func f(_ n: NSInteger) -> NSInteger{
    if n == 1 {
        return 1
    }
    return f(n - 1) + 1
}
二、什么情況下使用遞歸?

滿足以下三個條件的問題,就可以使用遞歸的方法求解

    1. 一個問題可以拆解成幾個子問題的解
    1. 這個問題和之后的子問題,除了數據規模以外,求解思路完全相同
    1. 存在遞歸終止條件
三、如何編寫遞歸代碼?

寫出遞歸代碼的關鍵在于:

    1. 寫出遞推公式
    1. 找出終止條件

總結一下:寫出遞歸代碼的關鍵在于找到將大問題分解成小問題的規律,并基于此寫出遞推公式,找出終止條件,最后將遞推公式和終止條件翻譯成實際代碼。

注意!! : 不要試圖用人腦去分解遞歸的每一個步驟,只需要搞清一層關系并保證后續規律一致即可。

四、寫遞歸代碼要注意的地方
    1. 遞歸代碼要警惕堆棧溢出,講第四章“棧”的時候講過,函數的調用是基于函數調用棧的,函數調用棧會保存函數的臨時變量,直到函數調用完畢。一旦遞歸調用層次很深,一直壓入棧,就會有堆棧溢出的風險。
    1. 遞歸代碼要警惕重復運算,如下圖,我們把看電影數排數的問題分解一下,就會發現其中f(3)被重復計算了很多次,這就是重復計算問題。
      重復計算問題

    為了避免重復計算,我們可以通過一個數據結構(例如散列表)來保存已經求解過的f(k),當遞歸調用f(k)時,先看下是否已經求解過了,如果是,直接從散列表中取值返回,不需要重復在計算了。

    1. 遞歸代碼要警惕存在環而導致無限遞歸的問題,例如A依賴B,B依賴C,C又依賴A,一旦存在這種環,就會出現無限遞歸的問題,可以用限制遞歸深度來解決,不過還有更高級的方法,后續再說。

第七章 排序基礎知識 + 復雜度為O(n*n)的排序算法

一、如何衡量一個排序算法的好壞:
    1. 最好、最壞、平均時間復雜度,及最好、最好時間復雜度的原始數據 (針對小規模數據排序時,如果是同階時間復雜度,應該把系數、常數、低階也考慮進來)
    1. 空間復雜度 (空間復雜度為1的叫做原地排序)
    1. 穩定性 (排序的穩定性是指相等的對象,在排序之后,順序保持不變)
    1. 對基于比較的排序算法,應該把比較次數和交換次數也考慮進去
二、有序度、逆序度、滿有序度
    1. 有序度就是有順序關系元素對個數,例如:1、3、4的有序度就是3,(1,3)、(1,4)、(3,4)
    1. 滿有序度就是指完全有序的數組,例如:1、2、3、4, 滿有序度 = n * (n-1) / 2
    1. 逆序度就是有逆序關系的元素個數,逆序度 = 滿有序度 - 有序度
    1. 我們排序的過程就是增加有序度減少逆序度的過程,最后達到滿有序度就說明排序完成了。(交換次數就是逆序度)
三、冒泡排序
    1. 原理:從第一個開始,依次比較相鄰元素的大小然后進行交換操作,把大的往后交換,直到沒有交換操作為止。
    1. 最好時間復雜度:O(n),當數組剛好是順序的時候,只需要挨個比較一遍就行了,不需要做交換操作,所以時間復雜度為O(n)
    1. 最壞時間復雜度:O(n * n),當數組剛好是完全逆序的時候,需要挨個比較,并且重復n次,所以時間復雜度為O(n*n)
    1. 平均時間復雜度:O(n * n),當數組是一半的滿有序度n * (n-1)/4時,進行計算的話,交換次數就是n * (n-1)/4,比較次數肯定比交換次數多,所以得出來的不準確的平均時間復雜度為O(n * n)
    1. 冒泡的穩定性:元素相同時不做交換,所以冒泡是穩定的排序算法
四、插入排序
    1. 原理: 選取未排序的元素,插入到已排序區間的合適位置,止到未排序區間為空。
    1. 最好時間復雜度: O(n),當數組剛好是完全順序時,每次只用比較一次就能找到正確的位置,重復n次,就可以清空未排序區間,所以最好時間復雜度為O(n)
    1. 最壞時間復雜度:O(n * n),當數組剛好是完全逆序時,每次都要比較n次才能找到正確位置,重復n次,才能清空未排序區間,所以最壞時間復雜度為O(n * n)
    1. 平均時間復雜度:O(n * n),因為往數組中插入一個元素的平均時間復雜度為O(n),而插入排序可以理解為重復n次的數組插入操作,所以平均時間復雜度為O(n * n)
    1. 插入的穩定性:未出現的元素總會插入到已排序元素的前邊,所以插入排序是穩定的排序算法。
五、插入排序為何比冒泡排序更優?
    1. 相同點:插入排序和冒泡排序的平均時間復雜度都是O(n*n),都是穩定的排序算法,都是原地排序,元素交換的次數都是逆序度。
    1. 插入比冒泡的優勢:冒泡的交換操作需要三個賦值操作,而插入只需要一步賦值操作,而且插入排序還有很大的優化空間,所以插入更優選一點。

第八章 時間復雜度為O(nlogn)的排序算法

一、歸并排序
    1. 原理:將數組分成前后兩部分,對這兩部分分別進行排序,將排序好的兩部分再合并在一起,這樣整個數組就有序了。
    1. 方法:使用分治思想,將大問題可以拆解成子問題,子問題的解決思路與大問題一樣,所以可以使用遞歸解決,需要寫出遞推公式,并找到終止條件
    1. 遞推公式:merge_sort(p…r) = merge(merge_sort(p...q),merge_sort(q+1,r)) , p是數組的最小索引值,q是數組的中間索引值,r是數組的最大索引值
    1. 終止條件:p>=r時不在繼續分解,也就是分解到只剩下一個的時候就不分解了
    1. 最好、最壞、平均時間復雜度都是:O(nlogn),T(n) = 2T(n/2) + n = O(n*logn)(因為合并兩個有序數組的時間復雜度是O(n),所以需要加上n)
    1. 空間復雜度:每次合并操作都需要開辟臨時內存空間,所以空間復雜度為O(n),不是原地排序
    1. 歸并的穩定性:因為合并的時候相同元素的前后順序不變,所以歸并是穩定的排序算法
二、快速排序
    1. 原理:選取數組中任意一個數據作為pivot分區點,將小于它的放在它的左側,大于它的放在它的右側,利用分治思想,繼續分別對左右兩側進行同樣的操作,直至區間縮小為1。
    1. 方法:使用分治思想,遞歸的解決問題,需要寫出遞推公示,找出終止條件
    1. 遞歸公示: quick_sort(p...r) = quick_sort(p...q-1) + quick_sort(q+1,r),q是分區點,p到q-1的是小于q的,q+1到r時大于q的
    1. 終止條件:p>=r,也就是區間中只剩一個元素時終止
    1. 最好時間復雜度:如果每次選取分區點時,都能把數組等分成兩個,此時的時間復雜度和歸并一樣,都是O(n*logn)
    1. 最壞時間復雜度:如果每次分區都是不均等的,那么就需要n次分區操作,每次分區平均掃描n/2個元素,此時時間復雜度就退化為O(n*n)了
    1. 平均時間復雜度:大部分情況下的時間復雜度都是O(n*logn)
    1. 空間復雜度:使用交換法,使空間復雜度降低為O(1)
    1. 快排的穩定性:因為分區過程涉及交換操作,所以快排是不穩定的排序算法
三、如何在O(n)時間復雜度內,找出無序數組中的第K大元素
    1. 選取數組區間A[0...n-1]的最后一個元素A[n-1]作為分區點,進行原地分區(也就是利用快排的思想,小的放左邊,大的放右邊,組合在一起行程一個新的數組),數組就變成了三部分A[0...p-1]、A[p]、A[p+1...n-1]
    1. 如果p+1=K,那A[p]就是第K大元素
    1. 如果K>p+1,那么第K大元素就在A[p+1]到A[n-1]區間中,利用遞歸方法,在用1中的方法把A[p+1]到A[n-1]進行原地分區,
    1. 如果K<p+1,那么第K大元素就在A[0]到A[p-1]區間中,與3中的同理
    1. 時間復雜度:第一次分區,需要遍歷N個元素,第二次分區需要遍歷N/2個元素,第三次分區需要遍歷N/4,知道區間縮小為1,總共需要遍歷N+N/2+N/4+N/8+....+1=2N-1個元素,所以時間復雜度為O(2n-1),忽略常量之后,就是O(n)
    1. 空間復雜度:原理與快排一樣,所以空間復雜度為O(1)

第九章 時間復雜度為O(n)的排序算法

一、線性排序
    1. 線性排序算法包括:桶排序、計數排序、基數排序
    1. 線性排序由于不涉及元素間的比較,所以能做到線性時間復雜度O(n)
    1. 對要排序的數據要求很苛刻
二、桶排序
    1. 原理:將數據分到若干個有序的桶里,每個桶里單獨排序,之后再將每個桶里的數據順序取出,組成的序列就是有序的了。(分桶,每個桶內快排)
    1. 對數據的要求:首先,數據必須很容易就分成若干個桶,并且桶和桶之間存在天然的大小順序;其次,數據在各個桶的分布是比較均勻的。
    1. 時間復雜度:最好為O(n),最壞為O(nlogn),取決于桶的劃分,如果桶適量且數據分布均勻則為O(n);如果數據全在一個桶里,然后桶內部排序用了快排,則為O(nlogn)
    1. 空間復雜度:O(n/m),n為數據規模,m為桶的數量
    1. 穩定性:如果每個桶內部都用歸并的話,是穩定的,如果用快排的話,就是不穩定的
    1. 適用場景:例如:外部排序,也就是數據存儲在外部磁盤,且數據量較大,而內存有限,無法將數據全部加載到內存中。
三、計數排序
    1. 原理:計數排序其實就是特殊的桶排序,劃分若干個桶,讓每個桶內的數值都是相同的,省去桶內排序的時間 (按值分桶,每個桶內值相同,通過統計計數,實現排序)
    1. 對數據的要求:只能用在數據范圍不大的場景中,并且數據范圍要與數據規模相差不大,并且只能給非負整數排序
    1. 時間復雜度:O(n)
    1. 空間復雜度:O(n)
    1. 穩定性:穩定
四、基數排序
    1. 原理:按照位進行排序,從后往前一位一位的排序 (在每一位上桶排序)。例如對10萬個手機號進行排序,先按照最后一位來排序手機號,然后再按照倒數第二位排序手機號,以此類推,最后按照第一位排序手機號。
    1. 對數據的要求:數據必須可以分出獨立的位來,并且位之間有遞進關系,高位大則低位就不用比較了,并且每一位的數據范圍不能太大,要可以用線性算法排序。
    1. 時間復雜度:O(n)
    1. 空間復雜度:O(n)
    1. 穩定性: 穩定

第十章 如何實現一個通用的、高性能的排序函數?

一、選擇合適的排序算法

首先,回顧一下前邊講過的排序算法,如下圖所示:

排序算法
    1. 由于線性排序算法對數據要求比較苛刻,所以做通用的排序函數,不能使用線性排序
    1. 如果對數據規模比較小的數據進行排序,可以選擇時間復雜度為O(n * n)的排序算法
    1. 對數據規模比較大的數據進行排序,就需要選擇O(nlogn)的排序算法了
    1. O(nlogn)的算法有歸并排序、快速排序等。歸并排序的空間復雜度為O(n),也就意味著排序100M的數據,就需要200M的空間,所以歸并不適合;快速排序在平均時間復雜度為O(nlogn),但是如果分區點選擇不好的話,最壞時間復雜度為O(n * n),如果可以對分區點的選取進行優化,快排還是不錯的選擇對象。
二、快排的分區點如何選擇
    1. 三數取中法,我們從首、尾、中間各取一個數,選擇中間值作為分區點。如果要排序的數組比較大,那么三數取中可能就不太夠了,需要“五數取中”或者“十數取中”。
    1. 隨機法,每次從要排序的區間內隨機選擇一個元素作為分區點,從概率學的角度來說,不太可能每次選取的分區點都是很差的,所以平均情況下這么選也是可以的,時間復雜度退化到O(n * n)的概率不大。
三、以Glibc中的qsort()函數為例,具體分析一下工業級的排序函數
    1. 如果你去看源碼,你就會知道qsort()會優先使用歸并排序來排序數據,歸并排序的空間復雜度為O(n),所以對小規模的數據,例如1KB、2KB而言,這個問題不大,現在計算機的內存都挺大的,這就是以空間換時間的一個典型應用。
    1. 但是如果數據量比較大的話,再用歸并就不合適了,這時候,qsort()會改用快速排序來排序,qsort()使用了三數取中法來選擇分區點。
    1. 對于遞歸太深會導致堆棧溢出的問題,qsort()自己實現了一個堆上的棧,用手動模擬遞歸的辦法來解決的。
    1. 實際上,qsort()不僅僅用了歸并和快排,還用到了插入排序,在快速排序過程中,當要排序的區間中的元素個數小于等于4時,qsort()就退化為了插入排序,不再用遞歸做快排了,原因也很簡單,因為在小規模數據面前,O(n * n)時間復雜度的算法不一定比O(nlogn)的算法執行的時間長
    1. 我們在說復雜度分析的時候說過,算法的性能可以用時間復雜度來分析,使用O表示法的時候,會省略低階、系數、常數,但是對比小規模數據來說,低階、系數、常數就不能忽略了,也就是說O(nlogn)在沒有忽略這些前,可能是O(klogn+c),k和c可能是一個比較大的數,就可能比O(n^2)大了。
假設k = 1000,c=200時,klogn+c > n^2

knlogn+c = 1000 * 100 * log100 + 200 遠大于 10000

n^2 = 100*100 = 10000

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容