一、鏈表

1.線性鏈表:
鏈表是一組存儲線性表的數據元素存儲單元;
由若干個節點組成,結點包括兩個域:
其中存儲數據元素信息的稱為數據域;存儲直接後繼存儲位置有域稱為指針域。

2.鏈表的基本操作:
(1)鏈表存儲結構

typedef int ElemType;
typedef struct node
{
    ElemType data;  //數據域
    struct node *next;  //指針域
} Node;

(2)單鏈表-初始化

Node* initList()
{
    Node *head = (Node*)malloc(sizeof(Node));
    head->data = 0;
    head->next = NULL;
    return head;
}
int main ()
{
    Node *list = initList();
    return 1;
}

(3)單鏈表-頭插法

int insertHead (Node* L, ElemType e)
{
    Node *p = (Node*)malloc(sizeof(Node));
    p->data = e;
    p->next = L->next;
    L->next = p;
}
int main ()
{
    Node *list = initList();

    insertHead(list, 10);
    insertHead(list, 20);
}

(4)單鏈表-遍歷

void listNode (Node* L)
{
    Node *p = L->next;
    while (p != NULL)
    {
        printf ("%dn", p->data) ;
        p = p->next;
    }
    printf("\n");
}

(5)單鏈表-獲取鏈表長度

int listLength (Node *L)
{
    Node *p = L;
    int len = 0;
    while (p != NULL)
    {
        p = p->next;
        len++;
    }
    return len;
}

(6)單鏈表-尾插法

Node* get_tail (Node *L)
{
    Node *p = L;
    while (p->next != NULL)
    {
        p = p->next;
    }
    return p;
}

(7)單鏈表-在指定位置插入數據

int insertNode(Node *L, int pos, ElemType e)
{
    Node *p = L;
    int i = 0;
    while(i < pos-1)
    {
        p = p->next;
        i++;
        if (p == NULL)
        return 0;
    }
    Node *q = (Node*)malloc(sizeof(Node));
    q->data = e;
    q->next = p->next;
    p->next = q;
    return 1;
}

(8)單鏈表-刪除節點

int deleteNode(Node *L, int pos)
{
    Node *p = L;
    int i = 0;
    while(i < pos-1)
    {
        p = p->next;
        i++;
        if (p == NULL)
        {
            return 0;
        }
    }
    if(p->next == NULL)
    {
        printf("要刪除的位置錯誤\n");
        return 0;
    }
    Node *q = p->next;
    p->next = q->next;
    free(q);
    return 1;
}

(9)單鏈表-釋放鏈表

void freeList (Node *L)
{
    Node *p = L->next;
    Node *q;
    while (p != NULL)
    {
        q = p->next;
        free (p) ;
        p = q;
    }
    L->next = NULL;
}

3.雙向鏈表:

鏈式存儲結構的節點中只有一個指示直接後繼的指針域,由此,從某個結點出發只能順指針向後尋查其他節點。若要尋查結點的直接前驅、則必須從表頭指針出發。換句話説,在單鏈表中,查找直接後繼的執行時間為O(1),而查找直接前驅的執行時間為O(n)。為克服單鏈表這種單向性的缺點,可利用雙向鏈表。在雙向鏈表的節點中有兩個指針域,一個指向直接後繼,另一個指向直接前驅。

雙向鏈表的結構

typedef int ElemType;

typedef struct node
{
    ElemType data;
    struct node *prev, *next; //prev為前驅結點,next為後繼結點
} Node;

二、棧

1.棧(stack)是限定僅在表尾進行插入或刪除操作的線性表。
因此對棧來説,表尾端有其特殊含義,稱為棧頂(top),相應地,表頭端稱為棧底(bottom)。不含元素的空表稱為空棧。
假設S=(a1,a2,…,an),則稱a1為棧底元素,an為棧頂元素。棧中元素按a1,a2,…,an的次序進棧,退棧的第一個元素應為棧頂元素。換句話説,棧的修改是按照後進先出的原則進行的。
因此,棧又稱為後進先出(Last In First Out,LIFO)的線性表。
棧是限制插入和刪除操作只能在一個位置進行的表,該位置是表的末端,叫作棧頂(top)。對棧的基本操作有進棧(push)和出棧(Pop),前者相當於插入,後者則是刪除最後插入的元素。

2.棧的基本操作:

(1)棧的鏈式結構實現:

typedef int ElemType;
typedef struct stack
{
    ElemType data;
    struct stack *next;
} Stack;

(2)棧的鏈式結構實現-初始化:

typedef int ElemType;
typedef struct stack
{
    ElemType data;
    struct stack *next;
} Stack;
Stack* initStack ()
{
    Stack *s = (Stack*)malloc(sizeof(Stack));
    s->data = 0;
    s->next = NULL;
    return s;
}

(3)棧的鏈式結構實現-判斷棧是否為空

int isEmpty (Stack *s)
{
    if (s->next == NULL)
    {
        printf("空的\n");
        return 1;
    }
    else
    {
        return 0;
    }
}

(4)棧的鏈式結構實現-進棧/壓棧

int push (Stack *s, ElemType e)
{
    Stack *p = (Stack*)malloc(sizeof(Stack));
    p->data = e;
    p->next = s->next;
    s->next = p;
    return 1;
}

(5)棧的鏈式結構實現-出棧

int pop(Stack *s, ElemType *e)
{
    if (s->next == NULL)
    {
        printf("空的\n");
        return 0;
    }
    *e = s->next->data;
    Stack *q = s->next;
    s->next = q->next;
    free (q) ;
    return 1;
}

(6)棧的鏈式結構實現-獲取棧頂元素

int getTop(Stack *s, ElemType *e)
{
    if (s->next == NULL)
    {
        printf("空的\n");
        return 0;
    }
    *e = s->next->data;
    return 1;
}

三、隊列

1.隊列(queue)是一種先進先出的線性表。
它只允許在表的一端進行插入,而在另一端刪除元素。在隊列中,允許插入的一端稱為隊尾,允許刪除的一端則稱為隊頭。假設隊列為q=(a1,a2,…,an),那麼,a1就是隊頭元素,an就是隊尾元素。
隊列中的元素是按照a1,a2,…,an的順序進入的,退出隊列也只能按照這個次序依次退出,也就是説,只有在a1,a2,…,an-1都離開隊列之後,an才能退出隊列。

2.隊列的基本操作:

(1)隊列的鏈式結構-初始化

Queue* initQueue ()
{
    Queue *q = (Queue*) malloc(sizeof (Queue));
    QueueNode *node = (QueueNode*)malloc(sizeof (QueueNode)) ;
    node->data = 0;
    node->next = NULL;
    q->front = node;
    q->rear = node;
    return q;
}

(2)隊列的鏈式結構

typedef struct QueueNode
{
    ElemType data;
    struct QueueNode *next;
} QueueNode;
typedef struct
{
    QueueNode *front;
    QueueNode *rear;
} Queue;

(3)隊列的鏈式結構-初始化

Queue* initQueue ()
{
    Queue *q = (Queue*)malloc(sizeof (Queue));
    QueueNode *node = (QueueNode*)malloc(sizeof (QueueNode));
    node->data = 0;
    node->next = NULL;
    q->front = node;
    q->rear = node;
    return q;
}

(4)隊列的鏈式結構-判斷隊列是否為空

int isEmpty (Queue *q)
{
    if (q->front == q->rear)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

(5)隊列的鏈式結構-入隊

void equeue (Queue *q, ElemType e)
{
    QueueNode *node = (QueueNode*) malloc(sizeof (QueueNode)) ;
    node->data = e;
    node->next = NULL;
    q->rear->next = node;
    q->rear = node;
}

(6)隊列的鏈式結構-出隊

int dequeue(Queue *q, ElemType *e)
{
    QueueNode *node = q->front->next;
    *e = node->data;
    q->front->next = node->next;
    if (q->rear == node)
    {
        q->rear = q->front;
    }
    free (node) ;
    return 1;
}

(7)隊列的鏈式結構-獲取隊頭元素

ElemType getFront (Queue *q)
{
    if (isEmpty (q))
    {
        printf("空的\n");
        return 0;
    }
    return q->front->next->data;
}

四、總結

1.基本操作的實現共性與差異
(1)共性
存儲基礎:鏈表、棧(鏈式)、隊列(鏈式)均基於節點 + 指針的鏈式存儲結構,通過動態內存分配(malloc)創建節點,藉助指針維護元素間的邏輯關係,最終都需通過free釋放內存以避免泄漏。

初始化邏輯:三者初始化均需創建頭節點(或頭指針),並將指針域置空(NULL),為後續操作建立初始的空結構。

遍歷 / 訪問邏輯:都需通過指針遍歷節點實現元素訪問,且訪問過程中需判斷指針是否為空以防止越界。
(2)差異

結構

核心操作位置

核心操作

時間複雜度(核心操作)

典型應用場景

單鏈表

任意位置

頭插、尾插、指定位置插入 / 刪除

插入 / 刪除 O (1)(已知位置)、遍歷 O (n)

動態數據存儲、不定長數據管理

雙向鏈表

任意位置

前驅 / 後繼節點的查找與操作

查找前驅 O (1)、其他同單鏈表

需頻繁查找前驅的場景(如鏈表排序)

棧(鏈式)

棧頂

壓棧、出棧

壓棧 / 出棧 O (1)

表達式求值、函數調用棧、回溯算法

隊列(鏈式)

隊尾(插入)、隊頭(刪除)

入隊、出隊

入隊 / 出隊 O (1)

任務排隊、消息隊列、廣度優先搜索

2.實現中的注意事項與易錯點
(1)內存管理:鏈式結構中每次創建節點都需通過malloc分配內存,操作完成後必須通過free釋放節點,否則會導致內存泄漏;同時需避免野指針。

(2)邊界條件判斷:

鏈表:插入 / 刪除時需判斷位置是否合法、遍歷到鏈表尾部時需判斷p->next是否為空;

棧:出棧和獲取棧頂元素時需先判斷棧是否為空;

隊列:出隊時需判斷隊列是否為空,且當最後一個節點出隊時需將隊尾指針重置為隊頭指針,避免野指針。

(3)指針操作順序:插入節點時需先維護後繼節點的指針關係,再修改前驅節點的指針,否則會導致鏈表斷裂。