《漫畫算法》筆記-上篇

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

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

小灰用漫畫(可愛的手繪小倉鼠)的形式,給算法這顆「炮彈」包上了「糖衣」,讓算法的爲力潛藏於內,外表再也不嚇人,變得萌萌噠,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。
    • 稀疏二叉樹,用數組表示會很浪費空間。

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

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

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

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

從更宏觀的角度:

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

二叉堆:本質上是一種徹底二叉樹。

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

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

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

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

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

相關文章
相關標籤/搜索