線性表的鏈式存儲結構
線性表的實現分順序存儲結構和鏈式存儲結構。
線性表的鏈式存儲結構又稱單鏈表。
上一節我們學習了線性表的順序存儲結構,並實現解順序存儲的基本操作。
這一節我們來學習線性表鏈式存儲結構,那我們再想象一下我為什么我們要引入鏈式存儲結構,萬物存在必有其道理
主要還是因為線性存儲結構存在着這樣一個問題:當我們需要插入和刪除元素時,就必須挪動大量與之無關的元素,因為線性存儲結構結點與節點之間的關系是相鄰關系,一個節點挨着一個節點
如為了插入或者刪除一個元素移動大量的元素,這樣就降低了程序運行效率。
當我們引入
顧名思義鏈式存儲結構,數據與數據之間是以鏈式關系來產生連接的的,我們可以腦補一下鎖鏈的樣子,不扯淡了,進入正題--
我們如何定義一個鏈式存儲結構的節點呢?
/*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; }
寫完插入和刪除操作,我們便可以看出,鏈式存儲結構對於插入和刪除的優勢是明顯的,不需要進行大量的元素的移動。
當然單鏈表這么個優秀,也是存在缺點的
缺點就是其不便於進行查找和修改,每查找或者修改一個元素就要開始從頭開始遍歷 - -這么坑爹的嗎 ?沒錯 就是這么坑爹 - -
所以當我們應用的場合不同 ,就用不同的存儲結構。