Introduction C Language

Download Report

Transcript Introduction C Language

Data Structure in C
─ 樹狀結構
大綱






樹狀結構 ─ 專有名詞
樹狀結構 ─ 表示法
二元樹
二元搜尋樹
引線二元樹
堆積
1
樹狀結構 ─專有名詞

專有名詞









節點(node)與邊(edge)
祖先(ancestor)節點與子孫(descendant)節點
父節點(parent node)與子節點(children node)
兄弟節點(sibling node)
非終點節點(non-terminal node)
終點節點(terminal node)或樹葉節點(leaf node)
分支度(degree)
階度(level)
高度(path)
2
階度
1
A
B
E
J
C
F
K
D
G
L
H
M
2
I
N
3
4
A是K的祖先節點,K是A的子孫節點
A是B, C, D的父節點,B, C, D是A的子節點
B, C, D為兄弟節點
J, K, L, G, M, N, I是終點節點,其餘的節點就是非終點節點
A的分支度為3
對B而言,高度為2,深度為1
樹狀結構 ─專有名詞 (續)

樹與林(Forest)的關係

林是由n >= 0個不同的互斥樹(disjoint trees)所
組合而成的,若將樹根移去將形成樹林
A
B
C
D
H
E
I
J
K
F
G
L M
N O
4
樹狀結構 ─ 表示法


圖形表示法 (p. 3)
串列表示法


將圖形化的樹狀結構寫成一個串列
Ex. 將p.3的圖形轉化為(A(B(E(J), F(K, L)),
C(G), D(H(M, N), I))
data link 1 link 2

...
左子 - 右弟表示法

link n
data
left child
right sibling
每一個節點需要兩個鏈結(或指標)欄位,左
欄位連結子節點,右欄位連結兄弟節點 
左子-右弟(left child-right sibling)表示法
5
樹狀結構 ─ 表示法 (續)

每一個節點僅有一個最左(left most)子節點及
一個近右(closest right)兄弟節點

子節點及兄弟節點在樹中的順序並不重要
A
B
E
J
C
F
K
G
L
D
H
M
I
N
6
樹狀結構 ─ 表示法 (續)

以分支度為2的樹表示

將左子-右弟表示法的樹狀結構順時鐘方向
旋轉45度即可
A
B
E
J
C
F
D
G
H
K
L
M
I
N
7
二元樹

二元樹(binary tree)


二元樹是由節點所組成的有限集合,這個集合
若不是空集合就是由樹根及分別是右子樹
(right subtree)及左子樹(left subtree)
特點(與其他一般樹的不同):



二元樹的節點個數可以是零(一般樹一定要有一個
節點)
二元樹有排列順序的關係(一般樹則沒有)
二元樹中每一節點的分支度至多為2 (一般樹無限制)
8
二元樹 (續)
A
B
C
D
A
A
B
D
H
左斜樹
(left skewed tree)
E
I
B
C
J
F
G
K
完整二元樹
(complete binary tree)
C
D
H
E
I
J
K
F
G
L M
N O
滿枝二元樹
(fully binary tree)
9
二元樹 (續)

特性:




一棵二元樹在第i階度的最多節點數為2i-1,i>=1
一棵階度(或深度)為k的二元樹,2k-1,k>=1
一棵二元樹,若n0表示所有的樹葉節點,n2表示所
有分支度為2的節點,n0=n2+1
表示法

一維陣列表示法


優點:處理容易,若為滿枝二元樹,則相當節省空間
缺點:若為歪斜樹,則相當浪費空間;一般而言,較鏈結
串列表示法浪費空間
10
二元樹 (續)
(1)
(2)
A
B
1st
level
(3)
(4)
(5)
(6)
(7)
C
2nd
level
(8)
(9)
D
3rd
level
4th
level
(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)
(9)
A
B
C
D
E
F
G
H
I
1st
level
2nd
level
J
(3)
(4)
(5)
(6)
(7)
(8)
(9)
A
B
C
D
E
F
G
H
I
3rd
level
K
4th
level
(2)
2nd
level
(10) (11) (12) (13) (14) (15)
3rd
level
(1)
1st
level
(10) (11) (12) (13) (14) (15)
(10) (11) (12) (13) (14) (15)
J
K
L
M
N
O
4th
level
11

鏈結表示法



優點:插入與刪除一個節點相當容易
缺點:很難找到該節點的父節點(parent)
解決方式:在節點的結構上增加一欄PARENT,指向其父節點
的位置
typedef struct node *tree_pointer;
typedef struct node {
int data;
tree_pointer left_child, right_child;
};
A
B
D
H
C
E
I
F
J
G
K
二元樹 (續)

二元樹追蹤(traversal)


即走訪樹中的每一個節點,且每個節點恰好被
尋訪一次
追蹤方式:



中序追蹤(inorder):先拜訪左子樹(L:向左移動),然
後拜訪節點(V:例如,列印資料),再拜訪右子樹(R:
向右移動)
前序追蹤(preorder):先拜訪節點(V) ,然後拜訪左子
樹(L) ,再拜訪右子樹(R)
後序追蹤(postorder):先拜訪左子樹(L) ,然後拜訪右
子樹(R) ,再拜訪節點(V)
13
+
*
E
D
/
A
%
B
void inorder(Node type *tree)
{
if (tree != NULL){
inorder(tree->llink);
printf(“%d”, tree->data);
inorder(tree->rlink);
} 中序追蹤:A/B%C*D+E
C
void postorder(Node type *tree)
{
if (tree != NULL){
postorder(tree->llink);
postorder(tree->rlink);
printf(“%d”, tree->data);
} 後序追蹤:ABC%/D*E+
void preorder(Node type *tree)
{
if (tree != NULL){
printf(“%d”, tree->data);
preorder(tree->llink);
preorder(tree->rlink);
} 前序追蹤:+*/A%BCDE
A
A
B
B
E
C
F
A
G
E
C
D
F
D
H
B
G
H
F
I
I
J
D
C
E
G
H I

將一般的樹轉化為二元樹的方法
一般採用左子右弟的表示法,將樹化為二元樹,
其步驟如下:




將節點的所有兄弟節點連接在一起
把所有不是連接到最左子點的子節點鏈結刪除
順時針旋轉45度
A
A
B
E
B
C
F
G
D
H
E
I
J
C
F
H
K
J
K
L
M
D
G
N
L
M
I
N
A

將樹林轉化為二元樹的方法

B
E
C
D
F
H
G
I
步驟:



先將樹林中的每棵樹化為二元
樹(不旋轉45度)
把所有二元樹利用樹根節點全
部鏈結在一起
順時針旋轉45度
A
B
E
C
D
F
A
B
H
G
I
E
C
D
F
H
G
I
A
B
E
C
H
F
D
G
I
二元樹 (續)

算術式的二元樹表示法

轉換規則:


考慮運算子的優先權(priority)與結合性
(associativity)適當的予以編號
由編號大至小依序處理,最大的為樹根,然後分
成兩邊,再依次找各邊最大的為子樹樹根,直到
所有的運算子皆處理完畢
18
(一)依運算順序在各運算子下編號
A+B*C- D*E
(二)由編號大的依序處理
(a)
(b)
-
+
(c)
-
(d)
+
*
D
+
E
A
*
*
B
D
C
E
二元樹 (續)

唯一二元樹的決定




給予一對inorder及preorder可以構建出一個
唯一的二元樹
給予一對inorder及postorder可以構建出一個
唯一的二元樹
給予一對preorder及postorder則不能構建出一
個唯一的二元樹
參考pp. 6-70~6-73
20
二元搜尋樹

二元搜尋樹(binary search tree)

定義:二元搜尋樹為一二元樹,可以是空集合,
假使不是空集合,則樹中的每一節點均含有一
鍵值(key value),而且具有下列特性:





在左子樹的所有節點之鍵值均小於樹根的鍵值
在右子樹的所有節點之鍵值均大於樹根的鍵值
左子樹和右子樹亦是二元搜尋樹
每個鍵值都不一樣
當依序給定輸入值(input sequence),我們可以建
造一個二元搜尋樹,若使用中序追蹤此樹,可
得到由小到大的排序結果
21
二元搜尋樹 (續)

二元搜尋樹的加入

當某節點欲加入時,只要逐一比對,依據鍵值
的大小往右或往左,即可找到適當位置
50
加入48
40
30
65
45
50
60
加入90
40
30
65
45
60
48
50
40
30
65
45
90
60
48
22
/* 處理二元搜尋樹,將新增資料加入至二元搜尋樹中 */
void access(char name[], int score)
{
struct student *node, *prev;
if(search(name) != NULL) /* 資料已存在則顯示錯誤 */
{ printf("Student %s has existed!\n", name);
return;
}
ptr = (struct student *) malloc(sizeof(struct student));
strcpy(ptr->name, name);
ptr->score = score;
ptr->llink = ptr->rlink = NULL;
if(root == NULL) /* 當根節點為NULL的狀況 */
root = ptr;
else /* 當根節點不為NULL的狀況 */
{
node = root;
while(node != NULL) /* 搜尋資料插入點 */
{
prev = node;
if(strcmp(ptr->name, node->name) < 0)
node = node->llink;
else
node = node->rlink;
}
if(strcmp(ptr->name, prev->name) < 0)
prev->llink = ptr;
else
prev->rlink = ptr;
}
}
/* 搜尋target所在節點 */
struct student *search(char target[])
{
struct student *node;
node = root;
while(node != NULL)
{
if(strcmp(target, node->name) == 0)
return node;
else
/* target小於目前節點,往左搜尋 */
if(strcmp(target, node->name) < 0)
node = node->llink;
else /* target大於目前節點,往右搜尋 */
node = node->rlink;
}
return node;
}
二元搜尋樹 (續)

二元搜尋樹的刪除


若刪除的是樹葉節點,則直接刪除之
若刪除的不是樹葉節點,則在左子樹找一最
大的節點或在右子樹找一最小的節點,取代
將被刪除的節點
26
二元搜尋樹 (續)
60
40
30
50
40
30
45
65
45
65
取右子樹最小的節點
45
60
40
30
65
60
取左子樹最大的節點
27
/* 將資料從二元搜尋樹中移除 */
void removing(char name[])
{ struct student *del_node;
if((del_node = search(name)) == NULL) /* 找不到資料則顯示錯誤 */
{
printf("Student %s not found!\n", name);
return; }
/* 節點不為樹葉節點的狀況 */
if(del_node->llink != NULL || del_node->rlink != NULL)
del_node = replace(del_node);
else /* 節點為樹葉節點的狀況 */
if(del_node == root)
root = NULL;
else
connect(del_node, 'n');
free(del_node); /* 釋放記憶體 */
printf("Data of student %s deleted!\n", name);
}
/* 尋找刪除非樹葉節點的替代節點 */
struct student *replace(struct student *node)
{ struct student *re_node;
/* 當右子樹找不到替代節點,會搜尋左子樹是否存在替代節點 */
if((re_node = search_re_r(node->rlink)) == NULL)
re_node = search_re_l(node->llink);
if(re_node->rlink != NULL) /* 當替代節點有右子樹存在的狀況 */
connect(re_node, 'r');
else
if(re_node->llink != NULL) /* 當替代節點有左子樹存在的狀況 */
connect(re_node, 'l');
else /* 當替代節點為樹葉節點的狀況 */
connect(re_node, 'n');
strcpy(node->name, re_node->name);
node->score = re_node->score;
return re_node;
}
/* 搜尋右子樹替代節點 */
struct student *search_re_r(struct student *node)
{ struct student *re_node;
re_node = node;
while(re_node != NULL && re_node->llink != NULL)
re_node = re_node->llink;
return re_node;
}
/* 搜尋左子樹替代節點 */
struct student *search_re_l(struct student *node)
{ struct student *re_node;
re_node = node;
while(re_node != NULL && re_node->rlink != NULL)
re_node = re_node->rlink;
return re_node;
}
/* 調整二元搜尋樹的鏈結,link為r表示處理右鏈結,為l表處理左鏈結,
為m則將鏈結指向NULL */
void connect(struct student *node, char link)
{ struct student *parent;
parent = search_p(node); /* 搜尋父節點 */
/* 節點為父節點左子樹的狀況 */
if(strcmp(node->name, parent->name) < 0)
if(link == 'r') /* link為r */
parent->llink = node->rlink;
else
if(link == 'l') /* link為l */
parent->llink = node->llink;
else /* link為m */
parent->llink = NULL;
else /* 節點為父節點右子樹的狀況 */
if(link == 'r') /* link為r */
parent->rlink = node->rlink;
else
if(link == 'l') /* link為l */
parent->rlink = node->llink;
else /* link為m */
parent->rlink = NULL;
}
/* 搜尋node的父節點 */
struct student *search_p(struct student *node)
{ struct student *parent;
parent = root;
while(parent != NULL)
{
if(strcmp(node->name, parent->name) < 0)
if(strcmp(node->name, parent->llink->name) == 0)
return parent;
else
parent = parent->llink;
else
if(strcmp(node->name, parent->rlink->name) == 0)
return parent;
else
parent = parent->rlink;
}
return NULL;
}
二元搜尋樹 (續)

利用二元搜尋樹來排序一組資料

步驟:





先將輸入資料置於一個queue中
以第一個資料當做二元樹的樹根
以後的資料與樹根比較,若小於則成為樹根之左子
樹,若大於則為其右子樹,如此一直遞迴式的進行
建立完畢後再以中序法追蹤
若要由小而大排序,則將結果直接輸出。若是由大
而小,則先將結果置於stack中,以後再相反輸出
34
引線二元樹

引線二元樹(threaded binary tree)


一般二元樹中有一半以上的link field是null link,
為便利儲存及節省link欄位的浪費,將空的link
換成一種叫引線(thread)的指標
引線二元樹的資料結構
LBIT
LLINK
DATA
RLINK
RBIT
1. 當LBIT=1時,LLINK是正常指標
2. 當LBIT=0時,LLINK是引線
3. 當RBIT=1時,RLINK是正常指標
4. 當RBIT=0時,RLINK是引線
35
開頭節點
(不放置任何資料)
LBIT
LLINK
DATA
1
1
0
H
D
0
B
1
0
I
A
0
E
1
1
1
0
RBIT
─
1
1
RLINK
1
0
0
1
J
C
F
0
1
1
0
0
K
G
0
0
引線二元樹 (續)

特性:



引線樹在做中序追蹤時不必使用stack,對任
何一個節點平均只執行一次
在引線樹中毋需追蹤整個樹,可以由任何節
點找到它中序次序的前一個或後一個節點
對於加入或刪除一個節點則引線樹較慢,這
是因為牽涉到引線的重排
37
引線二元樹 (續)

引線二元樹的加入
1
1
1
2
2
S
4
S
2
3
3
3
4
S
T
T
5
5
4
加T節點於S節點之右方
加T節點於S節點之左方
38
void insert_right( struct tbintree *node_parent,struct tbintree *node)
{ struct tbintree *w;
node->rchild = node_parent->rchild;
node->rbit = node_parent->rbit;
node->lchild = node_parent;
node->lbit = 0;
node_parent->rchild = node;
node_parent->rbit = 1;
if ( node->rbit == 1 ) /*node底下還有tree*/
{
w = insucc( node );
w->lchild = node;
}
加入新節點於某節點的右方
}
void insert_left( struct tbintree *node_parent,struct tbintree *node)
{ struct tbintree *w;
node->lchild = node_parent->lchild;
node->lbit = node_parent->lbit;
node->rchild = node_parent;
node->rbit = 0;
node_parent->lchild = node;
node_parent->lbit = 1;
if ( node->lbit == 1 ) /*node 底下還有tree*/
{
w = inpred( node );
w->rchild = node;
}
加入新節點於某節點的左方
}
引線二元樹的刪除

1
2
1
prev
prev->lchild = this->lchild;
prev->lbit = 0;
3
this
2
3
4
1
2
1
prev
3
this
4
prev->rchild = this->rchild;
prev->rbit = 0;
2
3
…
if ( ptr->lbit == 0 && ptr->rbit == 0 )
{
if ( ptr_parent == root ) /*刪除第一個節點*/
{
ptr_parent->lchild = root;
ptr_parent->lbit = 0;
} /*刪除左節點*/
else if ( ptr->number < ptr_parent->number )
{
ptr_parent->lchild = ptr->lchild;
ptr_parent->lbit = 0;
}
else
/*刪除右節點*/
{
ptr_parent->rchild = ptr->rchild;
ptr_parent->rbit = 0;
}
free(ptr);
刪除節點於樹葉節點的左右方
}
else if ( ptr->lbit == 1 && ptr->rbit == 1 )
{
/*求ptr的前行者節點,將右子樹插入前行者右方*/
ptr_pred = inpred ( ptr);
ptr_pred->rchild = ptr->rchild;
ptr_pred->rbit = ptr->rbit;
ptr_parent->lchild = ptr->lchild;
free(ptr);
}
else /*刪除一分支度節點*/
{
if ( ptr_parent == root )
/*刪除第一節點*/
{
if ( ptr->lbit == 1 )
{
ptr_pred = inpred(ptr);
root->lchild = ptr->lchild;
ptr_pred->rchild = root; }
else
{
ptr_succ = insucc(ptr);
root->lchild = ptr->rchild;
ptr_succ->lchild = root; }
}
else
{
if ( ptr->number < ptr_parent->number )
ptr_parent->lchild = ptr->lchild;
else
ptr_parent->rchild = ptr->rchild;
}
}
刪除節點於非樹葉節點的左右方
堆積

堆積(heap)



定義:堆積是一棵二元樹,其樹根的鍵值大
於子樹的鍵值,且必須符合完整二元樹
不管左子樹和右子樹的大小順序(與二元搜
尋樹最大的差異)
Heap可用於排序上,簡稱Heap Sort


在一堆雜亂無章的資料中,利用heap sort將它由
小至大或由大至小排序皆可
首先,將一堆資料利用完整二元樹將其建立起來,
再將它調整為Heap,爾後再依題意用Stack(由大
到小)或Queue(由小至大)輔助之
45

Heap的調整

由上而下

從樹根開始到 n / 2,分別與其子節點相比,若前者大則
不用交換。反之,則要交換
1
15
1
23
2 23 30 3
2 15 30 3
換

換
1
30
2 15 23 3
讓子節點先比,找出最大者再與父節點比
1
15
1
30
2 23 30 3
2 23 15 3
堆積 (續)
Heap的加入

40
23
10
15
8
23
30
15
8
15
30
30
8
30
10
40
23
15
50
23
30
8
再加入
50
10
40
23
8
23
10
40
15
40
40
10
50
15
50
8
10
30
50
23
15
40
8
10
30
47
void insert_f(void)
{ int id_temp;
if(last_index >= MAX) /* 資料數超過上限,顯示錯誤訊息 */
{
printf("\n Login members are more than %d!!\n", MAX);
printf(" Please wait for a minute!!\n");
}
else
{
printf("\n Please enter login ID number: ");
scanf("%d", &id_temp);
create(id_temp); /* 建立堆積 */
printf(" Login successfully!!\n");
}
}
void create(int id_temp) /* ID_TEMP為新增資料 */
{ heap_tree[++last_index] = id_temp; /* 將資料新增於最後 */
adjust_u(heap_tree, last_index); /* 調整新增資料 */
}
void adjust_u(int temp[], int index) /* INDEX為目前資料在陣列之INDEX */
{ while(index > 1) /* 將資料往上調整至根為止 */
{
if(temp[index] <= temp[index/2]) /* 資料調整完畢就跳出,否則交
換資料 */
break;
else
exchange(&temp[index], &temp[index/2]);
index /= 2;
}
}
void exchange(int *id1, int *id2) /* 交換傳來之ID1及ID2儲存之資料 */
{ int id_temp;
id_temp = *id1;
*id1 = *id2;
*id2 = id_temp;
}
堆積 (續)

Heap的刪除
40
40
40
刪除30
30
15
20
15
10
10
10
調整
20
15
換
20
刪除40
15
20
10
20
15
10
50
void delete_f(void)
{ int id_temp, del_index;
if(last_index < 1) /* 無資料存在,顯示錯誤訊息 */
{
printf("\n No member to logout!!\n");
printf(" Please check again!!\n");
}
else
{
printf("\n Please enter logout ID number: ");
scanf("%d", &id_temp);
del_index = search(id_temp); /* 尋找欲刪除資料 */
if(del_index == 0) /* 沒找到資料,顯示錯誤訊息 */
printf(" ID number not found!!\n");
else
{
removes(del_index); /* 刪除資料,並調整堆積樹 */
printf(" ID number %d logout!!\n", id_temp);
}
}
}
int search(int id_temp) /* 尋找陣列中ID_TEMP所在 */
{ int c_index;
for(c_index = 1; c_index <= MAX; c_index++)
if(id_temp == heap_tree[c_index])
return c_index; /* 找到則回傳資料在陣列中之INDEX */
return 0; /* 沒找到則回傳0 */
}
void removes(int index_temp) /* INDEX_TEMP為欲刪除資料之INDEX */
{ /* 以最後一筆資料代替刪除資料 */
heap_tree[index_temp] = heap_tree[last_index];
heap_tree[last_index--] = 0;
if(last_index > 1) /* 當資料筆數大於1筆,則做調整 */
{
/* 當替代資料大於其PARENT NODE,則往上調整 */
if(heap_tree[index_temp] > heap_tree[index_temp / 2] && index_temp > 1)
adjust_u(heap_tree, index_temp);
else /* 替代資料小於其CHILDEN NODE,則往下調整 */
adjust_d(heap_tree, index_temp, last_index-1);
}
}
void adjust_d(int temp[], int index1, int index2)
{/* ID_TEMP記錄目前資料,INDEX_TEMP則是目前資料之CHILDEN NODE的INDEX */
int id_temp, index_temp;
id_temp = temp[index1];
index_temp = index1 * 2;
/* 當比較資料之INDEX不大於最後一筆資料之INDEX,則繼續比較 */
while(index_temp <= index2)
{if((index_temp < index2) && (temp[index_temp] < temp[index_temp+1]))
index_temp++; /* INDEX_TEMP記錄目前資料之CHILDEN NODE中較大者 */
if(id_temp >= temp[index_temp]) /* 比較完畢則跳出,否則交換資料 */
break;
else
{
temp[index_temp/2] = temp[index_temp];
index_temp *= 2;
}
}
temp[index_temp/2] = id_temp;
}
堆積 (續)

何謂min-heap?


max-heap是父節點的鍵值一律大於其子節點
之值
min-heap則是父節點的鍵值一律小於其子節
點之值
10
21
32
28
29
54