# [資料結構] Linked List
## 前言
* 特別注意,我們學校的資料結構課程其實沒有教這個......直接當作我們已經會了,所以寫得不夠好的話請見諒。
* 這東西寫久了,有時候結構中一些小地方會莫名其妙和別人不太一樣。
* 然後不得不說,這東西的概念一定要懂,實作也一定要實際去做,不然到後面Tree的node連結你八成也搞不懂。
* 好了,正文開始。
---
## 陣列?動態陣列?Linked List?
* 首先,不知道陣列是什麼的可以左轉了。
* 這就是一個大小為`5`的簡單陣列:
```C++
int arr[5];
```
* 那什麼是動態陣列?今天如果有一到題目,先給你數字`n`,再給你`n`個數字$A_1,A_2,...A_n$,那你要怎麼把數字存到陣列中呢?
* 新手大多會這樣做:
```C++
int n, arr[10000];
cin >> n;
for(int i = 0; i < n; i++)
cin >> arr[i];
```
* 也就是事先給一個超大的記憶體空間,但這樣你自己也知道有問題。
* 數量超過10000就爆了(你用更大也一樣,都有個極限)。
* 如果數量只有10,你直接就浪費了9990個空間大小。
---
* 實際上你可以這樣:
```C++
int n;
int *arr = NULL;
cin >> n;
arr = new int[n];
for(int i = 0; i < n; i++)
cin >> arr[i];
```
* 也就是說,arr一開始是一個沒有意義的指標。
* 而當你確認好空間實際上要多少之後,再和記憶體去要空間出來。
* 然而使用上面的動態陣列依然在某些情況會不太優秀。
* 如果我需要頻繁地新增、刪除資料,這時候你有兩種做法:
* 每次資料有所變更時都重新用新空間。
* 時間成本太高。
* 一次給多一點空間,當容量不夠時再擴大。
* 其實C++內建的vector就是這種方式,但依然會浪費掉一點點的空間。
---
* 這時候就該用**Linked List**了,它的概念是一次只給一格的空間,然後利用**指標**把每個空間都串起來。
```graphviz
digraph list
{
node[shape="box"]
data1->data2->data3
{rank=same;data1;data2;data3}
}
```
* 看起來就像是火車的車廂一樣,一節拉著一節。
* 實作方式就是每組節點(node)裡面放你的資料和一個指標,用於指向下一個node的位置。
## 實作Linked List
* 實作Linked List的時候最好旁邊放紙筆一邊畫圖一邊做,否則容易搞混或忘記重設指標。
* 在這邊,我們加入資料和刪除資料的方式採用[Queues](/a56ukb7bS_SaekF3j2KlGA)的先進先出。
* 定義node:
```C++
struct node
{
int data;
node *next;
}
```
* 定義queue:
```C++
struct queue
{
node *front; //開頭,刪除的地方
node *rear; //結尾,加入的地方
int size; //裡面有幾筆資料
}
```
* 宣告並初始化queue:
```C++
queue q;
q.front = q.rear = NULL; //一開始沒有資料,都先設為NULL指標。
q.size = 0;
```
* 加入:
```C++
void Push(queue &q, int v)
{
node *n = new node;
n->data = v;
n->next = NULL;
if(q.front == NULL) //queue是空的
{
q.front = q.rear = n;
}
else
{
q.rear->next = n; //先將尾巴接上去
q.rear = n; //再將queue的尾巴移到正確位置
}
q.size++;
}
```
* 刪除:
```C++
int Pop(queue &q)
{
if(q.front == NULL)
return -1; //沒有元素在這個queue
int v = q.front->data;
q.front = q.front->next; //將開頭移至下一個節點
return v;
}
```
* 輸出:
```C++
void Display(queue &q)
{
for(node *n = q.front; n != NULL; n = n->next)
cout << n->data << endl;
}
```
* 如此一來,我們就完成了queue by Linked List。
### 完整的Linked List
* 上面的code由於是queue的型態,你會發現我們只能於開頭或結尾操作元素,但實際上我們是可以做到以下幾種事情的:
* 給予數字`v`,在linked list中搜尋後(回傳是第幾個位置/刪除它)。
* 給予數字`v`和位置`index`,將`v`插入到`index`的位置。(前提是已經有`index`以上的資料量了)。
* 因為懶得重打,就延續剛剛queue的結構。
* 回傳:
* 就一個一個找。
```C++
int Search(queue &q, int v)
{
int count = 0;
for(node *n = q.front; n != NULL; n = n->next)
{
if(n->data == v)
return count;
count++;
}
return -1;
}
```
* 剩下的code我懶ㄉ打了。
* 刪除:
* 找到目標節點後,將原本指向它的改為指向next node。
* 需特別注意如刪除開頭和結尾要更改queue的front和rear。
```graphviz
digraph list {
node[shape=record]
subgraph cluster_level1
{
label="刪除前"
node1 [label=" <f0>Data1|<f1>●"];
node2 [label=" <f0>Data2|<f1>●"];
node3 [label=" <f0>Data3|<f1>●"];
}
node1:f1->node2:f1->node3:f1
subgraph cluster_level2
{
label="刪除後"
node1b [label=" <f0>Data1|<f1>●"];
node2b [label=" <f0>Data2|<f1>●"];
node3b [label=" <f0>Data3|<f1>●"];
}
node1b:f1->node2b:f1->node3b:f1[color="White"]
node1b:f1->node3b:f1
}
```
* 插入也是差不多的概念:
* 將上一個節點的指標指向自己,然後將自己指向原本的下一個。
* 同樣須注意開頭和結尾。
```graphviz
digraph list {
node[shape=record]
subgraph cluster_level1
{
label="插入前"
node1 [label=" <f0>Data1|<f1>●"];
node2 [label=" <f0>Data2|<f1>●"];
node3 [label=" <f0>Data3|<f1>●"];
node4 [label=" <f0>Data2.5|<f1>●"];
}
node1:f1->node2:f1->node3:f1
subgraph cluster_level2
{
label="插入後"
node1b [label=" <f0>Data1|<f1>●"];
node2b [label=" <f0>Data2|<f1>●"];
node3b [label=" <f0>Data3|<f1>●"];
node4b [label=" <f0>Data2.5|<f1>●"];
}
node1b:f1->node2b:f1->node4b:f1->node3b:f1;
}
```
## 其他資源
* 我當初大一是自己看這個學的:
* [C 語言:鏈結串列(Linked List)的建立與刪除](https://kopu.chat/2017/06/02/c-%E8%AA%9E%E8%A8%80%EF%BC%9A%E9%8F%88%E7%B5%90%E4%B8%B2%E5%88%97linked-list%E7%9A%84%E5%BB%BA%E7%AB%8B%E8%88%87%E5%88%AA%E9%99%A4/)
* [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list) [name=宅色夫 jserv]
###### tags: `DS` `note`