數組/鏈表/棧/隊列/樹/圖 等數據結構的優缺點及應用場景


數組、字符串(Array & String)

數組的優點在於:

  • 構建非常簡單
  • 能在 O(1) 的時間里根據數組的下標(index)查詢某個元素

而數組的缺點在於:

  • 構建時必須分配一段連續的空間
  • 查詢某個元素是否存在時需要遍歷整個數組,耗費 O(n) 的時間(其中,n 是元素的個數)
  • 刪除和添加某個元素時,同樣需要耗費 O(n) 的時間

 鏈表(LinkedList)

單鏈表:鏈表中的每個元素實際上是一個單獨的對象,而所有對象都通過每個元素中的引用字段鏈接在一起。
雙鏈表:與單鏈表不同的是,雙鏈表的每個結點中都含有兩個引用字段。

由一系列節點組成的元素集合,每個節點包含數據域item和下一個節點的指針next,通過節點相互連接,最終串聯成一個鏈表。

class Node(object):
    def __init__(self,item):
        self.item = item
        self.next = None
a = [1,2,3]
b = Node(a)
c = Node(4)
b.next = c
print(b.next.item)

 

鏈表的優點如下:

  • 鏈表能靈活地分配內存空間;
  • 能在 O(1) 時間內刪除或者添加元素,前提是該元素的前一個元素已知,當然也取決於是單鏈表還是雙鏈表,在雙鏈表中,如果已知該元素的后一個元素,同樣可以在 O(1) 時間內刪除或者添加該元素。

鏈表的缺點是:

  • 不像數組能通過下標迅速讀取元素,每次都要從鏈表頭開始一個一個讀取;
  • 查詢第 k 個元素需要 O(k) 時間。

應用場景:如果要解決的問題里面需要很多快速查詢,鏈表可能並不適合;如果遇到的問題中,數據的元素個數不確定,而且需要經常進行數據的添加和刪除,那么鏈表會比較合適。而如果數據元素大小確定,刪除插入的操作並不多,那么數組可能更適合。

棧(Stack)

特點:棧的最大特點就是后進先出(LIFO)。對於棧中的數據來說,所有操作都是在棧的頂部完成的,只可以查看棧頂部的元素,只能夠向棧的頂部壓⼊數據,也只能從棧的頂部彈出數據。
實現:利用一個單鏈表來實現棧的數據結構。而且,因為我們都只針對棧頂元素進行操作,所以借用單鏈表的頭就能讓所有棧的操作在 O(1) 的時間內完成。
應用場景:在解決某個問題的時候,只要求關心最近一次的操作,並且在操作完成了之后,需要向前查找到更前一次的操作.

隊列(Queue)

特點:和棧不同,隊列的最大特點是先進先出(FIFO),就好像按順序排隊一樣。對於隊列的數據來說,我們只允許在隊尾查看和添加數據,在隊頭查看和刪除數據。
實現:可以借助雙鏈表來實現隊列。雙鏈表的頭指針允許在隊頭查看和刪除數據,而雙鏈表的尾指針允許我們在隊尾查看和添加數據。
應用場景:直觀來看,當我們需要按照一定的順序來處理數據,而該數據的數據量在不斷地變化的時候,則需要隊列來幫助解題。在算法面試題當中,廣度優先搜索(Breadth-First Search)是運用隊列最多的地方。

雙端隊列(Deque)

特點:雙端隊列和普通隊列最大的不同在於,它允許我們在隊列的頭尾兩端都能在 O(1) 的時間內進行數據的查看、添加和刪除。
實現:與隊列相似,我們可以利用一個雙鏈表實現雙端隊列。
應用場景:雙端隊列最常用的地方就是實現一個長度動態變化的窗口或者連續區間,而動態窗口這種數據結構在很多題目里都有運用。

樹(Tree)

樹的結構十分直觀,而樹的很多概念定義都有一個相同的特點:遞歸,也就是說,一棵樹要滿足某種性質,往往要求每個節點都必須滿足。例如,在定義一棵二叉搜索樹時,每個節點也都必須是一棵二叉搜索樹。
正因為樹有這樣的性質,大部分關於樹的面試題都與遞歸有關,換句話說,面試官希望通過一道關於樹的問題來考察你對於遞歸算法掌握的熟練程度。
樹的形狀

在面試中常考的樹的形狀有:普通二叉樹、平衡二叉樹、完全二叉樹、二叉搜索樹、四叉樹(Quadtree)、多叉樹(N-ary Tree)。
對於一些特殊的樹,例如紅黑樹(Red-Black Tree)、自平衡二叉搜索樹(AVL Tree),一般在面試中不會被問到,除非你所涉及的研究領域跟它們相關或者你十分感興趣,否則不需要特別着重准備。
關於樹的考題,無非就是要考查樹的遍歷以及序列化(serialization)

樹的遍歷

1. 前序遍歷(Preorder Traversal)

方法:先訪問根節點,然后訪問左子樹,最后訪問右子樹。在訪問左、右子樹的時候,同樣,先訪問子樹的根節點,再訪問子樹根節點的左子樹和右子樹,這是一個不斷遞歸的過程。

應用場景:運用最多的場合包括在樹里進行搜索以及創建一棵新的樹。
 
2. 中序遍歷(Inorder Traversal)

方法:先訪問左子樹,然后訪問根節點,最后訪問右子樹,在訪問左、右子樹的時候,同樣,先訪問子樹的左邊,再訪問子樹的根節點,最后再訪問子樹的右邊。

應用場景:最常見的是二叉搜索樹,由於二叉搜索樹的性質就是左孩子小於根節點,根節點小於右孩子,對二叉搜索樹進行中序遍歷的時候,被訪問到的節點大小是按順序進行的。
 
3. 后序遍歷(Postorder Traversal)

方法:先訪問左子樹,然后訪問右子樹,最后訪問根節點。

應用場景:在對某個節點進行分析的時候,需要來自左子樹和右子樹的信息。收集信息的操作是從樹的底部不斷地往上進行,好比你在修剪一棵樹的葉子,修剪的方法是從外面不斷地往根部將葉子一片片地修剪掉。

注意:
掌握好這三種遍歷的遞歸寫法和非遞歸寫法是非常重要的,懂得分析各種寫法的時間復雜度和空間復雜度同樣重要。

樹這個數據結構都是最應該花時間學習的,既能證明你對遞歸有很好的認識,又能幫助你學習圖論,尤其是二叉搜索樹(BST)。

 

#二叉樹

class BiTreeNode:
    def __init__(self,data):
        self.data = data
        self.lchild = None
        self.rchild = None

a = BiTreeNode('A')
b = BiTreeNode('B')
c = BiTreeNode('C')
d = BiTreeNode('D')
e = BiTreeNode('E')
f = BiTreeNode('F')
g = BiTreeNode('G')

e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f
root = e


#前序遍歷
def pre_order(root):
    if root:
        print(root.data,end = ',')
        pre_order(root.lchild)
        pre_order(root.rchild)
#中序遍歷
def in_order(root):
    if root:
        in_order(root.lchild)
        print(root.data,end =',')
        in_order(root.rchild)
#后序遍歷
def post_order(root):
    if root:
        in_order(root.lchild)
        in_order(root.rchild)
        print(root.data,end = ',')

in_order(root)

 

優先隊列(Priority Queue)

特點

能保證每次取出的元素都是隊列中優先級別最高的。優先級別可以是自定義的,例如,數據的數值越大,優先級越高;或者數據的數值越小,優先級越高。優先級別甚至可以通過各種復雜的計算得到。
應用場景

從一堆雜亂無章的數據當中按照一定的順序(或者優先級)逐步地篩選出部分乃至全部的數據。

優先隊列的本質是一個二叉堆結構。堆在英文里叫 Binary Heap,它是利用一個數組結構來實現的完全二叉樹。換句話說,優先隊列的本質是一個數組,數組里的每個元素既有可能是其他元素的父節點,也有可能是其他元素的子節點,而且,每個父節點只能有兩個子節點,很像一棵二叉樹的結構。

牢記下面優先隊列有三個重要的性質。

  • 數組里的第一個元素 array[0] 擁有最高的優先級別。
  • 給定一個下標 i,那么對於元素 array[i] 而言:
  1. 它的父節點所對應的元素下標是 (i-1)/2
  2. 它的左孩子所對應的元素下標是 2×i + 1
  3. 它的右孩子所對應的元素下標是 2×i + 2
  • 數組里每個元素的優先級別都要高於它兩個孩子的優先級別。

優先隊列最基本的操作有兩個。

  • 向上篩選(sift up / bubble up)

當有新的數據加入到優先隊列中,新的數據首先被放置在二叉堆的底部。

不斷進行向上篩選的操作,即如果發現該數據的優先級別比父節點的優先級別還要高,那么就和父節點的元素相互交換,再接着往上進行比較,直到無法再繼續交換為止。

  • 向下篩選(sift down / bubble down)

當堆頂的元素被取出時,要更新堆頂的元素來作為下一次按照優先級順序被取出的對象,需要將堆底部的元素放置到堆頂,然后不斷地對它執行向下篩選的操作。

將該元素和它的兩個孩子節點對比優先級,如果優先級最高的是其中一個孩子,就將該元素和那個孩子進行交換,然后反復進行下去,直到無法繼續交換為止。

圖(Graph)

  • 階(Order)、度:出度(Out-Degree)、入度(In-Degree)
  • 樹(Tree)、森林(Forest)、環(Loop)
  • 有向圖(Directed Graph)、無向圖(Undirected Graph)、完全有向圖、完全無向圖
  • 連通圖(Connected Graph)、連通分量(Connected Component)
  • 存儲和表達方式:鄰接矩陣(Adjacency Matrix)、鄰接鏈表(Adjacency List)

常用圖方法

  • 圖的存儲和表達方式:鄰接矩陣(Adjacency Matrix)、鄰接鏈表(Adjacency List)
  • 圖的遍歷:深度優先、廣度優先
  • 二部圖的檢測(Bipartite)、樹的檢測、環的檢測:有向圖、無向圖
  • 拓撲排序
  • 聯合-查找算法(Union-Find)
  • 最短路徑:Dijkstra、Bellman-Ford

環的檢測、二部圖的檢測、樹的檢測以及拓撲排序都是基於圖的遍歷,尤其是深度優先方式的遍歷。而遍歷可以在鄰接矩陣或者鄰接鏈表上進行,所以掌握好圖的遍歷是重中之重!因為它是所有其他圖論算法的基礎。

前綴樹(Trie)

前綴樹被廣泛地運用在字典查找當中,也被稱為字典樹。
舉例:給定一系列字符串,這些字符串構成了一種字典,要求你在這個字典當中找出所有以“ABC”開頭的字符串。

如果用前綴樹頭幫助對字典的存儲進行優化,那么可以把搜索的時間復雜度下降為 O(M),其中 M 表示字典里最長的那個單詞的字符個數,在很多情況下,字典里的單詞個數 N 是遠遠大於 M 的。因此,前綴樹在這種場合中是非常高效的。
經典應用

網站上的搜索框會羅列出以搜索文字作為開頭的相關搜索信息,這里運用了前綴樹進行后端的快速檢索。

漢字拼音輸入法的聯想輸出功能也運用了前綴樹。

 


免責聲明!

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



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