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