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