1. 程式人生 > >單鏈表(完整)

單鏈表(完整)

連結串列:

通過一組任意的儲存單元來儲存線性表中的資料元素,由一個個結點構成。

連結串列之所以讓初學者難以理解,是因為要回溯到上一個結點,並利用這個結點,很多人的思維一直都是往下面追溯,所以才會覺得難以理解,多動手畫畫圖會容易理解許多。

連結串列示意圖:

結點:

結點型別如下:

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;
	}

}

單鏈表的缺點(最主要):

不具有隨機訪問的特點,只能從頭指標開始一個個順序操作進行