570 likes | 740 Views
Data Structure in C ─ 樹狀結構. 大綱. 樹狀結構 ─ 專有名詞 樹狀結構 ─ 表示法 二元樹 二元搜尋樹 引線二元樹 堆積. 樹狀結構 ─專有名詞. 專有名詞 節點( node) 與邊( edge) 祖先( ancestor) 節點與子孫( descendant) 節點 父節點( parent node) 與子節點( children node) 兄弟節點( sibling node) 非終點節點( non-terminal node)
E N D
大綱 • 樹狀結構 ─ 專有名詞 • 樹狀結構 ─ 表示法 • 二元樹 • 二元搜尋樹 • 引線二元樹 • 堆積
樹狀結構 ─專有名詞 • 專有名詞 • 節點(node)與邊(edge) • 祖先(ancestor)節點與子孫(descendant)節點 • 父節點(parent node)與子節點(children node) • 兄弟節點(sibling node) • 非終點節點(non-terminal node) • 終點節點(terminal node)或樹葉節點(leaf node) • 分支度(degree) • 階度(level) • 高度(path)
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)所組合而成的,若將樹根移去將形成樹林
樹狀結構 ─ 表示法 • 圖形表示法 (p. 3) • 串列表示法 • 將圖形化的樹狀結構寫成一個串列 • Ex. 將p.3的圖形轉化為(A(B(E(J), F(K, L)), C(G), D(H(M, N), I)) • 左子 - 右弟表示法 • 每一個節點需要兩個鏈結(或指標)欄位,左欄位連結子節點,右欄位連結兄弟節點 左子-右弟(left child-right sibling)表示法
樹狀結構 ─ 表示法 (續) • 每一個節點僅有一個最左(left most)子節點及一個近右(closest right)兄弟節點 • 子節點及兄弟節點在樹中的順序並不重要
樹狀結構 ─ 表示法 (續) • 以分支度為2的樹表示 • 將左子-右弟表示法的樹狀結構順時鐘方向旋轉45度即可
二元樹 • 二元樹(binary tree) • 二元樹是由節點所組成的有限集合,這個集合若不是空集合就是由樹根及分別是右子樹(right subtree)及左子樹(left subtree) • 特點(與其他一般樹的不同): • 二元樹的節點個數可以是零(一般樹一定要有一個節點) • 二元樹有排列順序的關係(一般樹則沒有) • 二元樹中每一節點的分支度至多為2 (一般樹無限制)
二元樹 (續) 滿枝二元樹 (fully binary tree) 左斜樹 (left skewed tree) 完整二元樹 (complete binary tree)
二元樹 (續) • 特性: • 一棵二元樹在第i階度的最多節點數為2i-1,i>=1 • 一棵階度(或深度)為k的二元樹,2k-1,k>=1 • 一棵二元樹,若n0表示所有的樹葉節點,n2表示所有分支度為2的節點,n0=n2+1 • 表示法 • 一維陣列表示法 • 優點:處理容易,若為滿枝二元樹,則相當節省空間 • 缺點:若為歪斜樹,則相當浪費空間;一般而言,較鏈結串列表示法浪費空間
鏈結表示法 • 優點:插入與刪除一個節點相當容易 • 缺點:很難找到該節點的父節點(parent) • 解決方式:在節點的結構上增加一欄PARENT,指向其父節點的位置 typedef struct node *tree_pointer; typedef struct node { int data; tree_pointer left_child, right_child; };
二元樹 (續) • 二元樹追蹤(traversal) • 即走訪樹中的每一個節點,且每個節點恰好被尋訪一次 • 追蹤方式: • 中序追蹤(inorder):先拜訪左子樹(L:向左移動),然後拜訪節點(V:例如,列印資料),再拜訪右子樹(R:向右移動) • 前序追蹤(preorder):先拜訪節點(V),然後拜訪左子樹(L) ,再拜訪右子樹(R) • 後序追蹤(postorder):先拜訪左子樹(L),然後拜訪右子樹(R) ,再拜訪節點(V)
void inorder(Node type *tree) { if (tree != NULL){ inorder(tree->llink); printf(“%d”, tree->data); inorder(tree->rlink); } 中序追蹤:A/B%C*D+E void preorder(Node type *tree) { if (tree != NULL){ printf(“%d”, tree->data); preorder(tree->llink); preorder(tree->rlink); } void postorder(Node type *tree) { if (tree != NULL){ postorder(tree->llink); postorder(tree->rlink); printf(“%d”, tree->data); } 前序追蹤:+*/A%BCDE 後序追蹤:ABC%/D*E+
將一般的樹轉化為二元樹的方法 • 一般採用左子右弟的表示法,將樹化為二元樹,其步驟如下: • 將節點的所有兄弟節點連接在一起 • 把所有不是連接到最左子點的子節點鏈結刪除 • 順時針旋轉45度
將樹林轉化為二元樹的方法 • 步驟: • 先將樹林中的每棵樹化為二元樹(不旋轉45度) • 把所有二元樹利用樹根節點全部鏈結在一起 • 順時針旋轉45度
二元樹 (續) • 算術式的二元樹表示法 • 轉換規則: • 考慮運算子的優先權(priority)與結合性(associativity)適當的予以編號 • 由編號大至小依序處理,最大的為樹根,然後分成兩邊,再依次找各邊最大的為子樹樹根,直到所有的運算子皆處理完畢
二元樹 (續) • 唯一二元樹的決定 • 給予一對inorder及preorder可以構建出一個唯一的二元樹 • 給予一對inorder及postorder可以構建出一個唯一的二元樹 • 給予一對preorder及postorder則不能構建出一個唯一的二元樹 • 參考pp. 6-70~6-73
二元搜尋樹 • 二元搜尋樹(binary search tree) • 定義:二元搜尋樹為一二元樹,可以是空集合,假使不是空集合,則樹中的每一節點均含有一鍵值(key value),而且具有下列特性: • 在左子樹的所有節點之鍵值均小於樹根的鍵值 • 在右子樹的所有節點之鍵值均大於樹根的鍵值 • 左子樹和右子樹亦是二元搜尋樹 • 每個鍵值都不一樣 • 當依序給定輸入值(input sequence),我們可以建造一個二元搜尋樹,若使用中序追蹤此樹,可得到由小到大的排序結果
二元搜尋樹 (續) • 二元搜尋樹的加入 • 當某節點欲加入時,只要逐一比對,依據鍵值的大小往右或往左,即可找到適當位置 加入48 加入90
/* 處理二元搜尋樹,將新增資料加入至二元搜尋樹中 */ 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; }
二元搜尋樹 (續) • 二元搜尋樹的刪除 • 若刪除的是樹葉節點,則直接刪除之 • 若刪除的不是樹葉節點,則在左子樹找一最大的節點或在右子樹找一最小的節點,取代將被刪除的節點
二元搜尋樹 (續) 取右子樹最小的節點 取左子樹最大的節點
/* 將資料從二元搜尋樹中移除 */ 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中,以後再相反輸出
引線二元樹 • 引線二元樹(threaded binary tree) • 一般二元樹中有一半以上的link field是null link,為便利儲存及節省link欄位的浪費,將空的link換成一種叫引線(thread)的指標 • 引線二元樹的資料結構 1. 當LBIT=1時,LLINK是正常指標 2. 當LBIT=0時,LLINK是引線 3. 當RBIT=1時,RLINK是正常指標 4. 當RBIT=0時,RLINK是引線
開頭節點 (不放置任何資料)
引線二元樹 (續) • 特性: • 引線樹在做中序追蹤時不必使用stack,對任何一個節點平均只執行一次 • 在引線樹中毋需追蹤整個樹,可以由任何節點找到它中序次序的前一個或後一個節點 • 對於加入或刪除一個節點則引線樹較慢,這是因為牽涉到引線的重排
引線二元樹 (續) • 引線二元樹的加入
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; } } 加入新節點於某節點的左方
… 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(由小至大)輔助之
Heap的調整 • 由上而下 • 從樹根開始到 ,分別與其子節點相比,若前者大則不用交換。反之,則要交換 • 讓子節點先比,找出最大者再與父節點比
堆積 (續) • Heap的加入
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; }