線性表的鏈式存儲(C代碼實現)


線性表的鏈式存儲結構

線性表的實現分順序存儲結構鏈式存儲結構。

線性表的鏈式存儲結構又稱單鏈表。

上一節我們學習了線性表的順序存儲結構,並實現解順序存儲的基本操作。

這一節我們來學習線性表鏈式存儲結構,那我們再想象一下我為什么我們要引入鏈式存儲結構,萬物存在必有其道理

主要還是因為線性存儲結構存在着這樣一個問題:當我們需要插入和刪除元素時,就必須挪動大量與之無關的元素,因為線性存儲結構結點與節點之間的關系是相鄰關系,一個節點挨着一個節點

如為了插入或者刪除一個元素移動大量的元素,這樣就降低了程序運行效率。

當我們引入

顧名思義鏈式存儲結構,數據與數據之間是以鏈式關系來產生連接的的,我們可以腦補一下鎖鏈的樣子,不扯淡了,進入正題--

我們如何定義一個鏈式存儲結構的節點呢?

/*Node表示一個節點*/
typedef struct Node{
    int data;   //數據域
    struct Node* next;   //存放下一個節點的指針
}Node;
typedef struct Node* LinkList; /*取了一個別名,定義LinkList = Node*,用於存放節點的指針*/

一個節點包括一個數據域和指針域

我們將這種只帶有一個指針域的線性表稱為單鏈表

鏈表中第一個結點的存儲位置叫做頭指針。

單鏈表的第一個結點前附設一個結點,稱為頭結點

注意可以沒有頭節點,但是要是鏈表,就一定存在頭指針。

 那么問題來了,我們如何區分頭節點和頭指針呢?

頭指針:是指向鏈表的指針,如果不存在頭節點,那么頭指針就會指向鏈表的第一個節點。

頭節點:實際上是不存在的,只不過是為了鏈表的一些操作方便而設置的,頭節點與第一個節點以鏈式關系相連,並且頭節點的數據域沒有意義,指針域存放第一個節點的地址。

 

單鏈表的插入

 

實現代碼:

/*插入元素 n是位置,c是數*/
void InsertElemList(LinkList L,int n,int c){

    int count=0;
    LinkList p,s;
    p =L;   //注意這里的p不在指向第一個節點了
    count =1; 
    while(p->next && count<n){   9         p =p->next;
        ++count;
    }
    s =(Node*)malloc(sizeof(Node));
    s->data=c;
    s->next =p->next;
    p->next=s;
}

主要思路:

1、創建一個節點指針,用於存放頭節點地址,注意這里不是第一個節點。

2、將這個節點移動要插入節點的位置上(插在第幾個位置上就移動幾個位置)

3、然后創建一個要插入的節點並分配內存空間 

4、執行s->next =p->next;    p->next=s;    //注意前后順序不能調

單鏈表的刪除

主要代碼:

/*刪除元素*/
void DeleteElem(LinkList L,int n){
    int count=0;
    LinkList p ,q;
    p =L;   //注意這里的p不在指向第一個節點了
    count =1; 
    while(p->next && count<n){  //
        p =p->next;
        ++count;
    }
    if(!(p->next) || count>n)
        printf("沒有找到可刪除的元素");
    q= p->next;
    p->next = q->next;
     free(q);
}

主要思路:

1、定義一個計數器(用於確定刪除的位置)

2、創建一個指針p,指向頭節點

3、創建一個指針q(用於釋放內存)

4、當指針移動到要刪除元素的前一個,停止 

5,執行q= p->next;  p->next = q->next;  free(q);

單鏈表的建表(頭插法)

顧名思義直接插在第一位,就是頭節點的后面。

void CreateListHead(LinkList *L,int n){

    LinkList p;
    *L = (Node*)malloc(sizeof(Node));  //生成的新節點節點要初始化
    (*L)->next=NULL;    //並指向空

    for(int i=0;i<n;i++){
        p =(Node*)malloc(sizeof(Node)); //新生成的節點節點要初始化
        p->data=i;
        p->next=(*L)->next;  //這里不能指向NULL
        (*L)->next =p;   //兩級指針
    }
}

主要思路:

1、首先創建頭節點,並初始化指向空

2、然后再頭節點和NULL之間插入第一個元素,然后再頭節點和第一個元素插入第二個元素,以此類推...

3、這里要注意的是這里節點的地址要使用二級來存儲,因為我們在使用的malloc為鏈表初始化分配的內存空間在第一次函數調用后內存就失效了

也可以不使用二級指針,直接把初始化鏈表的代碼另外寫在外面即可

4、所以用可以使用二級指針來避免內存泄漏

單鏈表的建表(尾插法)

void CreateListTail(LinkList *L ,int n){

    LinkList p,r;   //生成節點p,存放Node地址
    *L = (Node*)malloc(sizeof(Node));  //生成的頭節點節點要初始化
    (*L)->next=NULL;
    r = *L;  //用於遍歷

    for(int i=0;i<n;i++){
        p =(Node*)malloc(sizeof(Node)); //新生成的節點節點要初始化
        p->data=i;
        r->next=p;
        r=p;  //r指針移動到p上r ,以便
    }
    r->next=NULL; //最后一個指向空
}

主要思路:

1、定義兩個指針,一個r,一個q

2、p用於創建新的節點,r用於確定尾部,這樣每一次插入的時候只要確定r的位置,就確定了尾部的位置

3、二級指針的使用同頭插法

4、核心代碼執行 r->next=p;   r=p;   r->next=NULL;

單鏈表的遍歷

void TraverseList(LinkList L){
    LinkList p;
    p = L->next;
    while(p){
        printf("%d ",p->data);
         p=p->next;
    }
}

清空單鏈表

/*清空鏈表*/
void ClearList(LinkList L){
    LinkList p ,q;
    p = L->next;     //指向第一個元素
    while(p){
        q=p->next;  //q指向了p的下一個
        free(p);   //釋放內存
        p =q;
    }
    L->next =NULL;     //頭節點指向空
}

主要思路:

1、定義一個指針指向第一個節點

2、在定義一個指針用於間接存放即將釋放的內存,用q表示

3、當p移動到要釋放節點內存時,將它賦值給q,然后移動到下一位,依次類推...

 單鏈表的初始化

主要代碼

/*初始化鏈表,傳的是二級指針,操作的是一級指針的地址*/
int InitList(LinkList *L){    
    *L=(LinkList)malloc(sizeof(Node));  //這里的*L就是Node節點的指針對象
    if(!(*L))   //申請內存失敗
        return 0;
    (*L)->next=NULL;
    return 1;
}

也沒有什么,一定要注意在malloc分配的內存在函數調用完成后就會失效,如果想繼續使用,要么借助C++里面的引用,或者二級指針(可能還有其它辦法)

因為內存泄漏問題被搞了一個晚上了 - -

罪過--罪過--

 指針作為參數傳值可以參考大佬資料:https://www.cnblogs.com/WeyneChen/p/6672045.html

其它的一下操作見下面代碼吧

#include <stdio.h>
#include <stdlib.h>
#include "time.h"

/*Node表示一個節點*/
typedef struct Node{
    int data;
    struct Node* next;
}Node;
typedef struct Node* LinkList; /*定義LinkList*/

/*初始化鏈表*/
int InitList(LinkList *L){    
    *L=(LinkList)malloc(sizeof(Node));  //這里的*L就是Node節點的指針對象
    if(!(*L))   //申請內存失敗
        return 0;
    (*L)->next=NULL;
    return 1;
}

//頭插法,n表示插入的個數
void CreateListHead(LinkList *L,int n){

    LinkList p;
    *L = (Node*)malloc(sizeof(Node));  //生成的新節點節點要初始化
    (*L)->next=NULL;    //並指向空

    for(int i=0;i<n;i++){
        p =(Node*)malloc(sizeof(Node)); //新生成的節點節點要初始化
        p->data=i;
        p->next=(*L)->next;  //這里不能指向NULL
        (*L)->next =p;   //兩級指針
    }
}
//尾插法,n表示插入的個數
void CreateListTail(LinkList *L ,int n){

    LinkList p,r;   //生成節點p,存放Node地址
    *L = (Node*)malloc(sizeof(Node));  //生成的頭節點節點要初始化
    (*L)->next=NULL;
    r = *L;  //用於遍歷

    for(int i=0;i<n;i++){
        p =(Node*)malloc(sizeof(Node)); //新生成的節點節點要初始化
        p->data=i;
        r->next=p;
        r=p;  //r指針移動到p上r ,以便
    }
    r->next=NULL; //最后一個指向空
}

/*遍歷鏈表*/
void TraverseList(LinkList L){
    LinkList p;
    p = L->next;
    while(p){
        printf("%d ",p->data);
         p=p->next;
    }
}

/*清空鏈表*/
void ClearList(LinkList L){
    LinkList p ,q;
    p = L->next;     //指向第一個元素
    while(p){
        q=p->next;  //q指向了p的下一個
        free(p);   //釋放內存
        p =q;
    }
    L->next =NULL;     //頭節點指向空
}

/*獲取鏈表長度*/
int GetLengthList(LinkList L){
    LinkList p;
    p=L->next;
    int count=0;   //計數器
    while(p){
        count++;
        p= p ->next;    
    }
    return count;

}

/*刪除元素*/
void DeleteElem(LinkList L,int n){
    int count=0;
    LinkList p ,q;
    p =L;   //注意這里的p不在指向第一個節點了
    count =1; 
    while(p->next && count<n){  //
        p =p->next;
        ++count;
    }
    if(!(p->next) || count>n)
        printf("沒有找到可刪除的元素");
    q= p->next;
    p->next = q->next;
     free(q);
}

/*插入元素 n是位置,c是數*/
void InsertElemList(LinkList L,int n,int c){

    int count=0;
    LinkList p,s;
    p =L;   //注意這里的p不在指向第一個節點了
    count =1; 
    while(p->next && count<n){  //
        p =p->next;
        ++count;
    }
    s =(Node*)malloc(sizeof(Node));
    s->data=c;
    s->next =p->next;
    p->next=s;

}

/* 初始條件:順序線性表L已存在 */
/* 操作結果:返回L中第1個與e滿足關系的數據元素的位序。 */
/* 若這樣的數據元素不存在,則返回值為0 */
int LocateElem(LinkList L,int e){

    LinkList p;
    p =L->next;
    while(p){
        if(p->data == e)
            return 1;
        p =p->next;
    }
    return 0;
}

/*獲取元素,n表示第幾個,並返回查到的值*/
int GetElem(LinkList L,int n){

    LinkList p;   //生成節點p,存放Node地址
    p = L->next;   //p指向第一個元素
    int count=1;   //計數器

    while(p && count<n){ //查找
        p = p->next;
        count++;
    }
    if(!p || count>n){ //沒找到
        return 0;
    }    
    int ret = p->data;    //找到
    return ret;
}

/*判斷鏈表是否為空*/
int ListElmpty(LinkList L){

    if(L->next){
        return 0;
    }else{
        return 1;
    }
}

int main(){
    LinkList L1;   //創建一個節點,用於頭插法
    LinkList L2;   //創建一個節點,用於尾插法

    printf("......頭插法......\n");
    InitList(&L1);  //初始化
    CreateListHead(&L1,5);
    TraverseList(L1);
    printf("\n");

    printf("......尾插法......\n");
    InitList(&L2);  //初始化
    CreateListTail(&L2,5);
    TraverseList(L2);
    printf("\n");

    //獲取元素的值
    int getElem= GetElem(L2,3);
    printf("%d \n",getElem);

    //獲取長度
    int GetLength=GetLengthList(L2);
    printf("L1鏈表的長度:%d",GetLength);
    printf("\n");

    //刪除L1中2號元素
    printf("刪除L1中2號元素:");
    DeleteElem(L1,2);
    TraverseList(L1);
    printf("\n");

    //在第三個位置插入11
    printf("在第三個位置插入11元素:");
    InsertElemList(L1,3,11);
    TraverseList(L1);
    printf("\n");

    int localFind=LocateElem(L1,11);
    printf("找到了嗎: %d\nd",localFind);

    //判斷L1是否為空
    int lstElempty=ListElmpty(L1);
    printf("L1為空嗎: %d\n",lstElempty);
    //清空L1
    ClearList(L1);
    //在判斷L1是否為空
    lstElempty=ListElmpty(L1);
    printf("L1為空嗎: %d\n",lstElempty);

    return 0;
}

 

寫完插入和刪除操作,我們便可以看出,鏈式存儲結構對於插入和刪除的優勢是明顯的,不需要進行大量的元素的移動。

當然單鏈表這么個優秀,也是存在缺點的

缺點就是其不便於進行查找和修改,每查找或者修改一個元素就要開始從頭開始遍歷  - -這么坑爹的嗎 ?沒錯 就是這么坑爹 - -

所以當我們應用的場合不同 ,就用不同的存儲結構。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM