1.鏈表概述
鏈表是一種常見的數據結構。它與常見的數組是不同的,使用數組時先要指定數組包含元素的個數,即爲數組的長度,但是如果向這個數組中加入的元素超過了數組的大小時,便不能將內容全部保存。
鏈表這種存儲方式,其元素個數是不受限定的,當進行添加元素的時候存儲的個數就會隨之改變。
鏈表一般有兩種形式,有空頭鏈表和無空頭鏈表。
在鏈表中有一個頭指針變量,這個指針變量保存一個地址,通過這個地址來找到這個鏈表,頭指針節點指向第一個節點,在鏈表中每個節點包含兩個部分:數據部分和指針部分。雖然結構體不能含有與本身類型相同的結構,但是可以含有之相同類型結構的指針,這種定義是鏈表的基礎,鏈表中每一項都包含在何處能找到下一項的信息。而最後一個節點的指針指向必須爲空NULL,從鏈表的原理來看不用擔心鏈表的長度會超出範圍這種問題。
2.鏈表的基本使用
2.0 準備工作
使用鏈表時,首先應包含一些基本的頭文件,因爲涉及到內存的操作和字符串的操作。
#include "stdio.h"
#include "stdlib.h" //提供malloc()和free()
#include "string.h" //提供strcpy()等
malloc函數
其函數原型如下:
- void *malloc(unsigned int size);
這個函數返回的是個void類型指針,所以在使用時應注意強制類型轉換成需要的指針類型。
free函數
其函數原型如下:
- void free(void *p);
這個函數是用來釋放指針p作指向的內存區。
2.1 創建節點(結構體)
struct Node
{
int a; //數據域
struct Node* next; //指針域(指向節點的指針)
};
2.2 全局定義鏈表頭尾指針 方便調用
struct Node* head= NULL;
struct Node* end = NULL;
2.3 創建鏈表,實現在鏈表中增加一個數據(尾添加)————增
void AddListTill(int a )
{
//創建一個節點
struct Node* temp=(struct Node*)malloc(sizeof(struct Node)); //此處注意強制類型轉換
//節點數據進行賦值
temp->a=a;
temp->next=NULL;
//連接分兩種情況1.一個節點都沒有2.已經有節點了,添加到尾巴上
if(NULL==head)
{
head=temp;
// end=temp;
}
else
{
end->next=temp;
// end=temp; //尾結點應該始終指向最後一個
}
end=temp; //尾結點應該始終指向最後一個
}
AddListTill
函數的功能是尾添加的方式在鏈表的尾部增加一個節點,其中輸入的參數是這個節點的數據。首先創建一個節點並申請一個節點的內存,之後對傳入節點的數據進行賦值,注意尾添加的新節點的指針應指向空;此時分兩種情況,1是鏈表中一個節點都沒有,那麼這個節點既是頭結點也是尾結點;2是已經有節點,那麼新添加的節點將成爲最後一個節點,而之前的節點因爲成爲了倒數第二個節點了所以它的指針應該指向新添加的節點,之後全局變量尾結點應該指向現在的節點(注意操作的先後順序不能變)。
2.4 遍歷鏈表 —————查
void ScanList()
{
struct Node *temp =head; //定義一個臨時變量來指向頭
while (temp !=NULL)
{
printf("%d\n",temp->a);
temp = temp->next; //temp指向下一個的地址 即實現++操作
}
}
ScanList
函數的功能是遍歷這個鏈表,首先定義一個用於遍歷的臨時指針,用while循環實現遍歷輸出等操作。
2.5 查詢指定的節點 (遍歷 一個個找)
struct Node* FindNode(int a )
{
struct Node *temp =head;
while(temp !=NULL)
{
if(a == temp->a)
{
return temp;
}
temp = temp->next;
}
//沒找到
return NULL;
}
FindNode
函數的功能仍然是遍歷鏈表,只不過會對每個節點中的數據進行一一判斷,若找到則返回該節點,若沒找到則返回NULL。
2.6 鏈表清空——————全部刪除
void FreeList()
{
//一個一個NULL
struct Node *temp =head; //定義一個臨時變量來指向頭
while (temp !=NULL)
{
// printf("%d\n",temp->a);
struct Node* pt =temp;
temp = temp->next; //temp指向下一個的地址 即實現++操作
free(pt); //釋放當前
}
//頭尾清空 不然下次的頭就接着0x10
head =NULL;
end =NULL;
}
FreeList
函數仍是採用遍歷的方式一個一個的將節點內存釋放,最後實現全部刪除的效果,但是要注意在最後應該講頭尾節點至NULL否則下次的鏈表將會接着這次的頭尾。
2.7.在指定位置插入節點 ————在指定位置增
void AddListRand(int index,int a)
{
if (NULL==head)
{
printf("鏈表沒有節點\n");
return;
}
struct Node* pt =FindNode(index);
if(NULL==pt) //沒有此節點
{
printf("沒有指定節點\n");
return;
}
//有此節點
//創建臨時節點,申請內存
struct Node* temp =(struct Node *)malloc(sizeof(struct Node));
//節點成員進行賦值
temp->a=a;
temp->next=NULL;
//連接到鏈表上 1.找到的節點在尾部 2.找到的節點在中間
if (pt == end)
{
//尾巴的下一個指向新插入的節點
end->next=temp;
//新的尾巴
end=temp;
}else
{
// 先連後面 (先將要插入的節點指針指向原來找到節點的下一個)
temp->next=pt->next;
//後連前面
pt->next=temp;
}
}
首先要知道在指定節點插入的過程就像手拉手得人在一條線,這時來了一個新人,他要求站在甲之後,首先要找到甲的位置,如果鏈表爲空或者沒有甲則無法插入,如果鏈表不爲空並且甲在這個鏈表中,則還要看甲是在鏈表中間還是甲就在最後的尾巴上,如果在尾巴上則新插入的即爲尾巴如圖1,若在甲乙之間則需要先連上後面乙再連上前面的甲,如圖2。
2.8尾刪除————刪
void DeleteListTail()
{
if (NULL == end)
{
printf("鏈表爲空,無需刪除\n");
return;
}
//鏈表不爲空
//鏈表有一個節點
if (head==end)
{
free(head);
head=NULL;
end=NULL;
}
else
{
//找到尾巴前一個節點
struct Node* temp =head;
while (temp->next!=end)
{
temp = temp->next;
}
//找到了,刪尾巴
//釋放尾巴
free(end);
//尾巴遷移
end=temp;
//尾巴指針爲NULL
end->next=NULL;
}
}
尾刪除的過程和前面一樣首先應判斷這個鏈表是不是爲空或者只有一個節點,若只有一個節點則直接置NULL,若不爲空,則先通過遍歷找到倒數第二個節點,安徽將最後一個節點釋放內存,再講倒數第二個節點設置爲end,然後將它的指針指向NULL。
2.9 刪除頭——————刪
void DeleteListHead()
{ //記住舊頭
struct Node* temp=head;
//鏈表檢測
if (NULL==head)
{
printf("鏈表爲空\n");
return;
}
head=head->next;//頭的第二個節點變成新的頭
free(temp);
}
先定義一個臨時變量指向舊的頭,將頭的第二個記爲新的頭指針head,之後將舊的頭釋放
2.10 刪除指定節點
void DeleteListRand(int a)
{
//鏈表判斷 是不是沒有東西
if(NULL==head)
{
printf("鏈表沒東西\n");
return;
}
//鏈表有東西,找這個節點
struct Node* temp =FindNode(a);
if(NULL==temp)
{
printf("查無此點\n");
return;
}
//找到了,且只有一個節點
if(head==end)
{
free(head);
head=NULL;
end=NULL;
}
else if(head->next==end) //有兩個節點
{
//看是刪除頭還是刪除尾
if(end==temp)
{ DeleteListTail(); }
else if(temp==head)
{ DeleteListHead(); }
}
else//多個節點
{
//看是刪除頭還是刪除尾
if(end==temp)
DeleteListTail();
else if(temp==head)
DeleteListHead();
else //刪除中間某個節點
{ //找要刪除temp前一個,遍歷
struct Node*pt =head;
while(pt->next!=temp)
{
pt=pt->next;
}
//找到了
//讓前一個直接連接後一個 跳過指定的即可
pt->next=temp->next;
free(temp);
}
}
}
情況與前面增加類似不再贅述,具體見圖
3. 測試主程序
下面是測試用的主程序,主要實現了鏈表的增刪查改等基本操作。
void main ()
{
struct Node *pFind ;
//創建5個節點
for(i=0;i<6;i++)
AddListTill(i);
// AddListRand(4,14); //在指定位置4增加節點14
// DeleteListTail(); //刪除一個尾結點
DeleteListRand(4); //刪除4節點
ScanList(); //便利輸出鏈表
FreeList(); //刪除鏈表
/* pFind = FindNode(5); //查找5節點
if (pFind != NULL)
{
printf("找到%d\n",pFind->a); //找到節點並且輸出該節點數據
}
else
{
printf("No Find!\n");
}
*/
}
有關無空頭的單鏈表的基本操作就總結到這裏,當然還有雙鏈表等更復雜的數據結構,以及遍歷和查找的優化算法也有待進一步探索與學習。