利用鏈結串列(linked list)

Download Report

Transcript 利用鏈結串列(linked list)

Data Structure in C
─ 鏈結串列
大綱




單向鏈結串列
環狀串列
雙向鏈結串列
鏈結串列的應用
1
鏈結串列

以陣列方式存放資料,若要插入(insert)或刪
除(delete)某一節點(node)就備感困難


Ex. 陣列中已有a, b, d, e四個元素,若要將c插入
d, e需往後一格
Ex.陣列中已有a, b, d, e四個元素,若要將d刪除
為不浪費空間需挪移元素
利用鏈結串列(linked list)
解決問題
2
鏈結串列 (續)

陣列 vs. 鏈結陣列
陣列
空間
需預先宣告
資料取得
索引
資料加入與刪除 較困難
資料搜尋
有效率
鏈結串列
需要時再宣告即可
指標
較容易
無效率
3
單向鏈結串列

單向鏈結串列 (single linked list)

若單向串列中的每個節點(node)的資料結構有兩
個欄位,分別為資料欄(data)及鏈結欄(next),
data
next ,若將節點定義為struct型態,
為
struct node{
int data;
struct node *next;
};
struct node *head=NULL, *tail, *this, *prev, *x, *p, *ptr;
4
單向鏈結串列 (續)

Ex. 串列A={a, b, c, d, e},以鏈結串列表示為
tail
head
A
指向串列前
端的指標
B
C
D
E
指向串列尾
端的指標
5
單向鏈結串列 (續)

單向鏈結串列的加入動作



加入於串列的前端
加入於串列的尾端
加入在串列某一特定節點後面
tail
head
●
●
●
6
加入於串列的前端
Step 1: x = (struct node *) malloc(sizeof(struct node));
x->next = NULL;
x
●
Step 2: x->next = head;
tail
head
●
x
●
●
●
Step 3: head = x;
x
head
tail
●
●
●
●
加入於串列的尾端
Step 1: x = (struct node *) malloc(sizeof(struct node));
x->next = NULL;
x
●
Step 2: tail->next = x;
tail
head
●
●
●
x
●
Step 3: tail = x;
head
x
●
tail
●
●
若未用tail指標,則每次
都必須要追蹤串列的尾端
●
加入在串列某一特定節點後面
Assumption: 將一個節點x加在ptr所指節點後面
ptr
head
●
tail
●
●
Step 1: x = (struct node *) malloc(sizeof(struct node));
x->next = NULL;
Step 2: x->next = ptr->next;
ptr
head
●
tail
●
●
x
●
Step 3: ptr->next = x;
ptr
head
●
tail
●
●
x
●
void insert_node(struct node *ptr, struct node *head, struct node *tail)
{ struct node *this
if(head == NULL)
/* 插入資料為第一筆 */
{
ptr->next=NULL;
head=ptr;
tail=ptr;}
else
{ this=head;
if(ptr->key > this->key) /* 插入位置為前端 */
{
ptr->next=this;
head=ptr;
}
else
{while(this->next != NULL)
{
prev=this;
this=this->next;
if(ptr->key > this->key) /* 插入位置於中間 */
{
ptr->next=this;
prev->next=ptr;
break; }
}
if(ptr->key <= this->score) /* 插入資料於尾端 */
{
ptr->next=NULL;
this->next=ptr;
tail=ptr;}
}
}
}
單向鏈結串列 (續)

單向鏈結串列的刪除動作



刪除串列前端的節點
刪除串列尾端的節點
刪除串列某一特定節點
tail
head
●
●
●
14
刪除串列前端的節點
Step 1: ptr = head;
head
ptr
tail
●
●
●
Step 2: head = ptr->next;
ptr
head
●
tail
●
Step 3: free(ptr);
head
tail
●
●
●
刪除串列尾端的節點
ptr = head;
while (ptr->next != tail)
ptr = ptr->next;
Step 1:必須先追蹤tail的前一個節點
ptr
head
●
tail
●
●
Step 2: ptr->next = NULL;
ptr
head
●
tail
●
Step 4: tail = ptr;
Step 3: free(tail);
head
ptr
●
●
head
ptr
tail
●
●
●
刪除串列某一特定節點
Step 1:尋找所要刪除的節點,並設定兩指標this及prev
prev
head
●
this
●
tail
●
即將被刪除的
前一節點
●
即將被刪
除節點
while (this->next != NULL)
prev = this;
this = this->next;
Step 2: prev->next = this->next;
head
●
●
●
tail
this
prev
Step 3: free(this);
prev
head
●
tail
●
●
●
void delete_f(int del_key, struct node *head, struct node *tail)
{ struct node *clear, *this, *prev; this=head;
if(del_key == head->key) { /* 刪除資料於前端 */
clear=this;
if(head->next == NULL) /* 資料僅存在一筆 */
tail=NULL;
head=head->next;
free(clear);
}
while(this->next != NULL) { /* 刪除資料於中間 */
prev=this;
this=this->next;
if (del_key == this->key) {
clear=this;
prev->next=this->next;
free(clear);
}
}
if(del_key == tail->key) { /*刪除資料於尾端 */
clear=this;
prev->next=NULL;
tail=prev;
free(clear);
}
單向鏈結串列 (續)

將兩串列相接
x
y
●
●
●
●

●
●
三種情況



x串列為NULL,y串列有資料
x串列有資料,y串列為NULL
x串列與y串列都有資料
20
void concatenate (struct node *x, struct node *y, struct node *y)
{
struct node *c;
if (x = = NULL)
z = y;
else if (y = = NULL)
z = x;
else {
z=x;
c=x;
while(c->next != NULL)
c = c->next;
c->next = y;
}
}

將一串列反轉


串列的反轉是將原先的串列首變成串列尾
Ex. 若一串列原先以小排到大,此時若想由大到小排列
void invert(struct node *head)
{
struct node *this, *prev, *next_n;
next_n = head;
head
this =NULL;
while(next_n != NULL){
prev = this;
this = next_n;
next_n = next->next;
head
this->next = prev;
}
tail = head;
●
head = this;
tail
}
●
●
next_n
this
tail
●
●
prev
●
prev
●
●
tail
●
head
this
this
next_n
next_n
單向鏈結串列 (續)

計算串列的長度

串列長度指的是此串列共有多少個節點,計算上只要指
標指到的為NULL,則利用一變數做累加,直到指標指
到NULL為止
int length(struct node *head)
{
struct node *this;
this = head;
int leng=0;
while (this != NULL) {
leng++’
this = this->next;}
return leng;
}
23
環狀串列

假若將鏈結串列最後一個節點的指標指向第
一個節點時,此串列稱為環狀串列(circular
list)

head
可以由任一點來追蹤所有節點,而不必區分那
一個是第一個節點
A
B
C
D
E
tail
24
環狀串列 (續)

環狀串列的加入動作

加入於串列的前端




若串列為空串列(NULL)
若串列不為空串列
加入於串列的尾端
加入在串列某一特定節點後面
25
加入於串列的前端 ─ 串列為空串列
Step 1: head = x; tail=x;
Step 2: x->next = x;
tail
head
x
加入於串列的前端 ─ 串列不為空串列
Step 1: x->next = head;
head
x
tail
Step 2: tail->next = x;
head
tail
x
Step 3: head = x;
tail
head
x
加入於串列的尾端
Step 2: x->next = head;
Step 1: tail->next = x;
tail head
head
x
tail
x
Step 3: tail = x;
head
tail
void insert_node(struct node *ptr, struct node *head, struct node *tail)
{ struct node *prev, *this;
if(head == NULL) { /* 插入資料為第一筆 */
ptr->next = ptr;
head = ptr;
tail = ptr;
}
else {
this = head;
if(ptr->key < this->key) { /* 插入位置為前端 */
ptr->next = this;
head = ptr;
tail->next = head;
}
else { while(this->next != head)
{
prev = this;
this = this->next;
if(ptr->key < this->key) { /* 插入位置於中間 */
ptr->next = this;
prev->next = ptr;
break; }
}
if( ptr->key >= this->key) { /* 插入資料於尾端 */
ptr->next = head;
this->next = ptr;
tail = ptr;
}
}
}
}
環狀串列 (續)

環狀串列的刪除動作



刪除串列前端的節點
刪除串列尾端的節點
刪除串列某一特定節點
31
刪除串列前端的節點
Step 1: tail->next = head->next;
tail
head
Step 2: ptr=head; head = head->next;
ptr
tail
head
Step 3: free(ptr);
head
tail
刪除串列尾端的節點
Step 1: 必須先追蹤tail的前一個節點
ptr
ptr = head;
while (ptr->next != tail)
ptr = ptr->next;
tail
head
Step 2: ptr->next = hear;
tail
head
ptr
Step 3: free(tail); tail=ptr;
head
tail
void delete_node(int del_key, struct node *head, struct node *tail)
{ struct node *clear, *prev, *this;
this = head;
if(del_key == head->key) { /* 刪除資料於前端 */
clear = head;
if(head->next == head) /* 資料僅存在一筆 */
{head = NULL;
tail = NULL;}
else
{
head = head->next;
tail->next = head;
}
}
while(this->next != head && head != NULL) /* 刪除資料於中間 */
{
prev = this;
this = this->next;
if(del_key == ,this->key)
{
clear = this;
prev->next = this->next;
tail = prev;
}
}
if(del_key == tail->key) { /*刪除資料於尾端 */
clear = tail;
prev->next = head;
tail = prev;
}
free(clear);
}
將兩串列相接
Btail
Atail Bhead
Ahead
Step 1: Atail->next = Bhead;
Ahead
Atail
Bhead
Btail
Step 2: Btail->next = Ahead;
Ahead
Btail
雙向鏈結串列

雙向鏈結串列(doubly linked list)

每個節點有三個欄位,一為左鏈結(LLink),二
為資料(Data),三為右鏈結(RLink)
LLink

Data
RLink
特點:


假設ptr是任何節點的指標,則
ptr = ptr->llink->rlink = ptr->rlink->llink;
若此雙向鏈結串列是空串列,則只有一個串列首
37
雙向鏈結串列 (續)

優點:




加入或刪除時,無需知道其前一節點的位址
可以從任一節點找到其前一節點或後一節點
可以將某一節點遺失的左或右指標適時地加以恢復之
缺點:



增加一個指標空間
加入時需改變四個指標(單向只需改變兩個指標)
刪除時需改變兩個指標(單向只要改變一個指標)
38
雙向鏈結串列 (續)

雙向鏈結串列的加入動作
加入於串列的前端
加入於串列的尾端
加入在串列某一特定節點後面



head
假設head節點不
放任何資料
void init_head(struct node *ptr, struct
node *head, struct node *tail)
ptr = (struct node *) malloc
tail {
(sizeof(struct node));
ptr->key = NULL;
ptr->llink = ptr;
ptr->rlink = ptr;
head = ptr;
tail = ptr;}
39
加入於串列的前端
Step 1: x->rlink = head;
head
tail
x
Step 2: x->llink = tail;
tail
head
x
Step 3: tail->rlink = x;
tail
head
x
Step 4: head->rlink = x;
tail
x
head
Step 5: tail = x;
x
head
tail
加入於串列的尾端
Assumption: 假設有一串列如下
x
head
tail
Step 1: x->rlink = tail->rlink;
head
tail
x
Step 2: tail->rlink = x;
tail
head
x
Step 3: x->llink = tail;
tail
head
x
Step 4: head->llink = x;
tail
x
head
Step 5: tail = x;
x
head
tail
雙向鏈結串列 (續)

加入在串列某一特定節點後面

必須先搜尋到某特定的節點,並假設此串列是
以key由小而大建立的,而搜尋步驟如下:
prev = head;
ptr = head->rlink;
while(ptr != head && x->key <p->key) {
prev = ptr;
ptr = ptr->rlink; }
44
void insert_node(struct node *ptr, struct node *head, struct node *tail)
{ struct node *this;
this = head->rlink;
while(this != head)
{
if(ptr->key < this->key) { /* 插入位置為中間 */
ptr->rlink = this;
ptr->llink = this->llink;
this->llink->rlink = ptr;
this->llink = ptr;
break; }
this = this->rlink; }
/* 插入位置為尾端 */
if(head->rlink == head || this == head)
{
ptr->rlink = head;
ptr->llink = head->llink;
head->llink->rlink = ptr;
head->llink = ptr;
tail = ptr;}
}
雙向鏈結串列 (續)

雙向鏈結串列的刪除動作



head
刪除串列前端的節點
刪除串列尾端的節點
刪除串列某一特定節點
tail
46
刪除串列前端的節點
Step 1: ptr = head->rlink; Step 2: head->rlink = ptr->rlink;
head
tail
p
Step 3: p->rlink->llink = p->llink;
head
tail
p
Step 4: free(ptr);
head
tail
刪除串列尾端的節點
Step 1: tail->llink->rlink = head;
head
tail
Step 2: head->llink = tail->llink; Step 3: ptr = tail;
head
tail
ptr
Step 4: tail = tail->llink;
head
Step 5: free(ptr);
tail
刪除串列某一特定節點
Assumption: 假設欲刪除this所指的節點
tail
head
this
Step 1: this->llink->rlink = this->rlink;
Step 2: this->rlink->llink = this->llink;
Step 3: free(this);
void delete_node(int del_key, struct node *head, struct node *tail)
{ struct node *clear, *this;
this = head->rlink;
while(this != head) { /* 刪除資料於中間 */
if(del_key == this->key)
{
clear = this;
this->llink->rlink = this->rlink;
this->rlink->llink = this->llink;
if(this == tail)
tail = this->llink;
break; }
this = this->rlink;
}
free(clear);
}
鏈結串列的應用



以鏈結串列表示堆疊
以鏈結串列表示佇列
以鏈結串列表示多項式相加
51
鏈結串列的應用 (續)

以鏈結串列表示堆疊



將堆疊內的資料視為單向鏈結串列中的資料項
Push: 視為將節點加入串列的前端
Pop: 視為刪除前端的節點
top
52
鏈結串列的應用 (續)

以鏈結串列表示堆疊 ─ Push
void push_stack(int data, struct node *ptr, struct node *top)
{ptr = (struct node *) malloc(sizeof(struct node));
ptr
ptr->item = data;
top
ptr->next = top;
data
top
top = ptr;
}
53
鏈結串列的應用 (續)

以鏈結串列表示堆疊 ─ Pop
void pop_stack(int data, struct node *top)
{struct node *clear
top
if(top ==NULL)
printf(“stack-empty”);
top
clear = top;
data = top->item;
top = top->next;
free(clear);
}
clear
54
鏈結串列的應用 (續)

以鏈結串列表示佇列



將佇列內的資料視為單向鏈結串列中的資料項
Enqueue:視為將節點加入串列的尾端
Dequeue:視為刪除前端的節點
front
由此處開始刪除
rear
由此處開始加入
55
鏈結串列的應用 (續)

以鏈結串列表示佇列 ─ Enqueue
void enqueue(int data, struct node *front, struct node *rear)
{ ptr = (struct node *) malloc(sizeof(struct node));
ptr->item = data;
ptr->next = NULL;
if(rear == NULL)
front = rear = ptr;
else
rear->next = ptr;
rear = ptr;}
56
鏈結串列的應用 (續)

以鏈結串列表示佇列 ─ Dequeue
void dequeue(int data, struct node *front)
{struct node *clear
if(front ==NULL)
printf(“stack-empty”);
data = front->item;
clear = front;
front = front->next;
free(clear);
}
57
鏈結串列的應用 (續)

以鏈結串列表示多項式相加

多項式相加可以用鏈結串列完成,其資料結構
Exp
Link
為 Coef




Coef表示變數的係數
Exp表示變數的指數
Link為指下一節點的指標
原理(若有A、B兩多項式)



Exp(A) = Exp(B)
Exp(A) > Exp(B)
Exp(A) < Exp(B)
58
void poly_add(void)
{
struct poly *this_n1, *this_n2, *prev;
this_n1 = eq_h1;
this_n2 = eq_h2;
prev = NULL;
while(this_n1 != NULL || this_n2 != NULL) /* 當兩個多項式皆相加完畢則
結束 */
{
ptr = (struct poly *) malloc(sizeof(struct poly));
ptr->next = NULL;
/* 第一個多項式指數大於第二個多項式 */
if(this_n1 != NULL && (this_n2 == NULL || this_n1->exp > this_n2>exp))
{
ptr->coef = this_n1->coef;
ptr->exp = this_n1->exp;
this_n1 = this_n1->next;
}
else
/* 第一個多項式指數小於第二個多項式 */
if(this_n1 == NULL || this_n1->exp < this_n2->exp)
{
ptr->coef = this_n2->coef;
ptr->exp = this_n2->exp;
this_n2 = this_n2->next;
}
else /* 兩個多項式指數相等,進行相加 */
{
ptr->coef = this_n1->coef + this_n2->coef;
ptr->exp = this_n1->exp;
if(this_n1 != NULL) this_n1 = this_n1->next;
if(this_n2 != NULL) this_n2 = this_n2->next;
}
if(ptr->coef != 0) /* 當相加結果不等於0,則放入答案多項式
中 */
{
if(ans_h == NULL) ans_h = ptr;
else prev->next = ptr;
prev = ptr;
}
else free(ptr);
}
}