1. 程式人生 > 其它 >資料結構(1)—— 線性表

資料結構(1)—— 線性表

寫在前面

為了考研,需要複習資料結構。而對於資料結構這門學科來說,寫程式碼是非常必要的。用程式碼把一些常見的資料結構與演算法實現一遍,非常有利於對於資料結構的理解。

於是今天是第一章,即線性表的實現。主要參考是王道的資料結構複習指導與黑皮的嚴蔚敏的教材。雖然程式碼檔案字尾都是cpp,但寫法還是C的寫法,主要借用了cpp裡的&bool來方便書寫。

靜態儲存的順序表實現

注意點

靜態儲存的順序表,其實就是用陣列來實現,主要注意點在於陣列的下標和順序表的位序並不是一一對應的關係,而是差1(順序表的位序 = 陣列的下標 + 1)。要注意這點。

程式碼

/*
 * @Description: 靜態儲存的順序表實現
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-05 21:39:06
 */
#include<bits/stdc++.h>
#define maxSize 10

// 定義靜態儲存的順序表結構
typedef struct{
    int data[maxSize];
    int length;
}SqList;

// 初始化
void initList(SqList &L){
    // 將所有初值設定為0
    for(int i = 0;i < maxSize;i++){
        L.data[i] = 0;
    }
    // 長度設定為0
    L.length = 0;
}

// 插入
bool listInsert(SqList &L,int i,int e){
    // 判斷i的範圍是否有效
    if(i < 1 || i>L.length+1){
        return false;
    }
    // 判斷儲存空間是否已經滿
    if(L.length >= maxSize){
        return false;
    }
    // 將第i個元素之後的元素後移
    for(int j=L.length;j >= i;j--){
        L.data[j] = L.data[j-1];
    }
    // 在位置i處放入e
    L.data[i-1] = e;
    L.length++;
    return true;
}

// 列印順序表
void printList(SqList &L){
    printf("順序表的元素為:\n");
    for(int i = 0;i < L.length; i++){
        printf("%d  ",L.data[i]);
    }
    printf("\n");
}

// 刪除
bool listDelete(SqList &L,int i,int &e){
    // 判斷i的範圍
    if(i < 1 || i > L.length){
        return false;
    }
    // 將被刪除的元素賦值給e
    e = L.data[i-1];
    // 將第i個位置後的元素前移
    for(int j=i;j<L.length;j++){
        L.data[j-1] = L.data[j];
    }
    L.length--;
    return true;
}

// 按值查詢
int locateElem(SqList L,int e){
    int i;
    for(i = 0;i < L.length;i++){
        if(L.data[i] == e){
            return i+1;
        }
    }
    return 0;
}

// 按位序查詢
int getElem(SqList L,int i){
    return L.data[i-1];
}

// 主程式測試
int main(){
    SqList L;
    // 初始化
    initList(L);
    printList(L);
    // 插入幾個元素看看
    listInsert(L,1,1);
    listInsert(L,2,2);
    listInsert(L,3,22);
    printList(L);
    // 刪除一個元素
    int deleteElem = 0;
    listDelete(L,1,deleteElem);
    printList(L);
    // 查詢元素22
    printf("元素22的所在位置為 -> %d\n",locateElem(L,22));
    // 根據位序查詢
    printf("位序為1的位置為 -> %d\n",getElem(L,1));
    return 0;
}

動態儲存的順序表實現

注意點

動態儲存的順序表,相當於是在結構體中儲存一個指標,當容量不夠的時候再去動態申請一波新的空間,把舊的移到新區域就好。其他實現與靜態儲存的順序表差別不大。

程式碼

/*
 * @Description: 動態儲存的順序表實現
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-06 19:18:08
 */
#include<bits/stdc++.h>
#define initSize 3

// 定義動態儲存的順序表結構
typedef struct{
    int *data;
    int maxSize;
    int length;
}SeqList;

// 初始化順序表
void initList(SeqList &L){
    // 用malloc函式申請一片連續的儲存空間
    L.data = (int *)malloc(initSize * sizeof(int));
    // 長度設為0
    L.length = 0;
    // 最大大小設為初始大小
    L.maxSize = initSize;
}

// 動態增加長度
void increaseSize(SeqList &L,int len){
    int *p = L.data;
    // C
    // L.data = (int *) malloc((L.maxSize + len) * sizeof(int));
    // C++
    L.data = new int[L.maxSize + len];
    // 把資料複製到新區域
    for(int i = 0;i < L.length;i++){
        L.data[i] = p[i];
    }
    // 增加長度
    L.maxSize += len;
    // 釋放原有空間
    // C
    // free(p);
    // C++
    delete p;
}

// 插入
bool listInsert(SeqList &L,int i,int e){
    // 判斷i的範圍是否有效
    if(i < 1 || i>L.length+1){
        return false;
    }
    // 判斷儲存空間是否已經滿
    if(L.length >= L.maxSize){
        // 如果已滿,申請更多空間
        // 一次多申請幾個
        increaseSize(L,5);
    }
    // 將第i個元素之後的元素後移
    for(int j=L.length;j >= i;j--){
        L.data[j] = L.data[j-1];
    }
    // 在位置i處放入e
    L.data[i-1] = e;
    L.length++;
    return true;
}

// 列印順序表
void printList(SeqList &L){
    printf("順序表的元素為:\n");
    for(int i = 0;i < L.length; i++){
        printf("%d  ",L.data[i]);
    }
    printf("\n");
}

// 刪除
bool listDelete(SeqList &L,int i,int &e){
    // 判斷i的範圍
    if(i < 1 || i > L.length){
        return false;
    }
    // 將被刪除的元素賦值給e
    e = L.data[i-1];
    // 將第i個位置後的元素前移
    for(int j=i;j<L.length;j++){
        L.data[j-1] = L.data[j];
    }
    L.length--;
    return true;
}

// 按值查詢
int locateElem(SeqList L,int e){
    int i;
    for(i = 0;i < L.length;i++){
        if(L.data[i] == e){
            return i+1;
        }
    }
    return 0;
}

// 按位序查詢
int getElem(SeqList L,int i){
    return L.data[i-1];
}

// 主程式測試
int main(){
    SeqList L;
    // 初始化
    initList(L);
    printList(L);
    // 插入幾個元素看看
    listInsert(L,1,1);
    listInsert(L,2,2);
    listInsert(L,3,22);
    printList(L);
    // 此時已滿,測試能不能繼續插入
    listInsert(L,2,30);
    printList(L);
    // 刪除一個元素
    int deleteElem = 0;
    listDelete(L,1,deleteElem);
    printList(L);
    // 查詢元素30
    printf("元素30的所在位置為 -> %d\n",locateElem(L,30));
    // 根據位序查詢
    printf("位序為1的位置為 -> %d\n",getElem(L,1));
    return 0;
}

帶頭結點的單鏈表實現

注意點

這裡實現的是帶頭結點的單鏈表,不帶頭結點的會在各個函式上有著區別,要注意這點。對於有頭結點的單鏈表,需要注意判空條件為L -> next == NULL,即頭指標指向NULL,否則都為非空。其他需要注意的便是頭插法與尾插法的實現了,這塊為難點和重點。

程式碼

/*
 * @Description: 單鏈表的實現
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-06 20:57:47
 */
#include<bits/stdc++.h>

// 定義帶頭結點的單鏈表
typedef struct LNode{
    int data;
    struct LNode *next;
}LNode,*LinkList;

// 初始化單鏈表
bool initList(LinkList &L){
    // 分配一個頭結點
    L = (LNode *) malloc(sizeof(LNode));
    // 記憶體已滿
    if (L == NULL){
        return false;
    }
    // 把頭結點指向空
    L -> next = NULL;
    return true;
}


// 判斷單鏈表是否為空
bool empty(LinkList L){
    if(L -> next == NULL){
        return true;
    }
    return false;
}

// 連結串列輸出
void printList(LinkList L){
    LNode *p = L -> next;
    printf("單鏈表內的元素為:\n");
    while(!empty(p)){
        printf("%d ",p->data);
        p = p->next;
    }
    // 最後一個元素雖指向NULL但仍有值,繼續列印
    printf("%d ",p->data);
    printf("\n");
}

// 按位查詢,返回第i個元素
LNode * getElem(LinkList L,int i){
    if(i < 0){
        return NULL;
    }
    LNode *p;
    int j = 0;
    p = L;
    while(p != NULL && j < i){
        p = p -> next;
        j++;
    }
    return p;
}

// 按值查詢
LNode * locateElem(LinkList L,int e){
    LNode *p = L -> next;
    // 迴圈遍歷連結串列
    while(p != NULL && p->data !=e){
        p = p->next;
    }
    return p;
}

// 後插,在p結點之後插入元素e
bool insertNextNode(LNode *p,int e){
    // 判斷是否超出長度
    if(p == NULL){
        return false;
    }
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if(s == NULL){
        return false;
    }
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

// 前插,在p結點之前插入元素e,O(1)
bool insertPriorNode(LNode *p,int e){
    if(p == NULL){
        return false;
    }
    LNode *s = (LNode *) malloc(sizeof(LNode));
    if(s == NULL){
        return false;
    }
    // 相當於把p的值給了s,把新的值e給了p,然後連線起來
    s->next = p->next;
    p->next = s;
    s->data = p->data;
    p->data = e;
    return true;
}

// 插入
bool listInsert(LinkList &L,int i,int e){
    if(i < 1){
        return false;
    }
    // 找到第i-1個結點
    LNode *p = getElem(L,i-1);
    // p結點之後插入元素e
    return insertNextNode(p,e);
}

// 刪除 O(n)
bool listDelete1(LinkList &L,int i,int &e){
    if(i < 1){
        return false;
    }
    // 查詢前驅結點
    LNode *p = getElem(L,i-1);
    // q指向被刪除結點
    LNode *q = p->next;
    // 把q從鏈中斷開
    p ->next = q->next;
    // 釋放空間
    free(q);
    return true;
}

// 刪除 O(1)
// !有坑,在刪除最後一個結點時會報錯
bool listDelete2(LNode *p){
    if (p == NULL){
        return false;
    }
    LNode *q = p->next;
    p->data = p->next->data;
    p->next = q->next;
    free(q);
    return true;
}

// 求連結串列長
int getLength(LinkList L){
    int len = 0;
    LNode *p = L;
    while(p -> next != NULL){
        p = p->next;
        len++;
    }
    return len;
}

// 尾插法建立
LinkList listTailInsert(LinkList &L){
    int x;
    // 初始化
    initList(L);
    // 定義頭尾指標
    LNode *s = L;
    LNode *r = L;
    scanf("%d",&x);
    while(x != 9999){
        // 在r結點之後插入x
        s = (LNode *) malloc(sizeof(LNode));
        s -> data = x;
        r -> next = s;
        // 讓r指標指向新的尾
        r = s;
        scanf("%d",&x);
    }
    r -> next = NULL;
    return L;
}

// 頭插法建立
LinkList listHeadInsert(LinkList &L){
    LNode *s;
    int x;
    // 初始化L
    initList(L);
    scanf("%d",&x);
    while(x != 9999){
        // 把新結點插入到頭部
        s = (LNode *)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;
        L->next = s;
        scanf("%d",&x);
    }
    return L;
}

// 主函式測試
int main(){
    // 測試尾插法
    LinkList L1,L2;
    listTailInsert(L1);
    printList(L1);
    // 測試頭插法
    listHeadInsert(L2);
    printList(L2);
    // 測試插入一個新結點
    listInsert(L1,1,100);
    listInsert(L2,3,200);
    printList(L1);
    printList(L2);
    // 測試刪除一個結點
    int deletedNode1;
    listDelete1(L1,3,deletedNode1);
    LNode *deletedNode2 = getElem(L2,3);
    listDelete2(deletedNode2);
    printList(L1);
    printList(L2);
    // 測試查詢結點
    printf("在L1中查詢100,下一個位置的值為 -> %d",locateElem(L1,100)->next->data);
    return 0;
}

雙向連結串列的實現

注意點

對於雙向連結串列,與單鏈表不同的是多了一個前向指標,可以實現向前遍歷這樣的操作了。需要注意的是雙向連結串列的判空條件以及插入時的臨界條件。

程式碼

/*
 * @Description: 雙向連結串列的實現
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-07 21:32:35
 */
#include <bits/stdc++.h>

// 定義雙向連結串列結構
typedef struct DLinkNode
{
    int data;
    // 前向指標與後向指標
    DLinkNode *pre;
    DLinkNode *next;
} DLinkNode, *DLinkList;

// 初始化雙鏈表
bool initList(DLinkList &DL)
{
    // 分配一個頭結點
    DL = (DLinkNode *)malloc(sizeof(DLinkNode));
    // 記憶體已滿
    if (DL == NULL)
    {
        return false;
    }
    // 把頭結點指向空
    DL->pre = NULL;
    return true;
}

// 建立
DLinkList createDLinkList(DLinkList &DL)
{
    int x;
    initList(DL);
    DLinkNode *p;
    DLinkNode *s;
    p = DL;
    scanf("%d", &x);
    while (x != 9999)
    {
        s = (DLinkNode *)malloc(sizeof(DLinkNode));
        s->data = x;
        p->next = s;
        s->pre = p;
        p = s;
        scanf("%d", &x);
    }
    s->next = NULL;
    return DL;
}

// 列印
void printList(DLinkList DL)
{
    DLinkNode *p = DL->next;
    printf("雙鏈表內的元素為:\n");
    while (p->next)
    {
        printf("%d ", p->data);
        p = p->next;
    }
    // 最後一個元素雖指向NULL但仍有值,繼續列印
    printf("%d ", p->data);
    printf("\n");
}

// 頭部插入
void insertListHead(DLinkList &DL, int data)
{
    DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
    pNode->data = data;
    pNode->next = DL->next;
    pNode->pre = DL;
    if (pNode->next != NULL)
    {
        pNode->next->pre = pNode;
    }
    DL->next = pNode;
}

// 尾部插入
void insertListTail(DLinkList &DL, int data)
{
    DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
    pNode->data = data;
    DLinkNode *pCur = DL;
    while (pCur->next != NULL)
    {
        pCur = pCur->next;
    }
    pCur->next = pNode;
    pNode->pre = pCur;
    pNode->next = NULL;
}

// 獲取連結串列長度
int getListLength(DLinkList DL)
{
    DLinkNode *p;
    int len = 0;
    p = DL;
    for (; p->next != NULL; p = p->next)
    {
        len++;
    }
    return len;
}

// 指定位置插入節點,前插
bool insertList(DLinkList &DL, int data, int index)
{
    DLinkNode *pCur = DL;
    int len = getListLength(DL);
    if (index > len)
    {
        return false;
    }
    else if (index == 0)
    {
        insertListHead(DL, data);
        return true;
    }
    else if (index == len)
    {
        insertListTail(DL, data);
        return true;
    }

    DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
    pNode->data = data;
    int i = 0;
    while (index--){
        pCur = pCur->next;
    }
    pNode->next = pCur->next;
    pCur->next = pNode;
    pNode->pre = pCur;
    pNode->next->pre = pNode;
    return true;
}

// 刪除指定值
bool deleteListNode(DLinkList &DL,int key){
    DLinkNode *pPre = DL;
    DLinkNode *pCur = DL->next;
    while(pCur != NULL){
        if(pCur->data == key){
            if(pCur->next != NULL){
                pCur->next->pre = pCur->pre;
            }
            pCur->pre->next = pCur->next;
            pPre = pCur;
            pCur = pCur->next;
            free(pPre);
            return true;
        }else{
            pPre = pCur;
            pCur = pCur->next;
        }
    }
    return false;
}

// 主函式測試
int main(){
    DLinkList DL;
    // 建立
    createDLinkList(DL);
    printList(DL);
    // 從頭插入一個值
    insertListHead(DL,5);
    printList(DL);
    // 從尾插入一個值
    insertListTail(DL,100);
    printList(DL);
    // 任意位置插入
    insertList(DL,1000,2);
    printList(DL);
    // 刪除值為100的結點
    deleteListNode(DL,100);
    printList(DL);
}

帶頭結點的單迴圈連結串列的實現

注意點

這裡實現的是帶頭結點的單迴圈連結串列,迴圈連結串列在理解了其本質後其實是很好寫的。主要就是把最後一個元素的next指標指向了第一個元素。關於實現,需要注意迴圈連結串列的判空條件,以及插入時的操作。

程式碼

/*
 * @Description: 迴圈單鏈表的實現
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-08 19:38:04
 */

#include<bits/stdc++.h>

// 定義迴圈單鏈表結構體
typedef struct CLNode{
    int data;
    CLNode *next;
}CLNode,* CLinklist;

// 初始化
bool initList(CLinklist &CL){
    CL = (CLNode *)malloc(sizeof(CLNode));
    if(CL == NULL){
        return false;
    }
    // 迴圈連結串列初始化頭結點指向本身
    CL->next=CL;
    return true;
}

// 判空
bool isEmpty(CLinklist CL){
    // 指向本身即為空
    if(CL->next == CL){
        return true;
    }
    return false;
}

// 長度
int getLength(CLinklist CL){
    CLNode *p = CL->next->next;
    int len = 0;
    // p未到表頭時
    while(p != CL->next){
        len++;
        p = p->next;
    }
    return len;
}

// 取元素
int getElem(CLinklist CL,int i,int *e){
    CLNode *p = CL->next;
    int j = 0;
    if(i < 1 || i > getLength(CL)){
        // 不合法,返回-1
        return -1;
    }
    while(j < i){
        j++;
        p = p->next;
    }
    *e = p->data;
    // 返回0代表成功
    return 0;
}

// 定位元素
int locateElem(CLinklist CL,int e){
    CLNode *p = CL->next->next;
    int j = 0;

    while(p != CL->next){
        j++;
        if(p->data == e){
            return j;
        }
        p = p->next;
    }
    return -1;
}

// 插入元素
bool listInsert(CLinklist &CL,int i,int e){
    CLNode *p = CL->next;
    CLNode *s;
    int j = 0;
    if(i < 1 || i > getLength(CL) + 1){
        return false;
    }
    while(j < i -1){
        j++;
        p = p->next;
    }
    s = (CLNode *)malloc(sizeof(CLNode));
    s->data = e;
    s->next = p->next;
    p->next = s;
    // 如果在表尾插入,則把新插入的元素當做頭結點(第一個元素)
    if(p == CL){
        CL = s;
    }
    return true;
}

// 刪除元素
bool listDelete(CLinklist &CL,int i,int &e){
    CLNode *p = CL->next;
    CLNode *q;
    int j = 0;
    if(i < 1 || i > getLength(CL)){
        return false;
    }
    while(j < i - 1){
        j++;
        p = p->next;
    }
    q = p->next;
    e = q->data;
    p->next = q->next;
    // 刪除表尾元素,指標發生變化
    if(q == CL){
        CL = p;
    }
    free(q);
    return true;
}

// 列印
void printList(CLinklist CL)
{
    CLNode *p = CL->next->next;
    printf("迴圈連結串列內的元素為:\n");
    while (p != CL->next)
    {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}

// 主函式測試
int main(){
    CLinklist CL;
    initList(CL);
    printList(CL);
    // 插入幾個元素
    listInsert(CL,1,100);
    listInsert(CL,2,2);
    listInsert(CL,3,3);
    listInsert(CL,4,4);
    listInsert(CL,5,5);
    printList(CL);
    // 插入到表尾試試
    listInsert(CL,5,1000);
    printList(CL);
    // 刪除一個元素
    int deletedElem;
    listDelete(CL,3,deletedElem);
    printList(CL);
}

總結

這裡還缺雙向迴圈連結串列的實現,其實這種實現也並不是很難的方式,讀者可以去自行實現。線上性表這一塊大量使用了指標,一定要好好理解指標的概念才能更好地實現程式碼。