
一 前言
1 為什麼要學習算法和資料結構?
- 解決特定問題。
- 深度優化程式性能的基礎。
- 學習一種思想:如何把現實問題轉化為計算機語言表示。
2 業務開發要掌握到程度?
- 了解常見資料結構和算法,溝通沒有障礙。
- 活學活用:遇到問題時知道要用什麼資料結構和算法去優化。
二 資料結構基礎
1 什麼是資料結構?
資料結構是資料的組織、管理和存儲格式,其使用目的是為了高效的通路和修改資料。
資料結構是算法的基石。如果把算法比喻成美麗靈動的舞者,那麼資料結構就是舞者腳下廣闊而堅實的舞台。
2 實體結構和邏輯結構的差別?
實體結構就像人的血肉和骨骼,看得見,摸得着,實實在在,如數組、連結清單。
邏輯結構就像人的思想和精神,它們看不見、摸不着,如隊列、棧、樹、圖。
3 線性存儲結構和非線性存儲結構的差別?
- 線性:元素之間的關系是一對一的,如棧、隊列。
- 非線性:每個元素可能連接配接0或多個元素,如樹、圖。
三 算法基礎
1 什麼是算法?
- 數學:算法是用于解決某一類問題的公式和思想。
- 計算機:一系列程式指令,用于解決特定的運算和邏輯問題。
2 如何衡量算法好壞?
- 時間複雜度:運作時間長短。
- 空間複雜度:占用記憶體大小。
3 怎麼計算時間複雜度?
大O表示法(漸進時間複雜度):把程式的相對執行時間函數T(n)簡化為一個數量級,這個數量級可以是n、n^2、logN等。
推導時間複雜度的幾個原則:
- 如果運作時間是常數量級,則用常數1表示。
- 隻保留時間函數中的最高階項。
- 如果最高階項存在,則省去最高項前面的系數。
時間複雜度對比:O(1) > O(logn) > O(n) > O(nlogn) > O(n^2)。
不同時間複雜度算法運作次數對比:
4 怎麼計算空間複雜度?
常量空間 O(1):存儲空間大小固定,和輸入規模沒有直接的關系。
線性空間 O(n):配置設定的空間是一個線性的集合,并且集合大小和輸入規模n成正比。
二維空間 O(n^2):配置設定的空間是一個二維數組集合,并且集合的長度和寬度都與輸入規模n成正比。
遞歸空間 O(logn):遞歸是一個比較特殊的場景。雖然遞歸代碼中并沒有顯式的聲明變量或集合,但是計算機在執行程式時,會專門配置設定一塊記憶體空間,用來存儲“方法調用棧”。執行遞歸操作所需要的記憶體空間和遞歸的深度成正比。
5 如何定義算法穩定性?
穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。
不穩定:如果a原本在b的前面,而a=b,排序之後 a 可能會出現在 b 的後面。
6 有哪些常見算法?
首先要明确:特定算法解決特定問題。
- 字元串:暴力比對、BM、KMP、Trie等。
- 查找:二分查找、周遊查找等。
- 排序:冒泡排序、快排、計數排序、堆排序等。
- 搜尋:TFIDF、PageRank等。
- 聚類分析:期望最大化、k-meanings、k-數位等。
- 深度學習:深度信念網絡、深度卷積神經網絡、生成式對抗等。
- 異常檢測:k最近鄰、局部異常因子等。
- ......
其中,字元串、查找、排序算法是最基礎的算法。
四 常見資料結構
1 數組
1)什麼是數組?
資料是有限個相同類型的變量所組成的有序集合。數組中的每一個變量被稱為元素。
2)數組的基本操作?
讀取O(1)、更新O(1)、插入O(n)、删除O(n)、擴容O(n)。
2 連結清單
1)什麼是連結清單?
連結清單是一種在實體上非連續、非順序的資料結構,由若幹個節點組成。
單向連結清單的每一個節點又包含兩部分,一部分是存放資料的變量data,另一部分是指向下一個節點的指針next。
2)連結清單的基本操作?
讀取O(n)、更新O(1)、插入O(1)、删除O(1)。
3)連結清單 VS 數組
數組:适合多讀、插入删除少的場景。
連結清單:适用于插入删除多、讀少的場景。
3 棧
1)什麼是棧?
棧是一種線性邏輯資料結構,棧的元素隻能後進先出。最早進入的元素存放的位置叫做棧底,最後進入的元素存放的位置叫棧頂。
一個比喻,棧是一個一端封閉一端的開放的中空管子,隊列是兩端開放的中空管子。
2)如何實作棧?
數組實作:
連結清單實作:
3)棧的基本操作
入棧O(1)、出棧O(1)。
4)棧的應用?
- 回溯曆史,比如方法調用棧。
- 頁面面包屑導航。
4 隊列
1)什麼是隊列?
一種線性邏輯資料結構,隊列的元素隻能後進後出。隊列的出口端叫做隊頭,隊列的入口端叫做隊尾。
2)如何實作隊列?
3)隊列的基本操作?
入隊 O(1)、出隊 O(1)。
4)隊列的應用
- 消息隊列
- 多線程的等待隊列
- 網絡爬蟲的待爬URL隊列
5 哈希表
1)什麼是哈希表?
一種邏輯資料結構,提供了鍵(key)和值(value)的映射關系。
2)哈希表的基本操作?
寫入:O(1)、讀取:O(1)、擴容O(n)。
3)什麼是哈希函數?
哈希表本質上是一個數組,隻是數組隻能根據下标,像a[0] a[1] a[2] a[3] 這樣來通路,而哈希表的key則是以字元串類型為主的。
通過哈希函數,我們可以把字元串或其他類型的key,轉化成數組的下标index。
如給出一個長度為8的數組,則:
當key=001121時,
index = HashCode ("001121") % Array.length = 7
當key=this時,
index = HashCode ("this") % Array.length = 6
4)什麼是哈希沖突?
不同的key通過哈希函數獲得的下标有可能是相同的,例如002936這個key對應的數組下标是2,002947對應的數組下标也是2,這種情況就是哈希沖突。
5)如何解決哈希沖突?
開放尋址法:例子Threadlocal。
連結清單法:例子Hashmap。
6 樹
1)什麼是樹?
樹(tree)是n(n≥0)個節點的有限集。
當n=0時,稱為空樹。在任意一個非空樹中,有如下特點:
- 有且僅有一個特定的稱為根的節點。
- 當n>1時,其餘節點可分為m(m>0)個互不相交的有限集,每一個集合本身又是一個樹,并稱為根的子樹。
2)樹的周遊?
(1)深度優先
前序:根節點、左子樹、右子樹。
中序:左子樹、根節點、右子樹。
後序:左子樹、右子樹、根節點。
實作方式:遞歸或棧。
(2)廣度優先
層序:一層一層周遊。
實作方式:隊列。
7 二叉樹
1)什麼是二叉樹?
二叉樹(binary tree)是樹的一種特殊形式。二叉,顧名思義,這種樹的每個節點最多有2個孩子節點。注意,這裡是最多有2個,也可能隻有1個,或者沒有孩子節點。
2)什麼是滿二叉樹?
一個二叉樹的所有非葉子節點都存在左右孩子,并且所有葉子節點都在同一層級上,那麼這個樹就是滿二叉樹。
3)什麼是完全二叉樹?
對一個有n個節點的二叉樹,按層級順序編号,則所有節點的編号為從1到n。如果這個樹所有節點和同樣深度的滿二叉樹的編号為從1到n的節點位置相同,則這個二叉樹為完全二叉樹。
8 二叉查找樹
1)什麼是二叉查找樹?
二叉查找樹在二叉樹的基礎上增加了以下幾個條件:
- 如果左子樹不為空,則左子樹上所有節點的值均小于根節點的值。
- 如果右子樹不為空,則右子樹上所有節點的值均大于根節點的值。
- 左、右子樹也都是二叉查找樹。
2)二叉查找樹的作用?
- 查找==》二分查找。
- 排序==》中序周遊。
3)二叉樹的實作方式?
- 連結清單。
- 數組:對于稀疏二叉樹來說,數組表示法是非常浪費空間的。
9 二叉堆
1)什麼是二叉堆?
二叉堆是一種特殊的完全二叉樹,它分為兩個類型:最大堆和最小堆。
- 最大堆的任何一個父節點的值,都大于或等于它左、右孩子節點的值。
- 最小堆的任何一個父節點的值,都小于或等于它左、右孩子節點的值。
2)二叉堆的基本操作?
(1)插入:插入最末,節點上浮。
(2)删除:删除頭節點,尾節點放到頭部,再下沉。
(3)建構二叉堆:二叉樹==》二叉堆,所有非葉子節點依次下沉。
3)二叉堆的實作方式?
數組:
五 常見排序算法
1 十大經典排序算法
2 冒泡排序
1)算法描述
冒泡排序是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,如果它們的順序錯誤就把它們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。
2)實作步驟
- 比較相鄰的元素。如果第一個比第二個大,就交換它們兩個。
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數。
- 針對所有的元素重複以上的步驟,除了最後一個。
- 重複步驟1~3,直到排序完成。
3)優缺點
- 優點:實作和了解簡單。
- 缺點:時間複雜度是O(n^2),排序元素多時效率比較低。
4)适用範圍
資料已經基本有序,且資料量較小的場景。
5)場景優化
(1)已經有序了還再繼續冒泡問題
- 本輪排序中,元素沒有交換,則isSorted為true,直接跳出大循環,避免後續無意義的重複。
(2)部分已經有序了,下一輪的時候但還是會被周遊
- 記錄有序和無序資料的邊界,有序的部分在下一輪就不用周遊了。
(3)隻有一個元素不對,但需要走完全部輪排序
- 雞尾酒排序:元素的比較和交換是雙向的,就像搖晃雞尾酒一樣。
3 歸并排序
歸并排序是建立在歸并操作上的一種有效的排序算法。該算法是采用分治法的一個非常典型的應用。遞歸的把目前序列分割成兩半(分割),在保持元素順序的同時将上一步得到的子序列內建到一起(歸并),最終形成一個有序數列。
圖源:
https://www.cnblogs.com/chengxiao/p/6194356.html- 把長度為n的輸入序列分成兩個長度為n/2的子序列。
- 對這兩個子序列分别采用歸并排序。
- 将兩個排序好的子序列合并成一個最終的排序序列。
優點:
- 性能好且穩定,時間複雜度為O(nlogn) 。
- 穩定排序,适用場景更多。
缺點:
- 非原地排序,空間複雜度高。
大資料量且期望要求排序穩定的場景。
4 快速排序
快速排序使用分治法政策來把一個序列分為較小和較大的2個子序列,然後遞歸地排序兩個子序列,以達到整個數列最終有序。
- 從數列中挑出一個元素,稱為 “基準值”(pivot)。
- 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區退出之後,該基準就處于數列的中間位置。這個稱為分區(partition)操作。
- 遞歸地對【小于基準值元素的子數列】和【大于基準值元素的子數列】進行排序。
- 性能較好,時間複雜度最好為O(nlogn),大多數場景性能都接近最優。
- 原地排序,時間複雜度優于歸并排序。
- 部分場景,排序性能最差為O(n^2)。
- 不穩定排序。
大資料量且不要求排序穩定的場景。
(1)每次的基準元素都選中最大或最小元素
- 随機選擇基準元素,而不是選擇第一個元素。
- 三數取中法,随機選擇三個數,取中間數為基準元素。
(2)數列含有大量重複資料
- 大于、小于、等于基準值。
(3)快排的性能優化
- 雙軸快排:2個基準數,例子:Arrays.sort() 。
5 堆排序
堆排序(Heapsort)是指利用堆這種資料結構所設計的一種排序算法。堆積是一個近似完全二叉樹的結構,并同時滿足堆積的性質:即子結點的鍵值或索引總是小于(或者大于)它的父節點。
- 将初始待排序關鍵字序列(R1,R2….Rn)建構成最大堆,此堆為初始的無序區。
- 将堆頂元素R[1]與最後一個元素R[n]交換,此時得到新的無序區(R1,R2,……Rn-1)和新的有序區(Rn),且滿足R[1,2…n-1]<=R[n]。
- 由于交換後新的堆頂R[1]可能違反堆的性質,是以需要對目前無序區(R1,R2,……Rn-1)調整為新堆,然後再次将R[1]與無序區最後一個元素交換,得到新的無序區(R1,R2….Rn-2)和新的有序區(Rn-1,Rn)。不斷重複此過程直到有序區的元素個數為n-1,則整個排序過程完成。
- 性能較好,時間複雜度為O(nlogn)。
- 時間複雜度比較穩定。
- 輔助空間複雜度為O(1)。
- 資料變動的情況下,堆的維護成本較高。
資料量大且資料呈流式輸入的場景。
5)為什麼實際情況快排比堆排快?
堆排序的過程可知,建立最大堆後,會将堆頂的元素和最後一個元素對調,然後讓那最後一個元素從頂上往下沉到恰當的位置,因為底部的元素一定是比較小的,下沉的過程中會進行大量的近乎無效的比較。是以堆排雖然和快排一樣複雜度都是O(NlogN),但堆排複雜度的常系數更大。
6 計數排序
計數排序不是基于比較的排序算法,其核心在于将輸入的資料值轉化為鍵存儲在額外開辟的數組空間中。作為一種線性時間複雜度的排序,計數排序要求輸入的資料必須是有确定範圍的整數。
- 找出待排序的數組中最大元素。
- 建構一個數組C,長度為最大元素值+1。
- 周遊無序的随機數列,每一個整數按照其值對号入座,對應數組下标的值加1。
- 周遊數組C,輸出數組元素的下标值,元素的值是幾就輸出幾次。
- 性能完爆比較排序,時間複雜度為O(n+k),k為數列最大值。
- 穩定排序。
- 适用範圍比較狹窄。
數列元素是整數,當k不是很大且序列比較集中時适用。
(1)數字不是從0開始,會存在空間浪費的問題
- 數列的最小值作為偏移量,以數列最大值-最小值+1作為統計數組的長度。
7 桶排序
桶排序是計數排序的更新版。它利用了函數的映射關系,高效與否的關鍵就在于這個映射函數的确定。實作原理:假設輸入資料服從均勻分布,将資料分到有限數量的桶裡,每個桶再分别排序(有可能再使用别的排序算法或是以遞歸方式繼續使用桶排序進行排序)。
- 建立桶,區間跨度=(最大值-最小值)/(桶的數量-1)。
- 周遊數列,對号入座。
- 每個桶内進行排序,可選擇快排等。
- 周遊所有的桶,輸出所有元素。
- 最優時間複雜度為O(n),完爆比較排序算法。
- 時間複雜度不穩定。
資料服從均勻分布的場景。
8 性能對比
随機生成區間0 ~ K之間的序列,共計N個數字,利用各種算法進行排序,記錄排序所需時間。
參考内容及圖源
[1]《漫畫算法:小灰的算法之旅》
[2]《算法(第4版)》
[3]《算法圖解》
[4]《劍指Offer》
[5]十大經典排序算法(動圖示範)
https://www.cnblogs.com/onepixel/p/7674659.html[6]維基百科
https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5