基本數據結構


眾所周知, 數據結構分為以下四個方面 :

1. 集合 ( 結點之間沒什么聯系, 不需要總結 )

2. 線性 ( 一條直線 )

3. 樹狀 ( 類似家譜 )

4. 圖  ( 難, 暫時先不總結 )

數據結構的定義: 分為結點的定義和結點之間關系的定義.


 

線性結構

- 順序表

typedef struct {

    int elem[100];

    int length;  // 這里的lenth是指當前分配的長度

} SqList;

由以上結構可以看出, 結點的值存儲在 elem 中,而結點之間的關系就是數組隱含, 所以不需要另外在定義關系.

- 單鏈表

typedef struct LNode{

    int elem;

    struct LNode *next;

} LNode, *LinkList;

結點: LNode 是用來保存結點的

關系: LinkList 就是鏈表頭指針, 關系是通過 next 指針聯系起來的.

頭指針: LinkList 就是頭指針, 指向頭結點的指針.

頭結點: (1)對帶頭結點的鏈表, 在表的任何結點之前插入結點或刪除表中任何結點, 所要做的都是修改前一個結點的指針域, 而任何元素都有前驅結點, 若鏈表沒有頭結點, 則首元素結點沒有前驅結點, 在其前插入結點或刪除結點時操作會復雜些.(2)對帶頭結點的鏈表, 表頭指針時指向結點的非空指針, 因此空表與非空表處理是一樣的.

- 循環鏈表

所謂循環鏈表, 其實

結點: 存儲情況, 同上邊完全一樣.

關系: 頭指針的 next 指向自己, 這樣的話就是循環鏈表了, 當插入結點時, 新的結點 p->next = L->next(指向頭結點).

- 雙向鏈表

typedef struct DLNode {

    int elem;

    struct DLNode *prior;

    struct DLNode *next;

} DLNode, *DLinkList;

結點: DLNode

結構: 頭指針 DLinkList, 通過 next 和 prior 來反映元素之間的線性關系.

- 靜態鏈表

所謂靜態鏈表: 是指用數組模擬操作, 實現的鏈表, 其中指針域, 使用數組下標表示.

typedef struct {

    int elem;

    int next;

} SLNode, slinklist[MAXSIZE];

結點: SLNode, 其中的 next 就是模擬指針.

關系: slinklist 是一個SLNode的數組, 數組中的 next 隱含關系.

棧和隊列: 是限制操作的線性表

- 順序棧 

typedef struct {

    int elem[100];

    int top;

} SqStack;

結點: 數組中的元素;

關系: SqStack.

為什么沒有鏈式棧, 因為棧這種結構限制了, 后進先出, 即只能從棧頂出戰, 即 top 會記錄棧頂位置, 所以它雖然是順序結構, 但是插入和刪除操作並不需要移動元素, 所以, 當然是順序棧好一些.

- 順序隊列( 循環隊列 )  

typedef struct {

    int elem[100];

    int front;

    int rear;

} SqQueue;

結點: elem數組中的元素

關系: 隱含在數組中, 注意 front 和 rear 的位置, 關系還是隱含在數組中, 隊列是先進先出, front 記錄了隊列頭, rear 記錄了隊列尾, 從 front出, rear進, 注意隊列判空和判滿條件: 如下

因為 出隊列時, 頭指針 front 會向后移動, 此時, 前一個存儲區域雖然出隊列了, 但是仍然占據了存儲空間沒有釋放, 這樣就勢必造成了空間的浪費, 這樣最好的辦法是使用循環隊列, 但是循環隊列如何判空和判滿呢?

 image

如上圖: 從結構上看, 隊列里只剩下了 3 個存儲單元, 前邊浪費了大量存儲空間, 所以要使用循環隊列, 並且不能通過 front == rear 來簡單的判斷判空或判滿, 浪費一個存儲空間, 即 (rear + 1) % 存儲空間 = front, 則判斷為慢, 關鍵看誰最上誰, 如果 front追上rear 空隊列, 如果是 rear追上了 front滿隊列. 為什么要 (rear + 1)%存儲空間呢? 因為當 rear已經在數組最右邊時, 如果單純的 rear+1, 那么已經超過數組最大范圍, 但是(rear+1)%存儲空間, 如果 rear+1沒有超過存儲空間, 那么取模與不取模操作都一樣, 但是如果 rear+1超過了數組范圍,那么取模以后, 又回到了第一個了, 這樣就達到了循環的目的, 而 (rear+1)%存儲空間 == front 表示 rear 已經循環到了 front的前一個存儲空間了.

- 鏈式隊列

typedef struct Qnode {

    int elem;

    struct Qnode *next;

} Qnode, *Qlink;

typedef struct SQlink {

    Qlink front;

    Qlink rear;

} *linkqueue;

結點: Qnode

關系: linkqueue

注意: 鏈式隊列不需要循環隊列, 因為不存在空間浪費的情況, 當有出隊列的結點時, 直接釋放該結點的內存就可以了.


數組相關, 矩陣壓縮存儲

- 三元組

typedef struct {

    int i, j; // 非零元 的行和列

    int elem;

} Tripe;

typedef struct {

    Tripe Matrix[MAX_SIZE];

    int mu, nu, tu;  // 矩陣的行, 列數, 及非零元個數

} TMatrix;

結點: Tripe;

關系: TMatrix

特點: 非零元在數組中按行邏輯順序存儲便於進行依次順序處理矩陣運算, 但是, 如果我想找到一行的非零元, 就比較麻煩, 還是需要從頭開始找, 由此引出 行邏輯鏈接順序表存儲法.

- 行邏輯鏈接順序表

typedef struct {

    int i, j; // 非零元 的行和列

    int elem;

} Tripe;

typedef struct {

    Tripe Matrix[MAX_SIZE];

    int mu, nu, tu; // 矩陣的行, 列數, 及非零元個數

    int rpos[MAXRC+1];  // 各行第一個非零元的位置表

} LMatrix;

結點: Tripe

關系: LMatrix

這個存儲結構跟三元組基本上一樣, 只是多了一個記錄在數組中, 第幾個元素還是是第幾行的開始非零元. 這里的 rpos[MAXRC+1] 記錄的是第幾行在數組中的非零元的起始位置, 例如 rpos[2] = 5 表示 第 2 行非零元的起始位置, 在Matrix=[5]

- 十字鏈表存儲發   

以上的存儲方式, 說白了, 還是順序存儲, 如果矩陣非零元個數和位置變化較大, 就比較適合使用鏈式存儲結構.

typedef struct mxtripe {

    int elem;

    int i, j;

    struct mxtripe *right;

    struct mxtripe *end;

}MxTripe, *OLink;

typedef struct {

    OLink *rhead;  // rhead 指向的是一個行向量, 該向量指向 元素類型

    OLink *chead;  // chead 指向的是一個列向量, 該向量指向 元素類型

    int mu, nu, tu;

}CrossList;

rhead, chead 指向的是向量的首地址, 即數組.

image

可見 rhead 指向 行級指針數組, chead 指向 列級指針數組.

十字鏈表在做矩陣運算時非常方便.


- 樹的雙親表示法

typedef struct treenode {

   int elem;

   int parent;

} PT;

typedef struct {

    PT nodes[MAX_TREE_SIZE];

    int r, n;  // 根結點和結點總數

} PTree;

image

結點: PT

關系: PTree, 其中關系也是隱含在結點的 parent中.

這種存儲方式, 很顯然, 找兒子特別困難. 找parent相對容易.

- 樹的孩子鏈表 表示法

typedef struct CTNode {  // 孩子結點, 此節點如果缺少 child, 保存信息並不完整

    int child;  // 在數組中的下標

    struct CTNode *next;

} *ChildPtr;

typedef struct {  // 樹中的結點

    int data;

    ChildPtr firstchild;  // 孩子鏈表頭指針

} CTBox;

typedef struct {  // 樹結構

    CTBox nodes[MAX_TREE_SIZE];

    int n,r;  // 結點數 和 根位置( 在數組中 )

} CTree;

此種結構, 找到孩子很容易, 但是由孩子找 parent 就很麻煩.

image

- 樹的孩子兄弟 表示法( 也叫二叉樹表示法或二叉鏈表 表示法 ) 推薦

typedef struct CSNode {

    int elem;

    struct CSNode *firstchild, *nextsibling;  // 左孩子, 右兄弟

} CSNode, *CSTree;

結點: CSNode

關系: 首先定義一個結點為根結點, 然后利用 firstchild 指針指向第一個孩子, 依次繼續, 具體結構圖, 如下:

image

二叉樹

- 順序存儲結構

typedef TelemType SqBiTree[MAX_TREE_SIZE];

SqBiTree bt;

結點: 存放在數組中.

關系: 通過結點存放在數組中的位置來判斷結點之間的關系.

缺點: 浪費很多存儲空間, 另外結點之間的關系不明顯. 下圖中黃顏色的全部是浪費的, 而且還有很多浪費的, 因為是按照完全二叉樹的方式存儲的.

image

- 二叉樹, 二叉鏈表表示法

typedef struct BiNode {

    int elem;

    struct BiNode *leftChild, *rightChild;

} BiNode, *BiTree;

因為 二叉樹的特點是最多只有2個兒子, 所以可以分為左右兩個兒子, 然后進行存儲.

結點: BiNode

關系: leftchild, rigthchild

可以看到, 這種方式的存儲方法, 跟實際畫圖是一樣的. 而且這種方式很像 左孩子又兄弟表示法, 這也是樹與二叉樹互換的依據.

image

- 二叉樹, 三叉鏈表表示法

typedef struct BiNode {

    int elem;

    struct BiNode *leftChild, *rightChild, *parent;

} BiNode, *BiTree;

從定義上可以看出, 對邊二叉鏈表表示法, 只是多了個指針指向 parent .

結點: BiNode

關系: leftchild, rightchild, parent

image

樹的遍歷

先序遍歷: 根左右

中序遍歷: 左根右

后續遍歷: 左右根

森林與二叉樹的轉換

由於二叉樹和樹都可以用 二叉鏈表作為存儲結構, 那么以二叉鏈表作為媒介可導出樹與二叉樹之間的一個對應關系, 從物理上, 他們的二叉表是相同的, 只是解釋不同. 如下圖:

image

image


免責聲明!

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



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