單鏈表(完整)
連結串列:
通過一組任意的儲存單元來儲存線性表中的資料元素,由一個個結點構成。
連結串列之所以讓初學者難以理解,是因為要回溯到上一個結點,並利用這個結點,很多人的思維一直都是往下面追溯,所以才會覺得難以理解,多動手畫畫圖會容易理解許多。
連結串列示意圖:
結點:
結點型別如下:
typedef struct lnode{
datatype data; //資料域
struct lnode *next; //指標域
}LNode,*LinkedList;
定義頭指標變數:LinkedList L;
結點示意圖:
必須將第一個結點的地址放到一個指標變數如L中,最後一個結點沒有後繼,其指標域必需置空,表明此表到此結束,這樣就介意從第一個結點的地址開始“順藤摸瓜”,找到每個結點。
頭結點:
為了方便滿足插入和刪除基本操作,我們在連結串列的頭部加入一個“頭結點”,如果沒有頭結點,第一個結點的插入和刪除操作跟其他結點不同,需要單獨分析,為了省去這個麻煩,因此我們定義了一個頭結點,頭結點的型別與資料結點一致,標識連結串列的頭指標變數L中存放該結點的地址,這樣即使是空表,頭指標變數L也不為空了。
頭結點的加入使得“第一個結點”的問題不存在了,也使得處理“空表”和“非空表”的操作變的一致。
頭結點的加入純粹是為了使運算方便,它的資料域即data是沒有定義的,指標域中存放的是第一個數 據結點的地址,空表時,指標域為NULL
注:表頭插入結點不需要頭結點,會增加繁瑣的操作,表尾插入結點增添頭結點會很省事
基本運算:
一、建立單鏈表
(1)在連結串列的頭部插入結點
插入的過程圖:(29,45,18,76,29)連結串列的建立過程
因為實在連結串列的頭部插入,讀入資料的順序和線性中的邏輯順序是相反的
插入的程式碼:
//在表頭插入結點 LinkList Create_LinkList1(){ L = NULL; //定義L為空連結串列 int x; //設資料元素為int型別 LNode *s; scanf("%d",&x); while(x!=-1){ s = (LNode*)malloc(sizeof(LNode)); //申請記憶體 if(s==NULL){ printf("申請記憶體空間失敗!"); break; } s->data = x; s->next = L; //若是第一個結點,則將NULL賦給s,從第二個開始, //因為是從頭部插入,所以s->next指向的是上一輪定義的結點 L = s; //頭指標指向最新的結點s scanf("%d",&x); } return L; //返回頭指標,通過頭指標可以遍歷該連結串列 }
(2)在連結串列的尾部插入結點
在連結串列的頭部插入結點建立單鏈表比較簡單,但讀入資料元素的順序與生成的連結串列中元素的順序是相反的,若希望次序一致,則用尾插入的方法。因為每次是將新結點插入到連結串列的尾部,所以需要加入一個新的指標r(rear的縮寫),該指標r永遠指向連結串列中的尾結點,以便能夠將新結點插入到連結串列的尾部。
插入結點示意圖:
演算法思路:
初始狀態:頭指標 L=NULL,尾指標r=NULL。按線性表中元素的順序依次讀入資料元素,非結束標誌時,申請結點,將新結點插入到r所指結點的後面,然後r指向新結點。(這裡為了方便,該單鏈表是帶有頭結點的)
程式碼:
//在表尾插入結點
LinkList Create_LinkList2(){
L = NULL;
LNode *s; //定義結點
LNode *r; //定義尾指標,永遠指向最後一個結點
int x;
s = (LNode*)malloc(sizeof(LNode)); //定義頭結點,申請記憶體
if(s==NULL){
printf("申請記憶體空間失敗!");
}
s->next = NULL;
L = s; //頭指標指向頭結點
r = s; //尾指標指向頭結點 注:此時連結串列裡面沒有資料結點
scanf("%d",&x);
while(x!=-1){
s = (LNode*)malloc(sizeof(LNode));
if(s==NULL){
printf("申請記憶體空間失敗!");
break;
}
s->data = x;
s->next = NULL;
r->next = s; //將尾結點的next指向最新的結點
r = s; //尾指標指向最新的結點
scanf("%d",&x);
}
return L;
}
二、求表長
演算法思路:
設一個移動指標p和計數器j。初始化後,p所指節點後面若還有結點,p向後移動,計數器自增即++
//獲取連結串列長度(帶頭結點)
int Length_LinkList1(LinkList L){
LNode *p = L;
int j=0;
while(p->next){
p = p->next;
j++;
}
return j;
}
//獲取連結串列長度(不帶頭結點)
int Length_LinkList2(LinkList L){
LNode *p = L; //非空表下指向的就是第一個結點
int j=0;
while(p){
j++;
p = p->next;
}
return j;
}
時間複雜度均為O(n)
三、查詢操作
(1)按序號操作
演算法思路:(均為帶頭結點的情況)
從連結串列的而第一個元素結點起,判斷當前結點是否是第i個,若是,則返回該結點的指標,否則,繼續後一個,表結束為止。沒有第i個結點時返回空。
//按序號查詢單鏈表中的第i個元素結點,找到返回指標,否則返回空 (帶頭結點)
LNode *Get_LinkList(LinkList L,int i){
LNode *p = L;
int j=0;
while(j<i&&p->next!=NULL){
p = p->next;
j++;
}
if(j==i)
return p;
else
return NULL;
}
(2)按值操作
演算法思路:(均為帶頭結點的情況)
從連結串列的而第一個元素結點起,判斷當前結點值是否等於x,若是,則返回該結點的指標,否則,繼續後一個,直到表結束為止。找不到時返回空。
// 按值查詢(帶頭結點)
LNode *Locate_LinkList(LinkList L,int x){
LNode *p = L->next;
while(p!=NULL&&p->data!=x){
p = p->next;
}
return p;
}
時間複雜度均為O(n)
插入和刪除操作都比較容易理解,程式碼中有註釋,這裡就不多說了
四、插入操作
//插入(前插結點)(帶頭結點)(失敗返回0,成功返回1)
int Insert_LinkList(LinkList L,int i,int x){
LNode *p,*s;
p = Get_LinkList(L,i-1); //獲取第i-1個結點
if(p==NULL){
printf("引數i錯誤!\n");
return 0;
}
else{
s = (LNode*)malloc(sizeof(LNode)); //申請、填裝結點
if(s==NULL){
printf("申請記憶體空間失敗!");
return 0;
}
s->data = x;
s->next = p->next;
p->next = s;
return 1;
}
}
五、刪除操作
//刪除結點
int Delete_LinkList(LinkList L,int i){
LinkList p,s;
p = Get_LinkList(L,i-1); //獲取第i-1個節點
if(p==NULL){
printf("第i-1個結點不存在\n");
return -1;
}else if(p->next==NULL){
printf("第i個結點不存在");
return 0;
}else{
s = p->next;
p->next = s->next;
free(s); //釋放*s;
return 1;
}
}
六、遍歷連結串列
void Find(LinkList L){
LNode *p = L->next;
int i=0;
while(p){
i++;
printf("---->|Node%d->data:%d|\n",i,p->data);
p = p->next;
}
}
附完整的單鏈表程式碼:
#include <stdio.h>
#include <malloc.h>
typedef int Elemtype; //Elemtype定義為int型
typedef struct lnode{ //結點
Elemtype data; //資料域
struct lnode *next; //指標域
}LNode,*LinkList; //LNode是結點型別,LinkList是指向LNode型別結點的指標型別
LinkList L; //定義頭指標變數
//在表頭插入結點
LinkList Create_LinkList1(){
L = NULL; //定義L為空連結串列
int x; //設資料元素為int型別
LNode *s;
scanf("%d",&x);
while(x!=-1){
s = (LNode*)malloc(sizeof(LNode)); //申請記憶體
if(s==NULL){
printf("申請記憶體空間失敗!");
break;
}
s->data = x;
s->next = L; //若是第一個結點,則將NULL賦給s,從第二個開始,
//因為是從頭部插入,所以s->next指向的是上一輪定義的結點
L = s; //頭指標指向最新的結點s
scanf("%d",&x);
}
return L; //返回頭指標,通過頭指標可以遍歷該連結串列
}
//在表尾插入結點
LinkList Create_LinkList2(){
L = NULL;
LNode *s; //定義結點
LNode *r; //定義尾指標,永遠指向最後一個結點
int x;
s = (LNode*)malloc(sizeof(LNode)); //定義頭結點,申請記憶體
if(s==NULL){
printf("申請記憶體空間失敗!");
}
s->next = NULL;
L = s; //頭指標指向頭結點
r = s; //尾指標指向頭結點 注:此時連結串列裡面沒有資料結點
scanf("%d",&x);
while(x!=-1){
s = (LNode*)malloc(sizeof(LNode));
if(s==NULL){
printf("申請記憶體空間失敗!");
break;
}
s->data = x;
s->next = NULL;
r->next = s; //將尾結點的next指向最新的結點
r = s; //尾指標指向最新的結點
scanf("%d",&x);
}
return L;
}
//獲取連結串列長度(帶頭結點)
int Length_LinkList1(LinkList L){
LNode *p = L;
int j=0;
while(p->next){
p = p->next;
j++;
}
return j;
}
//獲取連結串列長度(不帶頭結點)
int Length_LinkList2(LinkList L){
LNode *p = L; //非空表下指向的就是第一個結點
int j=0;
while(p){
j++;
p = p->next;
}
return j;
}
//按序號查詢單鏈表中的第i個元素結點,找到返回指標,否則返回空 (帶頭結點)
LNode *Get_LinkList(LinkList L,int i){
LNode *p = L;
int j=0;
while(j<i&&p->next!=NULL){
p = p->next;
j++;
}
if(j==i)
return p;
else
return NULL;
}
// 按值查詢(帶頭結點)
LNode *Locate_LinkList(LinkList L,int x){
LNode *p = L->next;
while(p!=NULL&&p->data!=x){
p = p->next;
}
return p;
}
//插入(前插結點)(帶頭結點)(失敗返回0,成功返回1)
int Insert_LinkList(LinkList L,int i,int x){
LNode *p,*s;
p = Get_LinkList(L,i-1); //獲取第i-1個結點
if(p==NULL){
printf("引數i錯誤!\n");
return 0;
}
else{
s = (LNode*)malloc(sizeof(LNode)); //申請、填裝結點
if(s==NULL){
printf("申請記憶體空間失敗!");
return 0;
}
s->data = x;
s->next = p->next;
p->next = s;
return 1;
}
}
//刪除結點
int Delete_LinkList(LinkList L,int i){
LinkList p,s;
p = Get_LinkList(L,i-1); //獲取第i-1個節點
if(p==NULL){
printf("第i-1個結點不存在\n");
return -1;
}else if(p->next==NULL){
printf("第i個結點不存在");
return 0;
}else{
s = p->next;
p->next = s->next;
free(s); //釋放*s;
return 1;
}
}
//遍歷連結串列
void Find(LinkList L){
LNode *p = L->next;
int i=0;
while(p){
i++;
printf("---->|Node%d->data:%d|\n",i,p->data);
p = p->next;
}
}
void list(){
printf("This is a Singly Linked List.\n------------------------------------------\nPlease press the button:\n");
printf("Button 1 ---> Create_LinkList()\n"); //建立單鏈表 (帶頭結點、表尾插入)
printf("Button 2 ---> Length_LinkList(L)\n"); //獲取連結串列長度
printf("Button 3 ---> Get_LinkList(L,i)\n"); //按序號查詢
printf("Button 4 ---> Locate_LinkList(L,x)\n"); //按值查詢
printf("Button 5 ---> Insert_LinkList(L,i,number)\n"); //插入結點
printf("Button 6 ---> Delete_LinkList(L,i)\n"); //刪除結點
printf("Button 7 ---> Find(L)\n"); //遍歷連結串列
printf("Button 8 ---> Exit the program\n-----------------------------------\n"); //退出程式
}
int main(){
list();
while(true){
printf("Choose Button: ");
int n;
int flag =1;
int length,i,number,item;//item用於Insert插入和Detele刪除的返回資料
LNode *p;
scanf("%d",&n);
switch(n){
case 1:
printf("If you enter '-1',the list is created\n"); //若輸入-1,則表示連結串列元素建立完成
L = Create_LinkList2();
printf("--------------------------------------------------------\n");
break;
case 2:
length = Length_LinkList1(L);
printf("The Singly Link List length is: %d\n-------------------------------------\n",length); //單鏈表的長度為:
break;
case 3:
printf("Please enter you want to find serial number-->i: ");//請輸入你想查詢的序號
scanf("%d",&i);
p =Get_LinkList(L,i);
if(p==NULL){
printf("Sorry!Wrong!\n----------------------------------------------\n"); //第i個位置為NULL,錯誤
}else{
printf("Successsful!The Node data is:%d\n--------------------------------------\n",p->data);//不為NULL,查詢成功,輸出該結點資料域
}
break;
case 4:
printf("Please enter you want to find number: ");
scanf("%d",&number);
p = Locate_LinkList(L,number);
if(p==NULL)
printf("Sorry!Wrong!\n-------------------------------------------------\n"); //第i個位置為NULL,錯誤
else
printf("Successsful!The Node data is:%d\n-------------------------------------------\n",p->data);//不為NULL,查詢成功,輸出該結點資料域
break;
case 5: //插入結點
printf("Please enter i and number: ");
scanf("%d%d",&i,&number);
item = Insert_LinkList(L,i,number);
if(item==0)
printf("Insert failed-----------------------------------------------------\n");
else
printf("Insert successful--------------------------------------------------------\n");
break;
case 6: //刪除結點
printf("Please enter i: ");
scanf("%d",&i);
item = Delete_LinkList(L,i);
if(item==0 || item==-1)
printf("Delete failed----------------------------------------------------------\n");
else
printf("Delete successful-------------------------------------------------------------\n");
break;
case 7:
printf("Traverse Singly Linked List:\n"); //遍歷該單鏈表
Find(L);
printf("-------------------------------------------------------------------------\n");
break;
case 8:
printf("Exit the program successful!\n");
flag = -1;
break;
default:
printf("Sorry!You can't do that!\n");
flag = -1;
break;
}
if(flag==-1)
break;
}
}
單鏈表的缺點(最主要):
不具有隨機訪問的特點,只能從頭指標開始一個個順序操作進行