C語言學習總結(四)——數據結構

程序 = 數據結構 + 算法。
當我們需要解決一個計算機問題,大致的步驟是這樣的:
1. 從一個具體的問題抽象出一個適當的數學模型
2. 設計一個解決這個模型的算法
3. 編寫相應的程序,測試、調整直到得到最終解答。
所以:數據結構是一門用來研究非數值計算的程序設計問題中計算機的操作對象及他們之間的關係和操作等的學科。

一、定義

數據元素和數據元素之間的關係,也可稱之爲數據的組織形式,包括3方面

  1. 邏輯結構:
    集合
    線性結構 1:1
    層次結構(樹形結構)1:n
    圖形結構(網狀結構)n:n

  2. 存儲結構:
    順序存儲:講數據元素按照邏輯上的先後次序存儲到一片連續的內存中(一般用數組實現)
    鏈式存儲:講數據元素存儲在內存的不同位置(每個數據元素單獨存),利用指針來建立元素之間的聯繫(用指針)
    索引存儲
    散列存儲

  3. 運算:增刪改查等

二、線性結構

  1. 順序表
    特點:邏輯結構和存儲結構一致,存儲密度高,但是插入和刪除等運算複雜度較高,且要求系統提供連續的空間進行存儲。
    常見:數組

  2. 鏈表
    特點:一種物理存儲單元上非連續、非順序的存儲結構,存儲密度低,插入和刪除的時間複雜度低。
    定義一個單鏈表節點(link_list)

    typedef int typedate;
    typedef struct node
    {
        typedate date;
        struct * next;  //指向下一個元素
    }linknode;
    此時定義的僅僅只是鏈表的一個節點
    
  3. 單鏈表
    最常見的鏈表,有頭有尾。

  4. 單循環鏈表
    鏈表的尾端指向鏈表的頭。

  5. 雙循環鏈表
    鏈表節點中有兩個指針,可以雙向尋找。
    C++
    typedef struct DuLNode {
    ElemType data;
    struct DuLNode *prior,*next;
    }DuLNode,*DuLinkList;


  6. 特點:僅允許在表的一端進行插入和刪除運算。這一端被稱爲棧頂,相對地,把另一端稱爲棧底。

  7. 隊列
    特點:只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作。

三、非線性結構

1. 樹

由n(n>=1)個有限節點組成一個具有層次關係的集合。把它叫做“樹”是因爲它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具有以下的特點:
* 每個節點有零個或多個子節點;
* 沒有父節點的節點稱爲根節點;
* 每一個非根節點有且只有一個父節點;
* 除了根節點外,每個子節點可以分爲多個不相交的子樹。

常見的樹:

  1. 二叉樹
    二叉樹是每個節點最多有兩個子樹的樹結構。它有五種基本形態:二叉樹可以是空集;根可以有空的左子樹或右子樹;或者左、右子樹皆爲空。
    二叉樹有以下幾個性質:
    • 性質1:二叉樹第i層上的結點數目最多爲 2{i-1} (i≥1)。
    • 性質2:深度爲k的二叉樹至多有2{k}-1個結點(k≥1)。
    • 性質3:包含n個結點的二叉樹的高度至少爲log2 (n+1)。
    • 性質4:在任意一棵二叉樹中,若終端結點的個數爲n0,度爲2的結點數爲n2,則n0=n2+1。
  2. AVL樹
    AVL樹是根據它的發明者G.M. Adelson-Velsky和E.M. Landis命名的。
    它是最先發明的自平衡二叉查找樹,也被稱爲高度平衡樹。相比於”二叉查找樹”,它的特點是:AVL樹中任何節點的兩個子樹的高度最大差別爲1。
  3. 伸展樹
    伸展樹(Splay Tree)是一種二叉排序樹,它能在O(log n)內完成插入、查找和刪除操作。它由Daniel Sleator和Robert Tarjan創造。
    伸展樹屬於二叉查找樹,即它具有和二叉查找樹一樣的性質:假設x爲樹中的任意一個結點,x節點包含關鍵字key,節點x的key值記爲key[x]。如果y是x的左子樹中的一個結點,則key[y] <= key[x];如果y是x的右子樹的一個結點,則key[y] >= key[x]。 >
    除了擁有二叉查找樹的性質之外,伸展樹還具有的一個特點是:當某個節點被訪問時,伸展樹會通過旋轉使該節點成爲樹根。這樣做的好處是,下次要訪問該節點時,能夠迅速的訪問到該節點。
    假設想要對一個二叉查找樹執行一系列的查找操作。爲了使整個查找時間更小,被查頻率高的那些條目就應當經常處於靠近樹根的位置。於是想到設計一個簡單方法,在每次查找之後對樹進行重構,把被查找的條目搬移到離樹根近一些的地方。伸展樹應運而生,它是一種自調整形式的二叉查找樹,它會沿着從某個節點到樹根之間的路徑,通過一系列的旋轉把這個節點搬移到樹根去。
  4. 哈弗曼樹
    Huffman Tree,中文名是哈夫曼樹或霍夫曼樹,它是最優二叉樹。
    定義:給定n個權值作爲n個葉子結點,構造一棵二叉樹,若樹的帶權路徑長度達到最小,則這棵樹被稱爲哈夫曼樹。
  5. 紅黑樹
    R-B Tree,全稱是Red-Black Tree,又稱爲“紅黑樹”,它一種特殊的二叉查找樹。紅黑樹的每個節點上都有存儲位表示節點的顏色,可以是紅(Red)或黑(Black)。
    紅黑樹的特性:
    • 每個節點或者是黑色,或者是紅色。
    • 根節點是黑色。
    • 每個葉子節點(NIL)是黑色。 [注意:這裏葉子節點,是指爲空(NIL或NULL)的葉子節點!]
    • 如果一個節點是紅色的,則它的子節點必須是黑色的。
    • 從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。

2. 堆

堆(heap),這裏所說的堆是數據結構中的堆,而不是內存模型中的堆。堆通常是一個可以被看做一棵樹,它滿足下列性質:

  • [性質一] 堆中某個節點的值總是不大於或不小於其父節點的值;
  • [性質二] 堆總是一棵完全樹。
    將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。常見的堆有二叉堆、左傾堆、斜堆、斐波那契堆等等。

    1. 二叉堆
      二叉堆是完全二元樹或者是近似完全二元樹,它分爲兩種:最大堆和最小堆。

      • 最大堆:父結點的鍵值總是大於或等於任何一個子節點的鍵值;
      • 最小堆:父結點的鍵值總是小於或等於任何一個子節點的鍵值。
    2. 左傾堆
      左傾堆是一棵二叉樹,它的節點除了和二叉樹的節點一樣具有左右子樹指針外,還有兩個屬性:
      鍵值和零距離。

      • 鍵值的作用是來比較節點的大小,從而對節點進行排序。
      • 零距離(英文名NPL,即Null Path Length)則是從一個節點到一個”最近的不滿節點”的路徑長度。
      • 不滿節點是指該該節點的左右孩子至少有有一個爲NULL。葉節點的NPL爲0,NULL節點的NPL爲-1。
        左傾堆的基本性質:
        [性質1] 節點的鍵值小於或等於它的左右子節點的鍵值。
        [性質2] 節點的左孩子的NPL >= 右孩子的NPL。
        [性質3] 節點的NPL = 它的右孩子的NPL + 1。
    3. 斜堆
      斜堆(Skew heap)也叫自適應堆(self-adjusting heap),它是左傾堆的一個變種。它的合併操作的時間複雜度也是O(log n)。
      相比於左傾堆,斜堆的節點沒有”零距離”這個屬性。除此之外,它們斜堆的合併操作也不同。斜堆的合併操作算法如下:

      • 如果一個空斜堆與一個非空斜堆合併,返回非空斜堆。
      • 如果兩個斜堆都非空,那麼比較兩個根節點,取較小堆的根節點爲新的根節點。將”較小堆的根節點的右孩子”和”較大堆”進行合併。
      • 合併後,交換新堆根節點的左孩子和右孩子。
    4. 二項堆
      二項堆是二項樹的集合。
      二項樹是一種遞歸定義的有序樹。它的遞歸定義如下:

      • 二項樹B0只有一個結點;
      • 二項樹Bk由兩棵二項樹B(k-1)組成的,其中一棵樹是另一棵樹根的最左孩子。
        二項堆通常被用來實現優先隊列,它堆是指滿足以下性質的二項樹的集合:
      • 每棵二項樹都滿足最小堆性質。即,父節點的關鍵字 <= 它的孩子的關鍵字。
      • 不能有兩棵或以上的二項樹具有相同的度數(包括度數爲0)。換句話說,具有度數k的二項樹有0個或1個。
    5. 斐波那契堆
      斐波那契堆(Fibonacci heap)是堆中一種,它和二項堆一樣,也是一種可合併堆;可用於實現合併優先隊列。斐波那契堆比二項堆具有更好的平攤分析性能,它的合併操作的時間複雜度是O(1)。

      • 與二項堆一樣,它也是由一組堆最小有序樹組成,並且是一種可合併堆。
      • 與二項堆不同的是,斐波那契堆中的樹不一定是二項樹;而且二項堆中的樹是有序排列的,但是斐波那契堆中的樹都是有根而無序的。

3. 圖

由有窮、非空點集和邊集合組成,簡寫成G(V,E).

  1. 鄰接矩陣無向圖的介紹
    鄰接矩陣無向圖是指通過鄰接矩陣表示的無向圖。
  2. 鄰接表無向圖的介紹
    鄰接表無向圖是指通過鄰接表表示的無向圖。
  3. 鄰接矩陣有向圖的介紹
    鄰接矩陣有向圖是指通過鄰接矩陣表示的有向圖。
  4. 鄰接表有向圖的介紹
    鄰接表有向圖是指通過鄰接表表示的有向圖。

圖相關算法介紹:

  1. 深度優先搜索介紹
    圖的深度優先搜索(Depth First Search),和樹的先序遍歷比較類似。
    它的思想:
    假設初始狀態是圖中所有頂點均未被訪問,則從某個頂點v出發,首先訪問該頂點,然後依次從它的各個未被訪問的鄰接點出發深度優先搜索遍歷圖,直至圖中所有和v有路徑相通的頂點都被訪問到。 若此時尚有其他頂點未被訪問到,則另選一個未被訪問的頂點作起始點,重複上述過程,直至圖中所有頂點都被訪問到爲止。
  2. 拓撲排序介紹
    拓撲排序(Topological Order)是指,將一個有向無環圖(Directed Acyclic Graph簡稱DAG)進行排序進而得到一個有序的線性序列。
    這樣說,可能理解起來比較抽象。下面通過簡單的例子進行說明!
    例如,一個項目包括A、B、C、D四個子部分來完成,並且A依賴於B和D,C依賴於D。現在要制定一個計劃,寫出A、B、C、D的執行順序。這時,就可以利用到拓撲排序,它就是用來確定事物發生的順序的。
    在拓撲排序中,如果存在一條從頂點A到頂點B的路徑,那麼在排序結果中B出現在A的後面。
  3. 克魯斯卡爾算法介紹
    克魯斯卡爾(Kruskal)算法,是用來求加權連通圖的最小生成樹的算法。
    基本思想:按照權值從小到大的順序選擇n-1條邊,並保證這n-1條邊不構成迴路。
    具體做法:首先構造一個只含n個頂點的森林,然後依權值從小到大從連通網中選擇邊加入到森林中,並使森林中不產生迴路,直至森林變成一棵樹爲止。
  4. 普里姆算法介紹
    普里姆(Prim)算法,和克魯斯卡爾算法一樣,是用來求加權連通圖的最小生成樹的算法。
    基本思想:對於圖G而言,V是所有頂點的集合;現在,設置兩個新的集合U和T,其中U用於存放G的最小生成樹中的頂點,T存放G的最小生成樹中的邊。 從所有uЄU,vЄ(V-U) (V-U表示出去U的所有頂點)的邊中選取權值最小的邊(u, v),將頂點v加入集合U中,將邊(u, v)加入集合T中,如此不斷重複,直到U=V爲止,最小生成樹構造完畢,這時集合T中包含了最小生成樹中的所有邊。
  5. 迪傑斯特拉算法介紹
    迪傑斯特拉(Dijkstra)算法是典型最短路徑算法,用於計算一個節點到其他節點的最短路徑。
    它的主要特點是以起始點爲中心向外層層擴展(廣度優先搜索思想),直到擴展到終點爲止。
    基本思想:通過Dijkstra計算圖G中的最短路徑時,需要指定起點s(即從頂點s開始計算)。
    此外,引進兩個集合S和U。S的作用是記錄已求出最短路徑的頂點(以及相應的最短路徑長度),而U則是記錄還未求出最短路徑的頂點(以及該頂點到起點s的距離)。
    初始時,S中只有起點s;U中是除s之外的頂點,並且U中頂點的路徑是”起點s到該頂點的路徑”。然後,從U中找出路徑最短的頂點,並將其加入到S中;接着,更新U中的頂點和頂點對應的路徑。 然後,再從U中找出路徑最短的頂點,並將其加入到S中;接着,更新U中的頂點和頂點對應的路徑。 … 重複該操作,直到遍歷完所有頂點。

四、算法

衡量算法的標準:

  • 時間複雜度
  • 空間複雜度
  • 難易程度
  • 健壯性

常用算法介紹:

  1. 冒泡排序介紹
    冒泡排序(Bubble Sort),又被稱爲氣泡排序或泡沫排序。
    它是一種較簡單的排序算法。它會遍歷若干次要排序的數列,每次遍歷時,它都會從前往後依次的比較相鄰兩個數的大小;如果前者比後者大,則交換它們的位置。這樣,一次遍歷之後,最大的元素就在數列的末尾! 採用相同的方法再次遍歷時,第二大的元素就被排列在最大元素之前。重複此操作,直到整個數列都有序爲止!
  2. 快速排序介紹
    快速排序(Quick Sort)使用分治法策略。
    它的基本思想是:選擇一個基準數,通過一趟排序將要排序的數據分割成獨立的兩部分;其中一部分的所有數據都比另外一部分的所有數據都要小。然後,再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。
  3. 直接插入排序介紹
    直接插入排序(Straight Insertion Sort)的基本思想是:把n個待排序的元素看成爲一個有序表和一個無序表。開始時有序表中只包含1個元素,無序表中包含有n-1個元素,排序過程中每次從無序表中取出第一個元素,將它插入到有序表中的適當位置,使之成爲新的有序表,重複n-1次可完成排序過程。
  4. 希爾排序介紹
    希爾排序(Shell Sort)是插入排序的一種,它是針對直接插入排序算法的改進。該方法又稱縮小增量排序,因DL.Shell於1959年提出而得名。
    希爾排序實質上是一種分組插入方法。它的基本思想是:對於n個待排序的數列,取一個小於n的整數gap(gap被稱爲步長)將待排序元素分成若干個組子序列,所有距離爲gap的倍數的記錄放在同一個組中;然後,對各組內的元素進行直接插入排序。 這一趟排序完成之後,每一個組的元素都是有序的。然後減小gap的值,並重復執行上述的分組和排序。重複這樣的操作,當gap=1時,整個數列就是有序的。
  5. 選擇排序介紹
    選擇排序(Selection sort)是一種簡單直觀的排序算法。
    它的基本思想是:首先在未排序的數列中找到最小(or最大)元素,然後將其存放到數列的起始位置;接着,再從剩餘未排序的元素中繼續尋找最小(or最大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
  6. 堆排序介紹
    堆排序(Heap Sort)是指利用堆這種數據結構所設計的一種排序算法。
    因此,學習堆排序之前,有必要了解堆!若讀者不熟悉堆,建議先學習”二叉堆”,然後再來學習本章。
    我們知道,堆分爲”最大堆”和”最小堆”。最大堆通常被用來進行”升序”排序,而最小堆通常被用來進行”降序”排序。 鑑於最大堆和最小堆是對稱關係,理解其中一種即可。本文將對最大堆實現的升序排序進行詳細說明。
  7. 歸併排序介紹
    將兩個的有序數列合併成一個有序數列,我們稱之爲”歸併”。
    歸併排序(Merge Sort)就是利用歸併思想對數列進行排序。根據具體的實現,歸併排序包括”從上往下”和”從下往上”2種方式。

    • 從下往上的歸併排序:將待排序的數列分成若干個長度爲1的子數列,然後將這些數列兩兩合併;得到若干個長度爲2的有序數列,再將這些數列兩兩合併;得到若干個長度爲4的有序數列,再將它們兩兩合併;直接合併成一個數列爲止。這樣就得到了我們想要的排序結果。
    • 從上往下的歸併排序:它與”從下往上”在排序上是反方向的。它基本包括3步:
      1. 分解 – 將當前區間一分爲二,即求分裂點 mid = (low + high)/2;
      2. 求解 – 遞歸地對兩個子區間a[low…mid] 和 a[mid+1…high]進行歸併排序。遞歸的終結條件是子區間長度爲1。
      3. 合併 – 將已排序的兩個子區間a[low…mid]和 a[mid+1…high]歸併爲一個有序的區間a[low…high]。
  8. 桶排序介紹
    桶排序(Bucket Sort)的原理很簡單,它是將數組分到有限數量的桶子裏。
    假設待排序的數組a中共有N個整數,並且已知數組a中數據的範圍[0, MAX)。在桶排序時,創建容量爲MAX的桶數組r,並將桶數組元素都初始化爲0;將容量爲MAX的桶數組中的每一個單元都看作一個”桶”。
    在排序時,逐個遍歷數組a,將數組a的值,作爲”桶數組r”的下標。當a中數據被讀取時,就將桶的值加1。例如,讀取到數組a[3]=5,則將r[5]的值+1。

  9. 基數排序介紹
    基數排序(Radix Sort)是桶排序的擴展,它的基本思想是:將整數按位數切割成不同的數字,然後按每個位數分別比較。
    具體做法是:將所有待比較數值統一爲同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章