敢於向黑暗宣戰的人,心里必須充滿光明。
一、鏈表的構成
1.構成
鏈表是由一連串的結構(稱為結點)組成的。
(1)結點的構成:
數據(要儲存的數據)+指針(指向下一個結點的指針)
(2)關於幾個定義
頭結點:鏈表首結點前的一個結點(不是必須的,但是如果有就可以在解決某些問題時候方便一些,通常可以用來儲存鏈表的長度等信息)
首結點:鏈表的第一個數據元素
頭指針:必須要有的(而頭結點可以沒有,注意兩者一個是指針一個是結點,一個必須有一個可以沒有),指向頭結點/首節點的指針(永遠指向鏈表的第一個結點)
2.結點類型聲明、創建結點
struct node{
int value;
struct node *next;//創建了一個指向一個node類型的指針,用於指向下一個結點
};
//至此聲明了一個結點類型
//頭指針:
struct node *first = NULL;
//結點創建:
struct node *new_node;
new_node = (struct node *)malloc(sizeof(struct node));//給結點分配內存單元。注意:malloc返回的是void類型的指針,所以可以強制類型轉換一下
new_node -> value = 10;//把數據存儲到結點中
二、在鏈表開始處插入結點
struct node * first = NULL;
struct node *new_node;
new_node =(struct node *)malloc(sizeof(struct node));
new_node ->value = 10;
//將new_node結點插入鏈表開始處
new_node->next = first;//new_node指向的node類型的next值為NULL,NULL可作為鏈表結尾(空指針)
first = new_node;//讓first 指向 new_node指向的結點(其實就是剛才malloc出來的結點)
//ok至此,現在就已經有了一個鏈表,它有一個結點,結點中儲存的值是10
new_node = (struct node*)malloc(sizeof(struct node));
new_node ->value = 2;
new_node->next = first;//又新創建了個結點並讓next指向第一次創建的結點(因為first是指向第一次創建的結點的)
first = new_node;//可以理解為把頭指針重置到頭部
我們把它封裝成函數
對於這樣的函數我們傳入一個鏈表list,和一個希望存入鏈表的數值n
struct node* add_to_list(struct node *list,int n)
{
sturct noed *new_node;
new_node = (struct node*)malloc(sizeof(struct node));
if(new_node == NULL)
{
printf("malloc error\n");
exit(0);
}
new_node->value = n;
new_node->next = list;//把新結點接到鏈表中
return new_node;
}
first = add_to_list(first,10);
first = add_to_list(first,20);
//需要注意的是add_to_list函數是沒有辦法修改指針的(因為這個相當於復制了一個指針傳進去,能修改它指向的東西,但是沒有辦法對他本身進賦值存儲)
//所以我們返回一個指向新結點的指針,讓他作為返回值賦值儲存給first
需要注意的是add_to_list函數是沒有辦法修改指針的(因為這個相當於復制了一個指針傳進去,能修改它指向的東西,但是沒有辦法對他本身進賦值存儲),所以我們返回一個指向新結點的指針,讓他作為返回值賦值儲存給first
三、搜索鏈表
while循環可以用,但是我們都知道for循環是很靈活的。這是一張訪問鏈表中結點的習慣方法:
[慣用方法]
for (p = first; p !=NULL; p = p->next)...
這里可以使用指針變量p來追蹤結點,p = p->next 就能實現了讓p從一個結點移動到下一個結點
第一種方法:
struct node* search_list(struct node *list,int n)
{
struct node *p;
for(p = list; p != NULL; p = p->next)
{
if(p -> value == n)
return list;
}
return NULL;
}
第二種方法:
struct node *search_list(struct node *list,int n)
{
for(;list != NULL;list = list->next)
{
if(list->next == n)
return list;
}
return NULL;
}
這里list是原始鏈表指針的副本,所以在函數中對他改變是沒有損害的
四、從鏈表中刪除結點
步驟:
1.定位要刪除的結點(搜索鏈表)
2.改變前一個結點的指向,從而使鏈表“繞過”希望刪除的結點
3.調用free函數收回期望刪除的結點占用的內存空間
一種方法:“追蹤指針”法,在搜索鏈表時總是保留一個指向前一個結點的指針(prev)還有一個指向當前指針的結點(cur)。
如下:(list是帶搜索鏈表,n是要刪除的整數)
for(cur = list,prev = NULL;cur != NULL && cur->value != n;prev = cur,cur = cur ->next);
prev->next = cur->next;//條件為假,結束循環,讓prev指向cur的下一個結點從而完成“繞過”操作
注意:表達式3是每次循環中最后一次被執行的操作
然后在free掉cur,讓prev指向next
封裝成函數:
struct node *delete_list(struct node *list,int n)
{
struct node *prev,*cur;
for(cur = list,prev = NULL;cur != NULL && cur->value != n;prev = cur,cur = cur->next);
//這個for循環只是為了找到希望刪除的結點即value等於n的結點
if(cur == NULL)//找到了最后也沒找到要刪除的結點,不用刪除,返回list
return list;
if(prev == NULL)//條件為假,未執行for循環,即首結點為n,直接讓list指向下一個結點來繞過即可
list = list->next;//對於刪除鏈表中的首結點是一種特殊情況,需要特殊判斷特殊繞過他。
else
prev->next = cur->next;//繞過要刪除的結點
free(cur);//釋放掉被刪除的結點的內存
return list;
}