演算法+資料結構=程式,今天就來說說遞迴+排序+查詢,再加上樹與圖
著名資料專家沃斯曾說:演算法+資料結構=程式
上次講了資料結構
這回就講講演算法
複雜度
複雜度分析,是貫徹資料結構和演算法中的一項基礎技能,學習資料結構和演算法的目的,無非就是要寫出佔用空間更小、執行時間更短的程式碼。
時間複雜度
- 大O表示法:
T(n) = O(f(n))
- 表示程式碼執行時間隨資料規模增長的 變化趨勢(注意只是表示「變化趨勢」)
- 由於只是表示變化趨勢,一般計算複雜度時,會忽略低階、常量、係數
-
幾種常見的時間複雜度量級:
多項式量級:
- 常數階 O(1)
- 對數階 O(logn)
- 線性階 O(n)
- 線性對數階 O(nlogn)
- 平方階 O(n²) O(n³) ... O(n^k)
非多項式量級:(n越多,執行時間急劇上升,效能低)
- 指數階 O(2^n)
- 階乘階 O(n!)
- 加法法則和乘法法則
- 加法法則:總複雜度等於量級最大的那段程式碼的複雜度
- 乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積
- 平均時間複雜度:
- 也叫加權平均時間複雜度或者期望時間複雜度。
- 要會計算:最好、最壞、平均時間
- 均攤時間複雜度
- 特殊的平均時間複雜度
- 相當於演算法有規律可循,計算時間時,可以把一次耗時多的操縱的時間,均攤給多次耗時少的操縱。
演算法
1. 遞迴
可以用遞迴的條件:
- 一個問題的解可以分解為幾個子問題的解
- 這個問題與分解之後的子問題,除了資料規模不同,求解思路完全一樣
- 存在遞迴終止條件
寫遞迴演算法的思路:
- 歸納出遞迴表示式
- 尋找終止條件
遞迴程式碼的弊端:
- 堆疊溢位
- 可能會重複計算
- 函式呼叫耗時多
- 空間複雜度高
2. 排序
衡量排序演算法好壞的三要素:
- 執行效率
- 最好時間複雜度
- 最壞時間複雜度
- 平均時間複雜度
- 時間複雜度的係數、常數 、低階(因為排序的資料規模一般不會非常大)
- 比較、交換的次數
- 額外記憶體消耗(記憶體消耗為O(1)的稱為原地排序)
- 穩定性,是否是穩定排序(即如果待排序的序列中存在值相等的元素,經過排序之後,相等元素之間原有的先後順序不變)
按時間複雜度分類:
- O(n²): 氣泡排序、插入排序、選擇排序
- O(nlogn):歸併排序、快速排序
- O(n) :桶排序、計數排序、基數排序 (條件苛刻,適用於部分場景)
2.1 氣泡排序
原理: 從下往上,逐次比較兩個相鄰的資料,如果下面的資料比上面的資料大,則把這兩個資料的位置互換。
- 最好時間複雜度 O(n)
- 最壞時間複雜度 O(n^2)
- 平均時間複雜度 O(n^2)
- 原地排序、穩定排序
2.2 插入排序
原理: 分為已排區域和未排區域,每次拿未排區域中的第一個數,插入到已排區域中正確的位置。
- 最好時間複雜度 O(n)
- 最壞時間複雜度 O(n^2)
- 平均時間複雜度 O(n^2)
- 原地排序、穩定排序
2.3 選擇排序
原理: 分為已排區域和未排區域,每次從未排區域中選取最小的數,放到已排區域的最後面。
- 最好時間複雜度 O(n^2)
- 最壞時間複雜度 O(n^2)
- 平均時間複雜度 O(n^2)
- 原地排序、 非穩定的排序演算法
- 一般都不考慮用該演算法。
2.4 歸併排序
原理: 歸併排序的核心思想還是蠻簡單的。如果要排序一個陣列,我們先把陣列從中間分成前後兩部分,然後對前後兩部分分別排序,再將排好序的兩部分合並在一起,這樣整個陣列就都有序了。
- 非原地排序,空間複雜度為O(n)
- 穩定排序
- 利用分治遞迴思想
- 遞推公式:
merge_sort(p…r) = merge(merge_sort(p…q), merge_sort(q+1…r))
-
最好、最壞、平均時間複雜度都是 O(nlogn)
2.5 快速排序
- 原地排序
- 非穩定排序
- 遞推公式:
quick_sort(p…r) = partition(p…r) + quick_sort(p…q-1) + quick_sort(q+1, r)
- 最好時間複雜度: O(nlogn)
- 最壞時間複雜度: O(n^2) (極小機率出現)
- 平均時間複雜度: O(nlogn)
2.6 桶排序
桶排序,顧名思義,會用到“桶”,核心思想是將要排序的資料分到幾個有序的桶裡,每個桶裡的資料再單獨進行排序。桶內排完序之後,再把每個桶裡的資料按照順序依次取出,組成的序列就是有序的了。
桶排序的時間複雜度為什麼是 O(n) 呢?我們一塊兒來分析一下。如果要排序的資料有 n 個,我們把它們均勻地劃分到 m 個桶內,每個桶裡就有 k=n/m 個元素。每個桶內部使用快速排序,時間複雜度為 O(k * logk)。m 個桶排序的時間複雜度就是 O(m * k * logk),因為 k=n/m,所以整個桶排序的時間複雜度就是 O(n*log(n/m))。當桶的個數 m 接近資料個數 n 時,log(n/m) 就是一個非常小的常量,這個時候桶排序的時間複雜度接近 O(n)。
苛刻的條件:
- 要排序的資料需要很容易就能劃分成 m 個桶
- 桶與桶之間有著天然的大小順序
- 資料在各個桶之間的分佈是比較均勻的
2.7 計數排序
計數排序其實是桶排序的一種特殊情況: 資料的訪問很小(例如年齡、考生的成績),桶的數量是有限的。 以給高考考生成績進行排名為例,考生的滿分是 900 分,最小是 0 分,對應901個桶,把全國的考生放入這901個桶,桶內的資料都是分數相同的考生,所以並不需要再進行排序。
特殊要求:
- 只能用在資料範圍不大的場景中,如果資料範圍 k 比要排序的資料 n 大很多,就不適合用計數排序了
- 計數排序只能給非負整數排序,如果要排序的資料是其他型別的,要將其在不改變相對大小的情況下,轉化為非負整數。如果要排序的資料中有負數,資料的範圍是 [-1000, 1000],那我們就需要先對每個資料都加 1000,轉化成非負整數。如果考生成績精確到小數後一位,我們就需要將所有的分數都先乘以 10,轉化成整數。
2.8 基數排序
我們再來看這樣一個排序問題。假設我們有 10 萬個手機號碼,希望將這 10 萬個手機號碼從小到大排序,你有什麼比較快速的排序方法呢?
我們之前講的快排,時間複雜度可以做到 O(nlogn),還有更高效的排序演算法嗎?桶排序、計數排序能派上用場嗎?手機號碼有 11 位,範圍太大,顯然不適合用這兩種排序演算法。針對這個排序問題,有沒有時間複雜度是 O(n) 的演算法呢?現在我就來介紹一種新的排序演算法,基數排序。
剛剛這個問題裡有這樣的規律:假設要比較兩個手機號碼 a,b 的大小,如果在前面幾位中,a 手機號碼已經比 b 手機號碼大了,那後面的幾位就不用看了。
藉助穩定排序演算法,這裡有一個巧妙的實現思路。還記得我們第 11 節中,在闡述排序演算法的穩定性的時候舉的訂單的例子嗎?我們這裡也可以藉助相同的處理思路,先按照最後一位來排序手機號碼,然後,再按照倒數第二位重新排序,以此類推,最後按照第一位重新排序。經過 11 次排序之後,手機號碼就都有序了。
手機號碼稍微有點長,畫圖比較不容易看清楚,我用字串排序的例子,畫了一張基數排序的過程分解圖,你可以看下。
注意,這裡按照每位來排序的排序演算法要是穩定的,否則這個實現思路就是不正確的。因為如果是非穩定排序演算法,那最後一次排序只會考慮最高位的大小順序,完全不管其他位的大小關係,那麼低位的排序就完全沒有意義了。
根據每一位來排序,我們可以用剛講過的桶排序或者計數排序,它們的時間複雜度可以做到 O(n)。如果要排序的資料有 k 位,那我們就需要 k 次桶排序或者計數排序,總的時間複雜度是 O(k*n)。當 k 不大的時候,比如手機號碼排序的例子,k 最大就是 11,所以基數排序的時間複雜度就近似於 O(n)。
實際上,有時候要排序的資料並不都是等長的,比如我們排序牛津字典中的 20 萬個英文單詞,最短的只有 1 個字母,最長的我特意去 查了下,有 45 個字母,中文翻譯是塵肺病。對於這種不等長的資料,基數排序還適用嗎?
實際上,我們可以把所有的單詞補齊到相同長度,位數不夠的可以在後面補“0”,因為根據ASCII 值,所有字母都大於“0”,所以補“0”不會影響到原有的大小順序。這樣就可以繼續用基數排序了。
我來總結一下,基數排序對要排序的資料是有要求的:
- 需要可以分割出獨立的“位”來比較,而且位之間有遞進的關係,如果 a 資料的高位比 b 資料大,那剩下的低位就不用比較了
- 除此之外,每一位的資料範圍不能太大,要可以用線性排序演算法來排序,否則,基數排序的時間複雜度就無法做到 O(n) 了。
3. 查詢
3.1 二分查詢法
- 依賴於陣列結構(資料量太大不適合用二分查詢法,資料需要連續的儲存空間)
- 資料必須是排好序的
- 時間複雜度:O(logn)
樹與圖
最後
學習影片內容可以看這裡:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2667626/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java實現遞迴查詢樹結構Java遞迴
- 資料結構與演算法:遞迴資料結構演算法遞迴
- oracle之樹狀結構的儲存與展示(遞迴查詢)Oracle遞迴
- iOS 演算法之排序、查詢、遞迴iOS演算法排序遞迴
- python 遞迴樹狀結構 和 排序Python遞迴排序
- 資料結構與演算法-二叉查詢樹資料結構演算法
- 第二章 :查詢與排序---------遞迴、查詢與排序補充排序遞迴
- 資料結構與演算法——歸併排序: 陣列&連結串列&遞迴&非遞迴解法全家桶資料結構演算法排序陣列遞迴
- 『資料結構與演算法』二叉查詢樹(BST)資料結構演算法
- 重學資料結構和演算法(二)之二叉樹、紅黑樹、遞迴樹、堆排序資料結構演算法二叉樹遞迴排序
- 資料結構:歸併排序(非遞迴)資料結構排序遞迴
- 資料結構與演算法知識點總結(5)查詢樹資料結構演算法
- 資料結構與演算法(十一)——演算法-遞迴資料結構演算法遞迴
- 【資料結構與演算法】手撕二叉查詢樹資料結構演算法
- 資料結構與演算法-二叉查詢樹平衡(DSW)資料結構演算法
- 資料結構與演算法-二叉查詢樹平衡(AVL)資料結構演算法
- 資料結構與演算法學習總結--遞迴資料結構演算法遞迴
- 【Java資料結構與演算法筆記(二)】樹的四種遍歷方式(遞迴&非遞迴)Java資料結構演算法筆記遞迴
- Java資料結構與演算法--遞迴和回溯Java資料結構演算法遞迴
- JS遞迴過濾樹形結構陣列物件--模糊查詢JS遞迴陣列物件
- 資料結構與演算法:二叉排序樹資料結構演算法排序
- 資料結構和演算法:遞迴資料結構演算法遞迴
- ORACLE遞迴查詢(適用於ID,PARENTID結構資料表)Oracle遞迴
- 資料結構與演算法:查詢演算法資料結構演算法
- js樹型結構資料簡易遞迴JS遞迴
- 通用-遞迴樹結構遞迴
- 【資料結構】查詢結構(二叉排序樹、ALV樹、雜湊技術雜湊表)資料結構排序
- 結構與演算法(04):排序規則與查詢演算法演算法排序
- 資料結構與演算法知識點總結(3)樹、圖與並查集資料結構演算法並查集
- 資料結構-遞迴資料結構遞迴
- 【資料結構基礎應用】【查詢和排序演算法】資料結構排序演算法
- Java資料結構(十五)—— 多路查詢樹Java資料結構
- 第二章 :查詢與排序-------二分查詢的遞迴解法排序遞迴
- 【資料結構與演算法】通俗易懂說連結串列資料結構演算法
- 資料結構與演算法——排序資料結構演算法排序
- 遞迴樹形查詢所有分類遞迴
- 【資料結構與演算法】—— 二分查詢資料結構演算法
- 資料結構與演算法-二分查詢資料結構演算法