歷時一個禮拜,我終於看完了“大話數據結構”


You got a dream, you gotta protect it. People can't do something themselves, they wanna tell you can't do it. If you want something, go get it. Period.

如果你有夢想的話,那么就要去捍衛它。當別人做不到的時候,他們就想要告訴你,你也不行。如果你想要什么,就得努力去爭取,就這樣。

  這段時間一直在復習,之前看了一遍《Head First Java》,然后看了一遍《深入理解JVM虛擬機》,根據大佬們推薦的后端之路,接下來就是程傑編著的《大話數據結構》了。

  我是本科是學通信的,課程上學過C、C++、Java,但並不像科班生有計算機網絡、操作系統、數據結構等必修課。所以我覺得既不是科班也不是非科班,頂多算半個科班吧。

  記得本科的時候學過一門課叫“計算機軟件基礎”,好像是大一的時候上的吧,那應該是第一次接觸到了“數據結構”,但完全是理論的東西,並沒有實操,而且內容也不像數據結構教材那樣豐富。

  直到今日,我覺得我才算是完整地走了一遍“數據結構”之路。將整個數據結構的知識拉通地走了一遍。(ps:有時候我特別悔恨我當時為什么不在ACM繼續留下去,一直覺得是自己太菜、覺得做題太枯燥。但是現在看來,確實是自己理論知識不過關啊,那個時候就應該將數據結構這本書拿來閱讀一遍。)

  好了,說了這么多,該說正事兒了。《大話數據結構》這本書還是非常友好的,大佬們說非常適合初學者,但是我覺得也適用於有編程經驗並且多多少少已經有一些數據結構基礎的童鞋~

  整本書分為了9章,前面7章講了數據結構及其算法,后面2章分別是“查找”與“排序”,可以說是使用前面講的數據結構的實現的一些常用算法。

  下面分別進行總結,方便以后的復習。

第一章:線性表

  數據稱為”元素“,兩種存儲結構:

  • 順序存儲結構(數組實現)
  • 鏈式存儲結構(結構體、指針)
    • 單鏈表(僅有指向后繼結點的指針)
    • 靜態鏈表(特殊,通過數組實現。針對一些沒有指針引用的語言)
    • 循環鏈表(將終端結點指針指向頭結點)
    • 雙向鏈表(既指向前繼,也有指向后繼)

第二章:棧與隊列

  數據稱為”元素“,兩種存儲結構。

2.1 棧

  • 順序存儲結構(數組實現)
  • 鏈式存儲結構(鏈棧)(結構體、指針)

2.2 隊列

  • 順序存儲結構(數組實現)
    • 普通隊列(一般不使用,可能會存在假溢出
    • 循環隊列
  • 鏈式存儲結構(鏈隊列)(結構體、指針)

第三章:串

  串(string)是由零個或多個字符組成的有限序列,又名“字符串”。

  • 順序存儲結構(數組實現,一般用法)
  • 鏈式存儲結構(結構體、指針)

3.1 朴素的模式匹配算法

  子串在串中的定位操作通常稱為串的模式匹配

  朴素的模式匹配算法,顯得很低效。

3.2 KMP模式匹配算法

  可以大大避免重復遍歷的情況。

第四章:樹

  以上都是一對一的數據結構,樹是一對多的數據結構。

4.1 結點分類

  結點擁有的子樹數稱為結點的度(Degree)。度為0的結點稱為葉子結點(Leaf)或終端結點。樹的度是樹內各結點的度的最大值。

4.2 結點間關系

  結點的子樹的跟稱為該結點的孩子(Child),相應的,該結點稱為該孩子的雙親(Parent)。同一個雙親的孩子之間稱為兄弟(Sibling)。

4.3 樹的其它概念

  • 結點的層次
  • 樹的深度
  • 森林:是m棵互不相交的樹的集合

4.4 樹的存儲結構

  存儲結構為結構體+數組

  • 雙親表示法
  • 孩子表示法
  • 孩子兄弟表示法(基本上就是二叉樹的原型

4.5 二叉樹

  • 斜樹
  • 滿二叉樹
  • 完全二叉樹

4.6 二叉樹的性質

  p169

4.7 二叉樹的存儲結構

  • 順序存儲結構(數組),一般只用於完全二叉樹
  • 鏈式存儲結構(二叉鏈表),常用存儲方式

4.8 遍歷二叉樹

  • 前序遍歷
  • 中序遍歷
  • 后序遍歷
  • 層次遍歷

4.9 創建二叉樹

  參考遍歷方式。

4.10 線索二叉樹

4.11 樹、二叉樹、森林的轉換

4.12 赫夫曼樹及其應用

  壓縮的原理。

第五章:圖

  數據元素稱為“頂點”

  • 網(帶權值的圖)
  • 連通圖(頂點互通)
    • 連通分量
  • 生成樹:n個頂點,n-1條邊
  • 有向樹

5.1 圖的存儲

  • 鄰接矩陣
    • 一維數組存頂點
    • 二維數組存邊
  • 鄰接表
    • 一維數組存頂點
    • 單鏈表存邊
  • 十字鏈表(針對有向圖)
  • 鄰接多重表
  • 邊集數組

5.2 圖的遍歷

  • 深度優先遍歷(DFS),遞歸的方式
  • 廣度優先遍歷(BFS),非遞歸

5.3 最小生成樹

  帶權值的網圖,n個頂點,n-1條邊把一個連通圖串起來。並使得權值的和最小。

  • 普利姆算法(Prim)(以某頂點為起點,找到最小的邊)
  • 克魯斯卡爾算法(以某邊為起點)

5.4 最短路徑

  • Dijktra(得到單一頂點到其它所有點的最短路徑)
  • Floyd(得到所有頂點到所有頂點的路徑)

5.5 拓撲排序

  可用於判斷一個給定的圖是否有有向無環圖。

第六章:查找

  • 順序表查找
  • 有序表查找
    • 折半查找
    • 插值查找
    • 斐波那契查找
  • 索引查找
    • 線性索引
      • 稠密索引
      • 分塊索引
      • 倒排索引
    • 樹形索引
    • 多級索引
  • 二叉排序樹(動態查找,可在查找的時候插入、刪除)
    • 平衡二叉樹
    • 多路查找樹
  • 散列表查找(哈希表hashtable),主要是面向查找的存儲結構
    • 處理三列沖突的方法
      • 開放定址法
        • 線性探測
        • 二次探測
        • 隨機探測
      • 再散列函數法
      • 鏈地址法Java集合中HashTable的查原理用是的就是這種方法)

  這一章令我最印象深刻的就是散列表查找了。因為之前在學習Java集合的時候,接觸過HashTable集合。它里面的查邏輯就是使用的散列表查詢。那段時間對於hash還是很懵懂的樣子,現在終於搞懂了其中的奧妙~

第七章:排序

  不多說,直接上代碼!

#include <stdio.h>
#include <stdlib.h>

#define N 500

#define MAXSIZE 10
#define MAX_LENGTH_INSERT_SORT 7
#define TRUE 1
#define FALSE 0

typedef int BOOL;

typedef struct{

    // r[0]一般作為哨兵或者臨時變量
    int r[MAXSIZE+1];
    int length;

}SqList;

void swap(SqList *L, int i, int j){

    int temp = L->r[i];
    L->r[i] = L->r[j];
    L->r[j] = temp;

}

/* 對順序表L作交換排序(冒泡排序初版) */
void BubbleSort0(SqList *L){

    int i,j;
    for(i = 1; i < L->length; i++){
        for(j = i+1; j <= L->length; j++){

            if(L->r[i]>L->r[j]){
                swap(L,i,j);
            }
        }
    }
}

/* 正宗冒泡排序算法*/
void BubbleSort1(SqList *L){

    int i,j;
    for(i = 1; i < L->length; i++){
        for(j = L->length-1; j>=i; j--){
            if(L->r[j] > L->r[j+1])
                swap(L, j, j+1);
        }
    }

}

/* 冒泡排序算法的優化,常用此算法 */
void BubbleSort2(SqList *L){

    int i,j;
    BOOL flag = TRUE;
    for(i = 1; i < L->length && flag; i++){

        flag = FALSE;
        for(j = L->length-1; j>=i; j--){
            if(L->r[j] > L->r[j+1]){
                swap(L, j, j+1);
                flag = TRUE;
            }
        }
    }
}

/* 直接插入排序,性能是簡單排序里最好的 */
void InsertSort(SqList *L){

    int i, j;
    for(i = 2; i <= L->length; i++){
        if(L->r[i] < L->r[i-1]){
            // 新來的卡牌設置為哨兵,作為后面的循環終止的判斷依據
            L->r[0] = L->r[i];
            for(j = i-1; L->r[j]>L->r[0]; j--){
                // 記錄后移
                L->r[j+1] = L->r[j];
            }
            // 將牌插入到正確的位置
            L->r[j+1] = L->r[0];
        }
    }
}

/* 對順序表L作歸並排序 */
void MergeSort(SqList *L){

    MSort(L->r, L->r, 1, L->length);

}

/* 將有序的SR[i..m]和SR[m+1..n]歸並為有序的TR[i..n] */
void Merge(int SR[], int TR[], int i, int m, int n){

    int j,k,l;
    for(j=m+1,k=i; i<=m && j<=n; k++){
        if(SR[i]<SR[i++])
            TR[k] = SR[i++];
        else
            TR[k] = SR[j++];
    }

    // 將剩余的SR[i..m]復制到TR
    if(i<=m){
        for(l=0;l<=m-i;l++)
            TR[k+l] = SR[i+l];
    }

    // 將剩余的SR[j..n]復制到TR
    if(j<=n){
        for(l=0;l<=n-j;l++){
            TR[k+l] = SR[j+l];
        }
    }
}

/* 將SR[s..t]歸並排序為TR1[s..t] */
void MSort(int SR[], int TR1[], int s, int t){

    int m;
    int TR2[MAXSIZE+1];
    if(s == t){
        TR1[1] = SR[s];
    }
    else{

        // 將SR[s..t]平分為SR[s..m]和SR[m+1..t]
        m = (s+t)/2;
        // 將SR[s..t]歸並排序為TR2[s..m]
        MSort(SR, TR2, s, m);
        // 將SR[m+1..t]歸並排序為TR2[m+1..t]
        MSort(SR, TR2, m+1, t);
        // 將TR2[s..m]和TR2[m+1..t]歸並到TR1[s..t]
        Merge(TR2, TR1, s, m, t);
    }
}

/* 將SR[]中相鄰長度為s的子序列兩兩歸並到TR[] */
void MergePass(int SR[], int TR[], int s, int n){

    int i = 1;
    int j;
    while(i <= n-2*s+1){

        // 兩兩歸並
        Merge(SR, TR, i, i+s-1, i+2*s-1);
        i = i+2*s;

    }

    // 歸並最后兩個序列
    if(i < n-s+1)
        Merge(SR, TR, i, i+s-1, n);
    else
        for(j = i; j <= n; j++)
            TR[j] = SR[j];
}

/* 對順序表L作非遞歸排序,歸並排序常用此算法 */
void MergeSort2(SqList *L){
    // 申請額外空間
    int* TR = (int*)malloc(L->length * sizeof(int));
    int k = 1;
    while(k<L->length){

        MergePass(L->r, TR, k, L->length);
        // 子序列長度加倍
        k = 2*k;
        MergePass(TR, L->r, k, L->length);
        // 子序列長度加倍
        k = 2*k;

    }
}

/* 快速排序優化算法
   交換順序表L中子表的記錄,使樞軸記錄到位,並返回其所在位置
   此時在它之前(后)的記錄均不大(小)於它
*/
int Partition(SqList *L, int low, int high){

    int pivotkey;
    // 計算數組中間的元素的下標
    int m = low + (high - low)/2;
    // 交換左端與右端數據,保證左端較小
    if(L->r[low] > L->r[high])
        swap(L, low, high);
    // 交換中間與右端數據,保證中間較小
    if(L->r[m] > L->r[high])
        swap(L, high, m);
    // 交換中間與左端數據,保證左端居中,此時L->r[low]已為整個序列左中右三個關鍵字的中間值
    if(L->r[m] > L->r[low])
        swap(L, m, low);

    // 用子表的第一個記錄作樞軸記錄
    pivotkey = L->r[low];

    // 將樞軸關鍵字備份到r[0]
    L->r[0] = pivotkey;
    while(low < high){

        while(low < high && L->r[high] >= pivotkey)
            high--;
        // 將比樞軸記錄小的記錄替換到低端
        L->r[low] = L->r[high];
        while(low < high && L->r[low] <= pivotkey)
            low++;
        // 將比樞軸記錄大的記錄替換到高端
        L->r[high] = L->r[low];
    }
    // 將樞軸記錄替換回L->r[low]
    L->r[low] = L->r[0];
    return low;
}

/* 對順序表L中的子序列L->r[low..high]進行快速排序 */
void QSort(SqList *L, int low, int high){

    int pivot;

    if((high - low) > MAX_LENGTH_INSERT_SORT){
        while(low < high){

            /* 將L->r[low..high]一分為二,並算出樞軸值pivot */
            pivot = Partition(L, low, high);

            // 對低子表遞歸排序
            QSort(L, low, pivot-1);
            // 尾遞歸(對高子表進行遞歸排序)
            low = pivot + 1;
        }
    }
    else
        InsertSort(L);

}


/* 對順序表L作快速排序 */
void QuickSort(SqList *L){

    QSort(L, 1, L->length);

}

int main()
{
    SqList s;
    int temp[10] = {99,1,5,18,3,7,4,6,2,11};
    memcpy(s.r, temp, 10*sizeof(int));
    s.length = 9;

    QuickSort(&s);

    for(int i = 1; i <= s.length; i++){
        printf("%d ", s.r[i]);
    }
    printf("\n");

    printf("Hello world!\n");

    return 0;
}

  以后麻麻再也不用擔心我不會寫快排啦~


免責聲明!

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



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