1. 程式人生 > >C語言程式設計 學習筆記 連結串列

C語言程式設計 學習筆記 連結串列

接可變陣列
但如果我們可以使用BLOCK,將其都拼接在一起,並不是用上面的方法複製貼上。每一個BLOCK會有一個單元指向的是下一個BLOCK的地址,這樣就不會有上述的問題了
在這裡插入圖片描述

所以對於一個單元,它裡面應該分成兩部分:
1.資料
2.下一個單元的地址(指標)
這樣指向的下一個資料結構也應是如此。
直到最後一個單元,儲存的是資料,地址用一個標記標誌它已經沒有指向下一個地方了
當然最開始,還需要一個指標指向第一個單元的地址,俗稱head
這樣的東西就叫做連結串列(linklist),每一個單元叫做節點
定義:

typedef struct _node{
	int value;
	struct _node *next;
} Node;

對於往連結串列內加入資料,程式碼實現如下:

#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
//	int value;
//	struct _node *next;
//} Node;

int main(int argc, char** argv) {
	Node *head = NULL;//最開始head指向的是null 
	int number;
	do{
		cin>>number;
		if(number != -1){
			//add to linked-list,這是新的一個p,它是要放到最後去的 
			Node *p = (Node*)malloc(sizeof(Node));
			p->value = number;
			p->next = NULL;
			
			//find the last,找到目前連結串列中的最後一個節點
			//該節點的next還是null,我們要讓其指向p 
			Node *last = head;
			if( last ){//最初始的時候last=head,head=null,所以此時的last->next是無效的,為了防止這個bug發生,新增if保險 
				while(last->next){//直到迴圈結束,那麼last->next就是最後一個null了 
					last = last->next;
				}
				//找到目前連結串列的末尾,讓其指向p 
				last->next = p;
			} else {
				head = p;//如果last是null,那麼head就是p,這個else只會在初次賦值的時候用到 
			}
		}
	} while(number != -1);
	return 0;
}

2.連結串列的函式
在上述do-while函式內的程式碼,實質上就是一個對連結串列進行賦值的功能,那麼我們可以將其封裝成一個函式,這樣更易使用,如:

#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
//	int value;
//	struct _node *next;
//} Node;

void add(Node* head, int number);
int main(int argc, char** argv) {
	Node *head = NULL;//最開始head指向的是null 
	int number;
	do{
		cin>>number;
		if(number != -1){
			add(head,number)
		}
	} while(number != -1);
	return 0;
}

void add(Node* head,int number){
	Node *p = (Node*)malloc(sizeof(Node));
	//add to linked-list,這是新的一個p,它是要放到最後去的 		
	p->value = number;
	p->next = NULL;
	
	//find the last,找到目前連結串列中的最後一個節點
	//該節點的next還是null,我們要讓其指向p 
	Node *last = head;
	if( last ){//最初始的時候last=head,head=null,所以此時的last->next是無效的,為了防止這個bug發生,新增if保險 
		while(last->next){//直到迴圈結束,那麼last->next就是最後一個null了 
			last = last->next;
		}
		//找到目前連結串列的末尾,讓其指向p 
		last->next = p;
	} else {
		head = p;//如果last是null,那麼head就是p,這個else只會在初次賦值的時候用到 
	}
} 

但這樣有非常嚴重的問題,在add函式中,我的head是一個傳參,在函式中的head = p; 這個操作實質上並沒有任何用,add函式用完後的傳參被釋放,head永遠是NULL

解決方法
1.將Node *head設為全域性變數
缺陷:全域性變數是有害的,是一次性的,這個head只對一個連結串列起作用,如果一個程式有多個連結串列,全設為全域性變數很危險

2.將add函式設定一個返回值head,執行完畢後將head的值返回回去

head = add(head, number);

這方法看似不錯,但是在使用的時候感覺會很奇怪(尤其是給他人使用這個方法的時候)——只不過想add值進去,為什麼要返回值呢?要返回也是返回add成功與否吧?

3.傳head的指標回去
那這樣,函式的應該為

void add(Node **pHead, int number);

但是用到**的時候就要注意,指標的指標是一個非常複雜的東西,一不小心會產生難以想象的錯誤,且指向指標的指標程式碼也不易於閱讀

4.建立一個結構體list,裡面存的是Node *head,再對其進行使用
函式呼叫的時候是呼叫list的地址,實質上其實是和3是一樣的,但是換了一個更易閱讀的方式。也就是通過自己定義的資料結構來代表整個list,程式碼:

#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
//	int value;
//	struct _node *next;
//} Node;

typedef struct _list{
	Node *head;
} List;

void add(List *plist, int number);
int main(int argc, char** argv) {
//	Node *head = NULL;//最開始head指向的是null 
	List list;
	list.head = NULL;
	int number;
	do{
		cin>>number;
		if(number != -1){
			add(&list, number);
		}
	} while(number != -1);
	return 0;
}

void add(List *plist, int number){
	Node *p = (Node*)malloc(sizeof(Node));
	//add to linked-list,這是新的一個p,它是要放到最後去的 		
	p->value = number;
	p->next = NULL;
	
	//find the last,找到目前連結串列中的最後一個節點
	//該節點的next還是null,我們要讓其指向p 
	Node *last = plist->head;
	if( last ){//最初始的時候last=head,head=null,所以此時的last->next是無效的,為了防止這個bug發生,新增if保險 
		while(last->next){//直到迴圈結束,那麼last->next就是最後一個null了 
			last = last->next;
		}
		//找到目前連結串列的末尾,讓其指向p 
		last->next = p;
	} else {
		plist->head = p;//如果last是null,那麼head就是p,這個else只會在初次賦值的時候用到 
	}
} 

思考:每次加入新東西的時候都要用last從head開始從頭尋找最後一個單元再做後續的動作,“每次都要遍歷”這個操作非常有重複性並且浪費時間,那麼我們能否就定義一個東西,讓它一直指向的就是我們的最後一個單元呢?(視訊內沒講了,提示:在list裡再加一個Node *tail)

連結串列的搜尋
遍歷:
一個指標,拿到head,輸出value
變到下一個地址
當沒有下一個地址(最後指向的是null)迴圈結束
可以用for,也可以用while,for迴圈更加易懂:

	Node *p;//開始遍歷
	for( p = list.head; p; p = p->next){
		cout<<p->value<<' ';
	} 
	cout<<endl;

同樣的,可以封裝成函式,取名為print,我們需要的只是head

void print(List *plist){
	Node *p;//開始遍歷
	for( p = plist->head; p; p = p->next){
		cout<<p->value<<' ';
	} 
	cout<<endl;
}

既然能遍歷了,那麼我想尋找連結串列中某一個特定的數那也很方便了

cin>>number;
	Node *p;
	int isfound = 0;
	for(p = list.head; p; p->next){
		if(p->value == number){
			cout<<"find it"<<endl;
			isfound = 1;
			break;
		}
	}
	if(!isfound){
		cout<<"don't find it"<<endl;
	}

那麼找到這個之後,我們想刪除這塊
單元,該如何考慮
1.該單元的前面一個單元所指的位置就不應該是待刪除的這塊了,而是下一塊
2.free掉這個待刪除的單元

1.1 如何找到上一個單元?
再建立一個指標q,那個指標指向它前面那個單元,這樣只要讓q.next = p.next就可以安心free(p)

Node *q;
	for(q = NULL,p = list.head; p; q=p,p = p->next){//先讓q=p,p再向後走,這樣的q就是上一個p了 
		if(p->value == number){
			q->next = p->next;
			free(p);
			break;
		}
	}

但是這樣有一個問題,q初始值是null,對於如果第一個單元就是要去掉的,此時我們要修改的是head所指的地方,而不是對q有什麼操作。
在如此情況下,上述程式碼中的q->next是非法、無意義的
所以我們需要通過語句將其保護起來(讓程式碼更加嚴謹)
程式碼實現:

Node *q;
	for(q = NULL,p = list.head; p; q=p,p = p->next){//先讓q=p,p再向後走,這樣的q就是上一個p了 
		if(p->value == number){
			if(q){
				q->next = p->next;
			} else {
				list.head = p->next;
			}
			free(p);
			break;
		}
	}

連結串列的清除
每次遍歷到一個,先有一個臨時變數儲存next,然後free(),接著到下一個節點,繼續free,直到p=NULL結束

	for( p = list.head; p; p=q){
		q = p->next;
		free(p);
	}

程式設計入門-連結串列到此為止,至此將此課的程式碼全部奉上:
node.h:

#ifndef _NODE_H_ 
#define _NODE_H_

typedef struct _node{
	int value;
	struct _node *next;
} Node;

#endif

main.cpp:

#include <iostream>
#include "node.h"
#include <stdlib.h>
using namespace std;
//typedef struct _node{
//	int value;
//	struct _node *next;
//} Node;

typedef struct _list{
	Node *head;
} List;

void add(List *plist, int number);
void print(List *plist);
int main(int argc, char** argv) {
//	Node *head = NULL;//最開始head指向的是null 
	List list;
	list.head = NULL;
	int number;
	do{
		cin>>number;
		if(number != -1){
			add(&list, number);
		}
	} while(number != -1);
//	print(&list);

	cin>>number;//尋找值 
	Node *p;
	int isfound = 0;
	for(p = list.head; p; p->next){
		if(p->value == number){
			cout<<"find it"<<endl;
			isfound = 1;
			break;
		}
	}
	if(!isfound){
		cout<<"don't find it"<<endl;
	}
	
	Node *q;//刪除指定值 
	for(q = NULL,p = list.head; p; q=p,p = p->next){//先讓q=p,p再向後走,這樣的q就是上一個p了 
		if(p->value == number){
			if(q){
				q->next = p->next;
			} else {
				list.head = p->next;
			}
			free(p);
			break;
		}
	}
	
	for( p = list.head; p; p=q){//連結串列的清除 
		q = p->next;
		free(p);
	}
	return 0;
}

void add(List *plist, int number){
	Node *p = (Node*)malloc(sizeof(Node));
	//add to linked-list,這是新的一個p,它是要放到最後去的 		
	p->value = number;
	p->next = NULL;
	
	//find the last,找到目前連結串列中的最後一個節點
	//該節點的next還是null,我們要讓其指向p 
	Node *last = plist->head;
	if( last ){//最初始的時候last=head,head=null,所以此時的last->next是無效的,為了防止這個bug發生,新增if保險 
		while(last->next){//直到迴圈結束,那麼last->next就是最後一個null了 
			last = last->next;
		}
		//找到目前連結串列的末尾,讓其指向p 
		last->next = p;
	} else {
		plist->head = p;//如果last是null,那麼head就是p,這個else只會在初次賦值的時候用到 
	}
} 
void print(List *plist){
	Node *p;//開始遍歷
	for( p = plist->head; p; p = p->next){
		cout<<p->value<<' ';
	} 
	cout<<endl;
}