《漫畫算法》筆記-上篇


漫畫算法-小灰的算法之旅

魏夢舒(@程序員小灰)著

小灰用漫畫(可愛的手繪小倉鼠)的形式,給算法這顆“炮彈”包上了“糖衣”,讓算法的為力潛藏於內,外表不再嚇人,變得萌萌噠,Q彈可愛。

本書通過主人公小灰,用漫畫的形式講述了算法與數據結構的基礎知識、復雜多變的算法面試及算法的實際應用。

  • 第一章:講述什么是算法、數據結構,有什么用。如何計算時間復雜度、空間復雜度。
  • 第二章:講述基本的數據結構:數組、鏈表、棧、隊列、哈希。
  • 第三章:講述了樹、二叉樹相關知識。
  • 第四章:講述了經典的排序算法:冒泡、快速、堆、計數、桶排序。
  • 第五章:講述了10多道職場上流行的算法面試題及詳細的解題思路。
  • 第六章:講述了算法在職場上的實際應用。

“學習算法,我們不需要死記硬背那些冗長復雜的背景知識、底層原理、指令語法......需要做的是領悟算法思想、理解算法對內存空間和性能的影響,以及開動腦筋去尋求解決問題的最佳方案。相比編程領域的其他技術,算法更純粹,更接近數學,也更具有趣味性。” -- 作者說

1. 算法和數據結構

算法

algorithm。始於計算出1+2+3+4...+10000的結果。首先把從1到10000這10000個數字兩兩分組相加,如下:

1 + 10000 = 10001
2 + 9999 = 10001
3 + 9998 = 10001
......

一共有多少組這樣結果相同的和呢?有10000/2即5000組,即結果:(1 + 10000) * 10000 / 2 = 50005000

在數學上稱這種等差數列求和的方法為:高斯算法

在數學領域:算法是用於解決一類問題的公式和思想。

在計算機領域:算法本質是一序列程序指令,用於解決特定的運算和邏輯問題。衡量算法好壞的重要標准有兩個:

  • 空間復雜度:占用內存空間的大小
  • 時間復雜度:運行時間的長短

應用:運算、查找、排序、尋找最優路線、面試等。

數據結構

數據結構是算法的基石。如果把算法比喻成美麗靈動的舞者,那么數據結構就是舞者腳下廣闊而堅實的舞台。

data structure,是數據的組織、管理和存儲格式,其使用的目的是為了高效地訪問和修改數據。

數據結構組成方式:

  • 線性結構:最簡單的數據結構。包括數組、鏈表,及衍生的棧、隊列、哈希表。
  • 樹:相對復雜的數據結構,有代表性的是二叉樹。
  • 圖:更為復雜的數據結構,因為在圖中會呈現出多對多的關聯關系。

時間復雜度

是對一個算法運行時間長短的量度,用大O表示,記作T(n) = O(f(n))

直白地講,時間復雜度就是把程序的相對執行時間函數T(n)簡化為一個數量級,這個數量級可以是n、n²、n³等。

推導出時間復雜度,有如下幾個原則:

  • 如果運行時間是常數量級,則用常數1表示
  • 只保留時間函數中的最高階項
  • 如果最高階項存在,則省去最高階項前面的系數

比如:

執行次數是T(n) = 3n,最高階項為3n,省去系數3,轉化的時間復雜度為T(n) = O(n)

執行次數是T(n) = 5logn,最高階項為5logn,省去系數5,轉化的時間復雜度為T(n) = O(logn)。5logn,數學中的對數。

執行次數是T(n) = 2,只有常數量級,轉化的時間復雜度為T(n)=O(1)

執行次數是T(n) = 0.5n² + 0.5n,最高階項為0.5n²,省去系數0.5,轉化的時間復雜度為T(n) = O(n²)

誰更節省時間呢?結論如下:
O(1) < O(logn) < O(n) < O(n²)

空間復雜度

space complexity。是對一個算法在運行過程中臨時占用存儲空間大小的度量,用大O表示,記作S(n) = O(f(n))

  • 常量空間:O(1),存儲空間大小固定,和輸入規模沒有直接關系
  • 線性空間:O(n),空間是一個線性的集合(比如數組),並且集合大小和輸入規模n成正比
  • 二維空間:O(n²)空間是一個二維數組集合,並且集合的長度和寬度都與輸入規模n成正比
  • 遞歸空間:執行遞歸操作所需的內存空間和遞歸的深度成正比。純粹的遞歸操作的空間復雜度也是線性的,如果遞歸的深度是n,那么空間復雜度就是O(n)

2. 數據結構基礎

數組

有限個,在內存中順序存儲。

內存是由一個個連續的內存單元組成的,每一個內存單元都有自己的地址。

數組中的每一個元素,都存儲在小小的內存單元中,並且元素直接緊密排列,既不能打亂元素的存儲順序,也不能跳過某個存儲單元進行存儲。

操作:

  • 讀取、更新:利用索引,比較容易,直接操作
  • 插入:末尾插入簡單,直接在最后插入即可。非末尾插入,由於數組的每一個元素都有其固定下標,所以需要先把插入位置及后面的元素向后移動,挪出地方,再把要插入的元素放到對應的數組位置上。
  • 刪除:和插入過程相反,若刪除的元素不是位於末尾,其后的元素都要向前移動。

插入、刪除的時間復雜度都是O(n)

優勢:非常高效的訪問能力,只要給出下標,就能找到對應元素。
劣勢:插入、刪除效率低下。數組元素連續緊密地存儲在內存中,插入、刪除元素都會導致大量元素被迫移動,影響效率。

數組適合讀操作多、寫操作少的場景。鏈表和數組正好相反。

鏈表

一種在物理上非連續、非順序的數據結構,由若干節點(node)所組成。

單向鏈表的每一個節點包含兩部分,存放數據的變量data,指向下一個節點的指針next。

鏈表的第一個節點被稱為頭結點,最后一個節點被稱為尾節點。尾結點的下一個節點指向空。

雙向鏈表,每一個節點除了擁有 data 和 next,還擁有指向前置節點的 prev 指針。

數組在內存中的存儲方式是順序存儲,鏈表 在內存中的存儲順序是隨機存儲。

數組在內存中占用了連續完整的存儲空間,而鏈表采用了見縫插針的方式,鏈表的沒一個節點分布在內存的不同位置,依靠next指針關聯起來。這樣可以靈活有效地利用零散的碎片空間。

鏈表操作:

  • 查找節點:只能從頭結點開始向后一個一個節點逐一查找。
  • 更新:查找到了,替換數據就好
  • 插入、刪除:分三種情況:尾部、頭部、中間,需要處理對應的next指針。具體看書中的圖會更直觀呢,文字描述太晦澀了。

;鏈表的插入和刪除的時間復雜度:如果不考慮插入、刪除操作之前查找元素的過程,只考慮純粹的插入、刪除操作,時間復雜度為O(1)

數組 vs 鏈表:數組能夠利用下標快速定位元素,對於讀操作多、寫操作少的場景非常使用。而鏈表的有事在於能靈活的進行插入和刪除操作,適用於讀操作少、寫操作多的場景。

棧和隊列

數據存儲的物理結構和邏輯結構:

  • 物理結構:物質層面,實實在在的,看得見,摸得着。
    • 順序存儲結構:數組
    • 鏈式存儲結構:鏈表
  • 邏輯結構:精神層面,抽象的概念,依賴於物理結構而存在。
    • 線性結構:順序表、棧、隊列
    • 非線性結構:樹、圖

棧:stack。一種線性數據結構,棧中的元素只能先進后出(FILO,First In Last Out)。最早進入的元素存放的位置叫作棧底,最后進入的元素存放的位置叫作棧頂。用數組、鏈表均可以實現。

棧的基本操作:

  • 入棧:只能從棧頂放入元素,新元素成為新棧頂。
  • 出棧:只能從棧頂彈出元素,出棧元素的前一個元素成為新棧頂。

入棧、出棧的時間復雜度:O(1),因為只會影響到最后一個元素。

隊列:queue。一種線性數據結構,隊列中的元素只能先進先出(FIFO,First In First Out)。隊列的出口端叫作隊頭(front),隊列的入口端叫作隊尾(rear)。用數組、鏈表均可以實現。

隊列的基本操作:

  • 入隊:enqueue,只能在隊尾的位置放入元素,新元素成為新隊尾。
  • 出隊:dequeue,只能在隊頭的位置移出元素,出隊元素的后一個元素成為新隊頭。

額外:循環隊列、雙端隊列、優先隊列。

散列表

散列表:也稱哈希表,hash table。是存儲 key-value 映射的集合,時間復雜度接近於O(1)。本質上也是一個數組。

哈希函數:通過某種方式,把 key 和數組下標進行轉換的函數。

散列表的讀寫操作:

  • 寫操作(put):在散列表中插入新的鍵值對。
    • 通過哈希函數,把 key 轉換成數組下標。如果數組下標的位置沒有元素,就放到該位置,如果該位置有值,這種情況為哈希沖突
    • 解決哈希沖突:開放尋址法和鏈表法。
  • 讀操作(get):通過給定的key,在散列表中查找對應的value。
    • 通過哈希函數,把 key 轉換成數組下標,然后根據下標找value.

3.樹

tree,是n個節點的有限集。有如下特點:

  • 有且僅有一個特定的稱為根的節點。
  • 當n>1時,其余節點可分為m(m>0)個互不相交的有限集,每一個集合本身又是一個樹,並稱為根的子樹。

樹的最大層級樹,被稱為樹的高度或深度。

相關節點

  • 根節點(root)
  • 葉子節點(leaf):沒有孩子的節點
  • 父節點
  • 孩子節點
  • 兄弟節點

二叉樹

樹的一種特殊形式。樹的每個節點最多有2個孩子節點。

二叉樹的兩個孩子節點,一個被稱為左孩子,一個被稱為右節點。

二叉樹有兩種特殊形式:滿二叉樹、完全二叉樹。

滿二叉樹:一個二叉樹的所有非葉子節點都存在左右孩子,並且所有葉子節點都在同一層接上。簡言之,滿二叉樹的每一個分支都是滿的。

完全二叉樹:對一個有n個節點的二叉樹,按層級順序編號,則所有節點的編號為從1到n。如果這個樹所有節點和同樣深度的滿二叉樹的編號為從1到n的節點位置相同,則這個二叉樹為滿二叉樹。

一棵樹,若為滿二叉樹,那么一定是完全二叉樹。反之,不一定。

在內存中存儲:

  • 鏈式存儲結構:一個節點含數據data,指向左孩子的節點,指向右孩子的節點。
  • 數組:按照層級順序把二叉樹的節點放到數組中對應的位置上。如果某一個節點的孩子為空,則數組的相應位置也空出來。
    • 為什么這么設計?可以更方便的定位孩子節點、父節點。
    • 若父節點的下標是parent,那么左孩子節點下標是2parent+1,右孩子節點下標是2parent+2。
    • 反之,若左孩子節點下標是leftChild,那么父節點下標是(leftChild - 1)/2。
    • 稀疏二叉樹,用數組表示會很浪費空間。

二叉樹的應用:查找操作、維持相對順序。

  • 查找:二叉查找樹。左子樹上所有節點都小於根節點,右子樹上所有節點都大於根節點。左右子樹也都是二叉查找樹。
  • 維持相對順序:二叉排序樹

二叉樹的遍歷:
從節點之間位置關系的角度:

  • 前序遍歷:輸出順序:根節點、左子樹、右子樹
  • 中序遍歷:輸出順序:左子樹、根節點、右子樹
  • 后序遍歷:輸出順序:左子樹、右子樹、根節點
  • 層序遍歷:按照從根節點到葉子節點的層級關系,一層一層橫向遍歷各個節點。

從更宏觀的角度:

  • 深度優先遍歷(前、中、后序遍歷,前中后是相對根節點)
  • 廣度優先遍歷(層序遍歷)

二叉堆:本質上是一種完全二叉樹。

  • 最大堆:任何一個父節點的值,都大於或等於它左、右孩子節點的值。
  • 最小堆:任何一個父節點的值,都小於或等於它左、右孩子節點的值。

二叉堆的根節點,叫作堆頂。最大堆的堆頂是整個堆中最大元素,最小堆的堆頂是整個堆中最小元素。

優先隊列:基於二叉堆實現:

  • 最大優先隊列:無論入隊順序如何,當前最大元素都會優先出隊,基於最大堆實現
  • 最小優先隊列:無論出隊順序如何,當前最小元素都會優先出隊,基於最小堆實現

觀后感:回想起了很多大學學習時的場景與概念(專業:軟件工程)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM