數據結構之鏈表

鏈表的定義

鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。鏈表由一系列結點(鏈表中每一個元素稱爲結點)組成,結點可以在運行時動態生成。每個結點包括兩個部分:一個是存儲數據元素的數據域,另一個是存儲下一個結點地址的指針域。 相比於線性表順序結構,操作複雜。 (百度)

鏈表:一種鏈式存儲的線性表,用一組地址任意的存儲單元存放線性表的數據元素,稱存儲單元爲一個結點。

鏈表的形式:結點地址{data,next}

鏈表的形式基本知道後,鏈表的使用離不開指針。關於指針指針指向的空間 是有區別的

指針不能看成是一個地址,指針是用來存放地址的。指針指向的空間不是地址,是一個還未使用開闢出來的空間。

ps:這之上是去年寫了一個開頭,今年複習的時候感覺鏈表的操作圍繞頭插,尾插,頭刪,尾刪這幾個操作來做。還是理解學會這幾個操作更好的去理解鏈表

鏈表的初始化

#pargma once
#include <stdio.h>

typedef int SLDatatype;

typedef struct Node{
    SLDatatype value;
    struct Node *next;
}

typedef struct SList{
    Node *first;//定義一個頭結點
}

初始化我個人覺得難點可能在於一開始比較想不通結構體內定義next。結構體調用自己定義一個next指針變量是爲了表示鏈表下一個的指向。在指針域指向下一個鏈表的指針域。(自行體會)

鏈表的兩種插入

//鏈表的頭插
void SListPushFront(SList *s, SLDatatype v){
    Node *node = (Node*)malloc(sizeof(Node));//插入肯定要開闢一個新空間
    node->value = v;
    
    node->next = s->first;
    s->first = node;
}

在這裏插入圖片描述

//鏈表的尾插
void SListPushBack(SList *s, SLDatatype v){
    Node *node = (Node*)malloc(sizeof(Node));
    node->value = v;
    
    if(s->first == NULL){
        s->first = node;
    }
    else{
        Node *c = s->first;
   		while(c->next != NULL){
        	c = c->next;
    	}
        c->next = node;
    }
}

while循環的意義在於找到最後一個結點,當找到最後一個結點之後,將最後一個結點的next指向node

//鏈表的任意位置插入
void SListInsertAfter(Node *pos, SLDataType v){
    Node *node = (Node*)malloc(sizeof(Node));
    node->value = v;
    
    node->next = pos->next;
    pos->next = node;
}

在這裏插入圖片描述

從這個圖中來看在pos結點與後一個結點之間插入一個新結點。讓新結點的指向爲pos結點的指向,然後讓pos結點的指向爲新結點。新的結點就被插入之中了。

鏈表的刪除

//鏈表的頭刪
void SListPopFront(SList *s){
    assert(s->first != NULL);
    if(s->first->next == NULL){
        s->first = NULL;
    }
    Node *second = s->first->next;
    free(s->first);
    s->first = second;
}

頭刪很容易理解,分成兩種情況。只有一個結點的情況下,直接讓頭結點爲空。如果不是隻有一個結點的情況下將頭結點指向的下一個結點先標記一下,釋放掉頭結點,在將標記的結點設爲頭結點。

//鏈表的尾刪
void SListPopBack(SList *s){
    assert(s->first != NULL);
    if(s->first->next == NULL){
        free(s->first);
        s->first = NULL;
    }
    else{
        Node *c = s->first;
        while(c->next->next != NULL){
            c = c->next;
        }
        free(c->next);
        c->next = NULL;
    }
}

其實仔細看尾刪跟尾插有點相似,只不過尾刪需要找到倒數第二個結點。然後釋放到第一個結點,再將第一個結點賦爲空。

//鏈表的任意位置之後結點刪除
void SListEarseAfter(Node *pos){
    Node *next = pos->next->next;
    free(pos->next);
    pos->next = next;
}

假設三個結點,先將最後一個結點標記,之後釋放掉中間結點,在讓第一個結點指向最後一個結點。

鏈表的另外兩種刪除

與之前順序表相似的地方,刪除遇到的一個鏈表中的數據域和刪除鏈表中所有的數據域

//刪除遇到的第一個數據域
void SListRemove(SList *s, SLDataType v){
    if(s->first == NULL){
        return;
    }
    if(s->first->value == v){
        Node *second = s->first->next;
        free(s->first);
        s->first = second;
    }
    else{
        Node *c = s->first;
        while(c->next != NULL){
            if(c->next->value == v){
                Node *next = c->next;//先將頭結點的後一個結點設爲next
                c->next = c->next->next;//這時候將頭結點的後二個結點next轉換爲頭結點的後一個結點
                free(next);//釋放掉後一個結點
                return;
            }
            c = c->next;
        }
    }
}

------------------------------------------------------------------------------------------------
//刪除遇到的所有數據域
void SListRemoveAll(SList *s, SLDataType v){
    if(s->first == NULL){
        return;
    }
    if(s->first->value == v){
        Node *next = s->first;
        s->first = s->first->next;
        free(next);
    }
    else{
        Node *c = s->first;
        while(c->next != NULL){
            if(c->next->value == v){
                Node *next = c->next;
                c->next = c->next->next;
                free(next);
            }
            else{
                c = c->next;//用到else是如果不等於數據域的話就繼續向後走,再到下一個結點去判斷數據域
            }
        }
    }
}

上面代碼其實很相似,只不過要考慮差距在哪,其實就是調用了幾個不同的判斷語句。

鏈表的核心就是這些了。我認爲這些如果不能理解一定多畫圖!!!!畫圖太有助於理解了!!!!

而且必須多敲代碼!!!!我都敲了好幾遍!!!!終於在今天寫起來比較順手了!!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章