【數(shù)據(jù)結(jié)構(gòu)】線性表之單鏈表

完整代碼需結(jié)合前面一篇順序表數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)-線性表之順序表各種操作
網(wǎng)易云課堂小甲魚課程鏈接:數(shù)據(jù)結(jié)構(gòu)與算法

線性表的鏈?zhǔn)酱鎯Y(jié)構(gòu)

線性表的順序存儲結(jié)構(gòu),最大的缺點就是插入和刪除時需要移動大量的元素,這顯然需要耗費時間。導(dǎo)致這個問題的原因是在于相鄰元素的存儲位置具有鄰居關(guān)系,它們在內(nèi)存中的位置是緊挨著的,中間沒有間隙,當(dāng)然無法快速插入和刪除。

定義

  • 線性表的鏈?zhǔn)酱鎯Y(jié)構(gòu)的特點是用一組任意的存儲單元存儲線性表中的數(shù)據(jù)元素,這組存儲單元可以存放在內(nèi)存中未被占用的任意位置
  • 相比順序存儲結(jié)構(gòu),鏈?zhǔn)酱鎯Y(jié)構(gòu)中,除了需要存儲數(shù)據(jù)元素信息之外,還需要存儲它的后繼元素的存儲地址(指針)
  • 數(shù)據(jù)域:存儲數(shù)據(jù)元素信息的域,指針域:存儲直接后繼位置的域。指針域中存儲的信息成為指針或鏈。這兩部分信息組成數(shù)據(jù)元素成為存儲映像,成為結(jié)點(Node)。
    結(jié)點結(jié)構(gòu)node
  • 鏈?zhǔn)浇Y(jié)構(gòu):n個結(jié)點鏈接成一個鏈表,即為線性表(a1,a2,a3,...an);
  • 如果鏈表的每個結(jié)點中只包含一個指針域,那就叫做單鏈表

單鏈表

小甲魚視屏中的單鏈表
  • 頭指針:鏈表中的第一個結(jié)點的存儲位置。
  • 線性表中最后一個結(jié)點的指針域為空(NULL)。
  • 看圖說明:


    單鏈表的特點

頭結(jié)點和頭指針

  • 頭結(jié)點:

    • 頭結(jié)點是加在單鏈表之前附設(shè)的一個頭結(jié)點。
    • 頭結(jié)點的數(shù)據(jù)域一般不存儲任何信息,也可以存放一些關(guān)于線性表的長度的附加信息。
    • 頭結(jié)點的指針域存放指向第一個結(jié)點的指針(即第一個結(jié)點的存儲位置)。
    • 頭結(jié)點不一定是鏈表的必要元素。
  • 頭指針:

    • 頭指針是指鏈表指向第一個結(jié)點的指針,若鏈表有頭結(jié)點,則是指向頭結(jié)點的指針。
    • 頭指針具有標(biāo)識作用,所以常用頭指針冠以鏈表的名字(指針變量的名字)。
    • 無論鏈表是否為空,頭指針均不為空。
    • 頭指針是鏈表的必要元素。


單鏈表的代碼實現(xiàn)

#include <stdio.h>
#define OK 1;
#define ERROR 0;
#define TRUE 1;
#define FALSE 0;
#define MAXSIZE 20  /* 定義線性表可能達到的最大長度 */
typedef int  ElemType; /*  定義數(shù)據(jù)元素類型,類型名為ElemType,此處所定義的數(shù)據(jù)元素只包含一個int型的數(shù)據(jù)項*/
typedef struct { /* 定義順序表類型,類型名為SqList,包含兩個數(shù)據(jù)項:數(shù)組data,用于存放數(shù)據(jù)元素,整數(shù)length表示線性表當(dāng)前長度*/
    
    ElemType data[MAXSIZE];   /*定義線性表占用的數(shù)組空間*/
    int length;         /*線性表當(dāng)前長度*/
    
}SqList;

typedef int Status; /*Status是函數(shù)的類型,其值是函數(shù)結(jié)果狀態(tài)碼,如OK等*/

// 用結(jié)構(gòu)指針描述單鏈表
typedef struct Node{
    
    ElemType data;  // 數(shù)據(jù)域
    struct Node *Next; // 指針域
    
}Node;

// 取別名
typedef struct Node *LinkList;

通過這種鏈?zhǔn)酱鎯Ψ绞?,若?code>p->data=ai,那么p->next->data=ai+1.

單鏈表的讀?。?strong>工作指針后移)

  • 獲取鏈表第i個數(shù)據(jù)的算法思路:
    1.聲明一個結(jié)點p指向鏈表的第一個結(jié)點,初始化j從1開始;
    2.當(dāng)j<i時,就遍歷鏈表,讓p的指針向后移動,不斷指向下一結(jié)點,j+1;
    3.若到鏈表末尾p為空,則說明第i個元素不存在;
    4.否則查找成功,返回結(jié)點p數(shù)據(jù)。
  • 代碼:
/**
 * 獲取鏈表第`i`個數(shù)據(jù)的
 */
Status GetElem(LinkList L, int i, ElemType *e){
    
    int j;
    LinkList p;  // 聲明指針p
    
    p = L->next;  // p指向鏈表L的第一個結(jié)點
    j = 1;      // 當(dāng)前位置計數(shù)器設(shè)置為1
    
    while (p && j<i) {  // 當(dāng)p不為空 切j<i時候 j繼續(xù)向后找
        
        p = p->next;
        j++;
    }
    
    if (!p || j>i) return ERROR;  // 到達結(jié)尾且沒找到
    
    *e = p->data;  // 獲取倒找的結(jié)果
    
    return OK;
}

單鏈表的插入算法

  • 算法思路:
    1.聲明一結(jié)點p指向鏈表頭結(jié)點,初始化j從1開始;
    2.當(dāng)j<1時,就遍歷鏈表,讓p的指針向后移動,不斷指向下一節(jié)點,j累加1;
    3.若到鏈表末尾p為空,則說明第i個元素不存在;
    4.否則查找成功,在系統(tǒng)中生成一個空節(jié)點s;
    5.將數(shù)據(jù)元素e賦值給s->data;
    6.利用單鏈表插入語句插入:s->next = p->next; p->next = s;;
    7.返回成功。
  • 代碼:
/**
 * 單鏈表的插入
 */
Status ListInsert(LinkList *L, int i, ElemType e){
    
    int j;
    LinkList p,s;
    
    p = *L;
    j = 1;
    
    while (p && j<i) {  // 用于尋找第i個結(jié)點
        
        p = p->next;
        j++;
    }
    
    if (!p || j>i) return ERROR;  // 到達結(jié)尾且沒找到
    
    s = (LinkList)malloc(sizeof(Node));  //生成一個空節(jié)點s
    
    // 插入語句
    s->next = p->next;
    p->next = s;
    
    return OK;
}

單鏈表的刪除算法(前繼結(jié)點的指針繞過繞過后繼結(jié)點)

  • 算法實現(xiàn)思路:p->next = p->next->next,即 q = p->next; p->next = q->next
    1.聲明一結(jié)點p指向鏈表頭結(jié)點,初始化j從1開始;
    2.當(dāng)j<1時,就遍歷鏈表,讓p的指針向后移動,不斷指向下一節(jié)點,j累加1;
    3.若到鏈表末尾p為空,則說明第i個元素不存在;
    4.否則查找成功,將欲刪除結(jié)點p->next賦值給q;
    5.單鏈表的刪除標(biāo)準(zhǔn)語句:p->next = q->next;
    6.將結(jié)點中的數(shù)據(jù)賦值給e,作為返回;
    7.釋放q結(jié)點。
  • 代碼:
/**
 * 單鏈表的刪除
 */
Status ListDelete(LinkList *L, int i, ElemType e){
    
    int j;
    LinkList p,q;
    
    p = *L;
    j = 1;
    
    while (p && j<i) {  // 用于尋找第i個結(jié)點
        
        p = p->next;
        j++;
    }
    
    if (!p || j>i) return ERROR;  // 到達結(jié)尾且沒找到
    
    q = p->next;
    
    // 刪除語句
    p->next = q->next;
    free(q);
    
    return OK;
}

單鏈表的插入和刪除圖解

特點:先找到,再刪除或者增加。時間復(fù)雜度為o(1);

單鏈表的整表創(chuàng)建

對于順序存儲結(jié)構(gòu)的線性表的整表創(chuàng)建,可以用數(shù)組的初始化來直觀理解。
但是對于單鏈表,和順序存儲結(jié)構(gòu)就不一樣,不像順序存儲結(jié)構(gòu)數(shù)據(jù)這么幾種,它的數(shù)據(jù)可以是分散在各個角落的,增長也是動態(tài)的。
對于每個鏈表來說,它所占用空間的大小和位置是不需要預(yù)先分配劃定的,可以根據(jù)系統(tǒng)的情況和實際的需要及時生成。

單鏈表整表創(chuàng)建的算法思路:

1.聲明一個結(jié)點p和計數(shù)器變量i;
2.初始化一個空鏈表L
3.讓L的頭結(jié)點的指針指向NULL,即建立一個帶頭結(jié)點的單鏈表;
4.循環(huán)實現(xiàn)后繼結(jié)點的賦值和插入。

一、頭插法建立單鏈表

頭插法從一個空表開始,生成新結(jié)點,讀取數(shù)據(jù)存放到新節(jié)點的數(shù)據(jù)域中,然后將新結(jié)點插入到當(dāng)前鏈表的表頭,知道結(jié)束。

簡單來說,就是把新加進的元素放在表頭的第一個位置(如同插隊):

  • 先讓新節(jié)點的next指向頭結(jié)點之后;

  • 讓后讓表頭的next指向新節(jié)點。

  • 代碼:

/**
 * 頭插法
 */
void CreatListHead(LinkList *L, int n){

    LinkList p;
    int i;
    
    srand(time(0));   // 初始化隨機數(shù)種子
    
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    
    for (i = 0; i < n; i++) {
        
        p = (LinkList)malloc(sizeof(Node)); // 生成新節(jié)點
        p ->data = rand()%100 + 1;
        p->next = (*L)->next;
        (*L)->next = p;
        
    }
}

這里使用了c語言里面的生成隨機數(shù)的函數(shù)rand()來構(gòu)造結(jié)點里面的數(shù)據(jù)。

二、尾插法建立單鏈表

頭插法建立鏈表隨讓算法簡單,但是生成的鏈表中結(jié)點的次序和輸入的順序相反。
若把新結(jié)點都插入到最后,那么這種算法就是尾插法。

  • 代碼:
/**
 * 尾插法
 */
void CreatListTail(LinkList *L, int n){
    
    LinkList p, r;
    int i;
    
    srand(time(0));
    *L = (LinkList)malloc(sizeof(Node));
    r = *L;
    
    for (i = 0; i < n; i++) {
        
        p = (Node *)malloc(sizeof(Node));
        p->data = rand()%100+1;
        r->next = p;
        r = p;
    }
    
    r->next = NULL;
}
尾插法圖解

單鏈表的整表刪除

  • 單鏈表的整表刪除的算法思路:
    1.聲明結(jié)點pq;
    2.將第一個結(jié)點賦值給p,下一結(jié)點賦值給q
    3.循環(huán)執(zhí)行釋放p和將q賦值給p的操作。
  • 代碼:
Status ClearList(LinkList *L){

    LinkList p,q;
    
    p = (*L)->next;
    
    while (p) {
        q = p->next;
        free(p);
        p = q;
    }
    
    (*L)->next = NULL;
    
    return OK;
}

注意:整表刪除的時候,需要一個個結(jié)點的刪除。

單鏈表結(jié)構(gòu)和順序存儲結(jié)構(gòu)對比

存儲分配方式:
  • 順序存儲結(jié)構(gòu)用一段連續(xù)的存儲單元依次存儲線性表的數(shù)據(jù)元素;
  • 單鏈表采用鏈?zhǔn)酱鎯Y(jié)構(gòu),用一組任意的儲存單元存放線性表的元素;
時間性能:
  • 查找:
    • 順醋存儲結(jié)構(gòu)O(1);
    • 單鏈表O(n);
  • 插入和刪除:
    • 順序存儲結(jié)構(gòu)需要平均移動表長一般的元素,時間為O(n);
    • 單鏈表在計算機處某位置的指針后,插入和刪除時間為O(1);
空間性能
  • 順序存儲結(jié)構(gòu)需要預(yù)分配存儲空間,分大了,容易造成空間浪費,分小了,容易發(fā)生溢出。
  • 單鏈表不需要分配存儲空間,只要有就可以分配,元素個數(shù)也不受限制。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容