線性表的鏈式存儲結構
線性表的實現分順序存儲結構和鏈式存儲結構。
線性表的鏈式存儲結構又稱單鏈表。
上一節我們學習了線性表的順序存儲結構,並實現解順序存儲的基本操作。
這一節我們來學習線性表鏈式存儲結構,那我們再想象一下我為什么我們要引入鏈式存儲結構,萬物存在必有其道理
主要還是因為線性存儲結構存在着這樣一個問題:當我們需要插入和刪除元素時,就必須挪動大量與之無關的元素,因為線性存儲結構結點與節點之間的關系是相鄰關系,一個節點挨着一個節點
如為了插入或者刪除一個元素移動大量的元素,這樣就降低了程序運行效率。
當我們引入
顧名思義鏈式存儲結構,數據與數據之間是以鏈式關系來產生連接的的,我們可以腦補一下鎖鏈的樣子,不扯淡了,進入正題--
我們如何定義一個鏈式存儲結構的節點呢?
/*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;
}

寫完插入和刪除操作,我們便可以看出,鏈式存儲結構對於插入和刪除的優勢是明顯的,不需要進行大量的元素的移動。
當然單鏈表這么個優秀,也是存在缺點的
缺點就是其不便於進行查找和修改,每查找或者修改一個元素就要開始從頭開始遍歷 - -這么坑爹的嗎 ?沒錯 就是這么坑爹 - -
所以當我們應用的場合不同 ,就用不同的存儲結構。
