# Linked List 連結串列
[TOC]
## 介紹
Linked List (連結串列)是一種常見的資料結構,利用 **node 節點** 來記錄、表示、儲存資料,且利用每個 node 的 **pointer 指標** 來指向下一個 node,以此來連接多個 node,並以 **NULL** 來代表 Linked List 的終點。
```graphviz
digraph obj{
node[shape=record]
rankdir="LR"
A[label = "Node 1|{Data: 12|Pointer: 0x100104300}|Address: 0x1001042f0"]
B[label = "Node 2|{Data: 7|Pointer: NULL}|Address: 0x100104300"]
C[label=NULL]
A->B
B->C
}
```
## 程式碼
以下範例是自行設定 node 的數量以及每一個 node 內的 data 值。
```C=
#include <stdio.h>
#include <stdlib.h>
struct node{
int data;
struct node *next;
};
typedef struct node Node;
int main(){
int i,val,num;
Node *first,*current,*previous;
printf("Number of nodes : ");
scanf("%d",&num);
for (i=0;i<num;i++){
current=(Node*)malloc(sizeof(Node)); /*建立新的node*/
printf("Data for node %d : ",i+1);
scanf("%d",&(current->data)); /*輸入node的data成員*/
if (i==0){ /*若此為第一個node*/
first=current; /*將指標first指向目前的節點*/
previous=current; /*將指標previous指向目前的節點*/
}
else{
previous->next=current; /*將前一個node的pointer指向目前的節點*/
current->next=NULL; /*將目前的pointer指向NULL*/
previous=current; /*將前一個node設為目前的node*/
}
}
current=first;
while(current!=NULL){
printf("address=%p,",current);
printf("data=%d,",current->data);
printf("next=%p,\n",current->next);
current=current->next;
}
return 0;
}
```
執行後可以得到以下結果:
:::success
Number of nodes : 3
Data for node 1 : 61
Data for node 2 : 85
Data for node 3 : 49
address=00000000000813C0,data=61,next=00000000000813E0,
address=00000000000813E0,data=85,next=0000000000081400,
address=0000000000081400,data=49,next=0000000000000000,
:::
* 程式解說 :
在此程式中,我們運用到了 **動態記憶體配置(malloc)** 有效的解決記憶體過多或過少的問題。在第12行的三個指向NODE型態的指標 first、current 與 previous 分別表示為指向串列的第一個節點、現在正要處理的點,以及前一個節點的指標。我們將三個 node 的 data 、 pointer 和位址都印出來,可以清楚知道前一個 node 的 pointer 指向下一個 node 的位址。
## 相關函式
### 列印函數 PrintList( )
輸入串列中第一個 node 的位址,並且利用 **走訪(traversal)** 的方式將串列中所有節點的 data 印出。
* **走訪(traversal)** : 即為沿著連結的方向,經過每一個 node。
```C=
void PrintList(Node* first){
Node* node=first; /*將node指向第一個節點*/
if (first==NULL){ /*如果串列是空的,則印出 List is empty!*/
printf("List is empty!\n");
}
else{ /*如果不是,則走訪並印出所有node的data值*/
while(node!=NULL){
printf("%d ",node->data);
node=node->next;
}
}
}
```
### 釋放記憶空間函數 FreeList( ) or ClearList( )
當串列不再使用時,需要回收由 malloc( ) 所佔去的記憶體空間。一樣利用 **走訪(traversal)** 的方式,輸入串列中第一個 node 的位址,依序將每個 node 的記憶空間釋放掉。
```C=
void FreeList(Node* first){
Node *current, *tmp;
current=first;
while(current!=NULL){
tmp=current;
currnet=current->next;
free(tmp);
}
}
```
### 搜尋函數 SearchNode( )
當我們要刪除或是插入 node 時,必須要先知道我們要找的位址在哪裡,因此藉由這個函數,我們可以輸入所要插入的串列位置以及所要找的data,透過 **走訪(traversal)** 的方式來找到第一筆符合此data的位址。
```C=
Node* SearchNode(Node* first, int item){
Node* node=first;
while(node->data!=NULL){
if (node->data==item){
return node;
}
else{
node=node->next;
}
}
return NULL; /*如果找不到符合的節點,則回傳NULL*/
}
```
### 插入節點函數 InsertNode( )
在找到要插入地位址之後,便可以將所要插入的 node 資料放進去。InsertNode函數接收兩個引數,節點的位址以及所要輸入的 data。
```C=
void InsertNode(Node* node, int item){
Node* newnode=(Node*)malloc(sizeof(Node));
newnode->data=item;
newnode->next=node->next;
node->next=newnode;
}
```
另外,為了方便操作,以下再介紹兩個函式 **Push_front( )** 以及 **Push_back( )** ,
分別是在串列的**第一個前**以及**最後一個之後**再插入新的資料。
* #### Push_front( )
```C=
void Push_front(int a){
Node* newnode=(Node*)malloc(sizeof(Node));/*建立新的node*/
newnode->data=a; /*輸入node的data值*/
newnode->next=first; /*將新的node的pointer指向first*/
first=newnode; /*將first換到新的node的位置*/
}
```
* #### Push_back( )
```C=
void Push_back(int a){
Node* newnode=(Node*)malloc(sizeof(Node));/*建立新的node*/
newnode->data=a;
newnode->next=NULL;
if (first==0){ /*如果list是空的,令newnode為first*/
first=newnode;
}
else{
Node* current=first;
while (current->next!=NULL){ /*尋找最後一個node的位置*/
current=current->next;
}
current->next=newnode; /*將newnode接上尾巴的位置*/
}
}
```
### 刪除節點函式
輸入串列的位址以及所想要刪除的 node 的位址,經過刪除後,可以得到新的串列的位址。當我們要刪除的時候,記得要作釋放記憶體的動作!!
```C=
Node* DeleteNode(Node* first, Node* node){
Node* ptr=first;
if (first==NULL){ /*當串列是空的時候,印出Nothing to delete!*/
printf("Nothing to delete!\n");
return NULL;
}
if(node==first){ /*當第一個是要刪除的node時*/
first=first->next; /*將first更改為下一個節點的位址*/
free(node); /*釋放記憶體*/
return first;
}
else{
while(ptr->next!=node){ /*尋找所要刪除的node的位址*/
ptr=ptr->next;
}
ptr->next=node->next; /*將前一個node的pinter指向下一個node*/
free(node); /*釋放記憶體*/
return first;
}
}
```
### 反轉串列函式 Reverse( )
將串列的順序整個顛倒過來。
```graphviz
digraph obj{
node[shape=record]
rankdir="LR"
A[label = "A|Data: 12"]
B[label = "B|Data: 7"]
C[label = "C|Data: 53"]
N[label=NULL]
A->B
B->C
C->N
}
```
將第一個 node(A) 的 Pointer 指向 NULL,再將後面一個 node(B) 指向自己。
```graphviz
digraph obj{
node[shape=record]
rankdir="LR"
A[label = "A|Data: 12"]
B[label = "B|Data: 7"]
C[label = "C|Data: 53"]
N1[label=NULL]
N2[label=NULL]
A->B[style=dashed]
B->C
C->N1
N2->A[dir="back",arrowhead="normal",arrowtail="normal"]
}
```
不過此時原本B的位址只有A知道,經過這個步驟後,B的位址就找不到了。因此我們會需要多一個指標來記錄下一個 node 的位址。最後即可得到以下串列
```graphviz
digraph obj{
node[shape=record]
rankdir="LR"
A[label = "A|Data: 12"]
B[label = "B|Data: 7"]
C[label = "C|Data: 53"]
N1[label=NULL]
N2[label=NULL]
A->B[dir="back",arrowhead="normal",arrowtail="normal"]
B->C[dir="back",arrowhead="normal",arrowtail="normal"]
C->N1[style=dashed]
N2->A[dir="back",arrowhead="normal",arrowtail="normal"]
}
```
```C=
void Reverse(){
if (first==0||first->next==0){
return ;
}
Node *previous, *current, *preceding ;
previous=NULL;
current=first;
preceding=first->next;
while (preceding!=NULL){
current->next=previous;
previous=current;
current=preceding;
preceding=preceding->next;
}
current->next=previous;
first=current;
}
```
### 範例
最後我們將以上的所有函式套入以下的範例。其中我們先將串列的 data 設定好,再套用函式進去
```C=
#include <stdio.h>
#include <stdlib.h>
struct node{ /*建立structure*/
int data;
struct node *next;
};
typedef struct node Node;
Node *current, *first, *previous;
void InsertNode(Node* node, int item){ /*插入node*/
Node* newnode=(Node*)malloc(sizeof(Node));
newnode->data=item;
newnode->next=node->next;
node->next=newnode;
}
void PrintList(Node* first){ /*印出串列*/
Node* node=first;
if (first==NULL){
printf("List is empty!\n");
}
else{
while(node!=NULL){
printf("%d ",node->data);
node=node->next;
}
printf("\n");
}
}
Node* SearchNode(Node* first, int item){ /*搜尋節點位址*/
Node* node=first;
while(node->data!=NULL){
if (node->data==item){
return node;
}
else{
node=node->next;
}
}
return NULL;
}
void Push_front(int a){ /*插入新node在前面*/
Node* newnode=(Node*)malloc(sizeof(Node));
newnode->data=a;
newnode->next=first;
first=newnode;
}
void Push_back(int a){ /*插入新node在尾巴*/
Node* newnode=(Node*)malloc(sizeof(Node));
newnode->data=a;
newnode->next=NULL;
if (first==0){
first=newnode;
}
else{
Node* current=first;
while (current->next!=NULL){
current=current->next;
}
current->next=newnode;
}
}
void FreeList(Node* first){ /*釋放記憶空間*/
Node *current, *tmp;
current=first;
while(current!=NULL){
tmp=current;
current=current->next;
free(tmp);
}
}
Node* DeleteNode(Node* first, Node* node){ /*刪除節點*/
Node* ptr=first;
if (first==NULL){
printf("Nothing to delete!\n");
return NULL;
}
if(node==first){
first=first->next;
free(node);
return first;
}
else{
while(ptr->next!=node){
ptr=ptr->next;
}
ptr->next=node->next;
free(node);
return first;
}
}
void Reverse(){ /*反轉串列*/
if (first==0||first->next==0){
return ;
}
Node *previous, *current, *preceding ;
previous=NULL;
current=first;
preceding=first->next;
while (preceding!=NULL){
current->next=previous;
previous=current;
current=preceding;
preceding=preceding->next;
}
current->next=previous;
first=current;
}
int main(){
int num[5]={9,54,61,5,27}; /*設定串列的個數及data*/
int i;
for (i=0;i<5;i++){
current=(Node*)malloc(sizeof(Node));
if (i==0){
first = current;
}
else{
previous->next=current;
}
current->data=num[i];
current->next=NULL;
previous=current;
}
current=first;
PrintList(first);
Push_front(38); /*分別在串列前後插入新節點38,65*/
Push_back(65);
printf("After Push_front() and Push_back():\n");
PrintList(first);
Node* node1=SearchNode(first,61); /*搜尋61所在的node位址,並插入新節點46*/
InsertNode(node1,46);
printf("After SearchNode() and InsertNode():\n");
PrintList(first);
Reverse(); /*反轉整個串列*/
printf("After Reverse():\n");
PrintList(first);
Node* node2=SearchNode(first,54); /*搜尋並刪除54所在的node位址*/
DeleteNode(first,node2);
printf("After SearchNode() and DeleteNode():\n");
PrintList(first);
FreeList(first); /*釋放記憶體*/
return 0;
}
```
執行後可以得到以下結果 :
:::success
9 54 61 5 27
After Push_front() and Push_back():
38 9 54 61 5 27 65
After SearchNode() and InsertNode():
38 9 54 61 46 5 27 65
After Reverse():
65 27 5 46 61 54 9 38
After SearchNode() and DeleteNode():
65 27 5 46 61 9 38
:::