1. 程式人生 > >重學資料結構(一)單鏈表

重學資料結構(一)單鏈表

最近在重新學習資料結構,特此記錄學習過程,碼農再次上線

關於單鏈表的一些基本操作,以下為基本思路程式碼

首先看一張直觀圖
直觀圖

連結串列的結構體定義如下(為簡便,這裡的ElemType採用int):

//單鏈表
typedef struct LNode {
    int data;
    struct LNode *next;
} LinkList;

接下來肯定就是建立連結串列,而目前建立單鏈表分為兩種方式,分別是"頭插法"與"尾插法",首先看圖示:
頭插法

尾插法

清楚明白:

  • 頭插法就是每次插入的節點在上一個節點之前,頭節點之後
  • 尾插法就是每次插入的節點在上一節點之後

我們對比來看節點的插入:

//頭插法核心程式碼
s = (LinkList *) malloc(sizeof(LinkList));
s->data = a[i];
s->next = L->next;//將s插入到下一節點之前
L->next = s;  

//尾插法核心程式碼
s = (LinkList *) malloc(sizeof(LinkList));
s->data = a[i];
r->next = s;//將s插入到r之後
 r = s;

那麼寫成函式,就是如下所示:

/**
 * 頭插法建立單鏈表
 * @param a 陣列元素,對應data
 * @param n 連結串列長度(除去頭結點)
 */
void CreateListByHead(LinkList *L, const int a[], int n) { LinkList *s; //建立頭節點 L->next = NULL; for (int i = 0; i < n; ++i) { s = (LinkList *) malloc(sizeof(LinkList)); s->data = a[i]; s->next = L->next;//將s插入到下一節點之前 L->next = s; } } /** * 尾插法建立單鏈表 * @param a 陣列元素,對應data * @param n 連結串列長度(除去頭結點) */
void CreateListByTail(LinkList *L, const int a[], int n) { LinkList *r = L, *s; for (int i = 0; i < n; ++i) {//迴圈建立資料節點 s = (LinkList *) malloc(sizeof(LinkList)); s->data = a[i]; r->next = s;//將s插入到r之後 r = s; } r->next = NULL; //尾節點next域置為空 }

那麼接下來的集中基本操作方法也就不再贅述,其實都是一些對連結串列的操作,指標的移動,臨界條件的判斷,我也都寫在註釋中了,尤其是第一個Delete方法寫的最詳細,可以仔細看一下哦,為了直觀一點,我也附上程式碼圖片(編譯環境Clion)~

按照邏輯序號刪除某節點

這裡也要注意是否會斷鏈的問題,free()不要釋放錯了,否則會有錯誤
刪除操作

直觀程式碼

/**
 * 刪除單鏈表中的第i個元素
 * @param L 連結串列
 * @param i 第i個元素
 * @return  程式執行完畢狀態
 */
int ListDelete(LinkList *L, int i) {
    int j = 0;
    LinkList *p = L, *q;
    /*
     *找到第i-1個節點 ,p!= NULL 是防止尋找的節點超過連結串列,
     * 比如一共有四個節點,而i=6即想找到第6個節點,那麼當j++變成5,
     * p指向第五個不存在的節點後就會返回NULL
     *此時迴圈結束,p == NULL
     */
    while (j < i - 1 && p != NULL) {
        j++;
        p = p->next;
    }
    if (p == NULL)
        return ERROR;
    else {
        q = p->next;
        /*
         * 如果p->next為空,則返回ERROR
         * 此時是為了防止邊界條件的情況
         *  若連結串列一共有四個節點,而我們要找到第五個節點,
         *   那麼在之前的找到的i-1也就是第四個節點
         *  此時q = p->next 返回的就是一個NULL值
         */
        if (q == NULL)
            return ERROR;
        p->next = q->next;
        free(q);
        return TRUE;
    }
}
在邏輯位置之後插入節點

這裡注意,插入操作遵循***先右後左***原則,否則會斷鏈,如圖:
插入操作

直觀程式碼

/**
 * 在邏輯位置後插入節點
 * @param L  傳入連結串列
 * @param i 邏輯位置
 * @param e 插入資料
 * @return 插入成功返回1,失敗返回0
 */
int ListInsert(LinkList *L, int i, int e) {
    int j = 0;
    LinkList *p = L, *s;
    while (j < i && p != NULL) {//查詢第i個位置
        j++;
        p = p->next;
    }
    if (p == NULL)//如果為空返回錯誤值
        return ERROR;
    else {
        s = (LinkList *) malloc(sizeof(LinkList));
        s->data = e;
        s->next = p->next;
        p->next = s;
        return TRUE;
    }
}
按照元素值查詢特定節點

這裡預設連結串列中的元素是唯一的,如果想要查詢不唯一的數值,就需要大家去拓展了,也不是很難,可以採用將查到的資料邏輯序號放到陣列中返回等等等等方法~
直觀程式碼

 /**
  * 按照元素值查詢特定節點並返回邏輯序號
  * @param L 傳入單鏈表
  * @param e 傳入特定元素
  * @return 沒有找到返回0,找到返回邏輯序號i
  */
 int FindElement(LinkList *L, int e){
     int i = 1; //不要忘了初始值!
     LinkList *s = L; //指向開始節點
     while (s != NULL && s->data != e){
         s = s->next;
         i++;
     }
     if (s == NULL )
         return ERROR;
     else
         return i;
 }
輸出連結串列

直觀程式碼

/**
 * 輸出單鏈表
 */
void DispList(LinkList *L) {
    while (L->next != NULL) {
        printf("%d->", L->data);
        L = L->next;
    }
    printf("%d", L->data);
}
最後附上測試用的main函式,為了方便就直接用陣列賦值了
#include <stdio.h>
#include "linklist.h"

int main() {

    int a[10];
    for (int j = 0; j < 10; ++j) a[j] = j + 1;
    LinkList *L = (LinkList *) malloc(sizeof(LinkList));

    CreateListByTail(L, a, 10);
    printf("建立完成後的單鏈表為: ");
    DispList(L);

    ListInsert(L, 5, 555);
    printf("\n在第五個節點後插入555之後的單鏈表為: ");
    DispList(L);

    ListDelete(L, 3);
    printf("\n刪除第三個節點後的單鏈表為: ");
    DispList(L);

    int i = FindElement(L, 5);
    if (i != 0)
        printf("\n擁有此元素!在第%d個位置上!",i);
    else
        printf("\n沒有此元素!!!!");
    return 0;
}

測試結果