6329 views
# Binary Search Tree 二元搜尋樹 **參考:** http://squall.cs.ntou.edu.tw/cpp/103spring/labtest/test1/BinarySearchTree.html ## 介紹 ### (一)定義 為一種Binary Tree,若不為空則滿足 : 1. 左子樹所有Nodes之值均小於Root。 2. 柚子樹所有Nodes之值均大於Root。 3. 左右子樹亦是Binary Search Tree。 ```graphviz graph{ 10--5 10--15 5--3[weight=2] 5--9 3--2 3--4 15--12 15--19[weight=2] } ``` ### (二)利用B.S.T.進行有小到大之排序 **Step1:** 將input data建成B.S.T. **Step2:** 對B.S.T.進行**Inorder traversal**即得出小$\rightarrow$大排序 ## 相關函式及其程式碼 在Binary Search Tree的Struct已建構完畢之下,我們來介紹以下函式。 ### Search (搜尋資料) 此操作是藉由Binary Search Tree的特徵==Data(**L**)<Data(**V**)<Data(**R**)==,來判斷該向右走還是向左走。 ```graphviz digraph{ find[label="Find 10";style=dashed] a1[label=9] b1[label=5] c1[label=2] d1[label=7] e1[label=10] {rank=same;find,a1} find->a1[style=dashed] a1->b1 a1->c1 b1->d1 b1->e1 } ``` ```graphviz digraph{ a2[label=9;color=red;fontcolor=red] b2[label=5;color=red;fontcolor=red] c2[label=2] d2[label=7] e2[label=10;color=red;fontcolor=red] a2->b2[color=red;style=bold] a2->c2 b2->d2 b2->e2[color=red;style=bold] } ``` #### 程式碼 ```C= BST_Node* FindNode(BST_Node* root, int value){ while(root!=NULL){ if (root->data==value){ return root; /*若找到目標,則回傳此節點*/ } else if (root->data>value){ root = root->left; /*若目標比此節點之data小,則向左移動*/ } else{ root = root->right; /*若目標比此節點之data大,則向右移動*/ } } return NULL; /*找不到*/ ``` ### Insert (新增資料) 此函數的操作可以視為Search( )的延伸,先利用 ==Data(**L**)<Data(**V**)<Data(**R**)== 的性質找到新增的節點適合的位置,在將新的節點接上Binary Search Tree。在尋找「適合的位置」時,會多假設一個節點(parent)來記錄目前所在位置(current)的父節點的位置,而在找到「適合的位置」後,在從父節點的位置接上新的節點。 ```graphviz digraph{ N[label="N=NULL";fontcolor=red;color=none] a[label=9] b[label=5] c[label=10] d[label=2] e[label=7] f[label=N;fontcolor=red;style=dashed] g[label=N;fontcolor=red;style=dashed] insert[label="Insert 13";style=dashed] {rank=same;insert,a} insert->a[style=dashed] a->b a->c b->d[weight=2] b->e c->f[style=dashed] c->g[weight=2;style=dashed] } ``` ```graphviz digraph{ a[label=9;color=red;fontcolor=red] b[label=5] c[label=10;color=red;fontcolor=red] d[label=2] e[label=7] f[label=N;fontcolor=red;style=dashed] g[label=13;color=blue;fontcolor=blue] a->b a->c[color=red;style=bold] b->d[weight=2] b->e c->f[style=dashed] c->g[weight=2;color=red;style=bold] } ``` #### 程式碼 ```C BST_Node* InsertNode(BST_Node* root, int val){ BST_Node* newnode; BST_Node* current; BST_Node* parent; newnode = (BST_Node*)malloc(sizeof(BST_Node)); newnode->data = val; newnode->left = NULL; newnode->right = NULL; if (root==NULL){ return newnode;/*若為空,則回傳newnode當作Binary Search Tree的root*/ } else{ current = root; while(current!=NULL){/*藉由判斷val的大小,將current移動到適當的位置*/ parent = current; if (current->data>val){ current = current->left; } else{ current = current->right; } } /*while()迴圈結束後,parent是在leaf的位置*/ if (parent->data>val){ parent->left = newnode;/*若val值較小,則將newnode接在parent的左子點*/ } else{ parent->right = newnode;/*若val值較大,則將newnode接在parent的右子點*/ } } return root; } ``` ### Delete (刪除資料) 此為Binary Search Tree的函式中最難的部分。 在Binary Search Tree上刪除資料,必須在刪除後依然符合 ==Data(**L**)<Data(**V**)<Data(**R**)== 的性質,因此所有指向「欲刪除之節點」的pointer必須修改指向新的記憶體位置。另外,我們還需要再利用一個函式來找出「欲刪除之節點」的parent,而這個函式與尋找節點的函式非常相像,差別在於回傳其父節點。 以下根據「欲刪除之節點」的child pointer 個數分成三類: * **Case1.** 沒有child pointer * **Case2.** 只有一個child pointer * **Case3.** 有兩個child pointer #### Case1:沒有child pointer 只需要考慮原本指向此節點的父點parent,將其 left child/right child 指向NULL即可。 #### Case2:只有一個child pointer 假如「欲刪除之節點」有一個left child,則需要將其父點 parent 的 left child/right child 重新指向「欲刪除之節點」的 left child。 #### Case3:有兩個child pointer 須先從「欲刪除之節點」的==左子樹中尋找最大值==或是==右子樹中尋找最小值==作為取代點(假設此節點為X),來取代「欲刪除之節點」的data,再來對於X進行刪除的動作。 * 左子樹尋找最大值的方法:不斷地向右移動,直到碰到NULL為止。 ```C= /*尋找欲刪除節點之父節點*/ BST_Node* FindParent(BST_Node* root, int value, int* pos){ BST_Node *parent; parent = root; *pos = 0; /*pos用來標記是向左移動還是向右移動*/ while(root!=NULL){ if (root->data == value){ return parent; /*找到目標,回傳此節點之父節點*/ } else{ parent = root; if (root->data > value){ root = root->left; *pos=-1; /*向左移動,並以*pos=-1表示*/ } else{ root = root->right; *pos =1; /*向右移動,並以*pos=1表示*/ } } } return NULL; /*找不到,回傳NULL*/ } /*刪除節點*/ BST_Node* DeleteNode(BST_Node* root, int value){ BST_Node *parent; BST_Node *ptr; BST_Node *next; int pos; parent = FindParent(root, value, &pos);/*利用函式找出父節點*/ if (parent!=NULL){ switch(pos) /*判斷節點在父節點的哪裡*/ { case -1: ptr = parent->left; break; case 1: ptr = parent->right; break; case 0: ptr = parent; break; } if (ptr->left==NULL){ /*若節點之左子節點為NULL*/ if (parent == ptr){ /*父節點即為節點本身,即節點為root*/ root = root->right; /*將root移向其右子節點*/ } else{ if (pos == 1){ /*欲刪除的節點在父節點右方,所以將父節點的右節點*/ /*指向欲刪除節點的右節點*/ parent->right = ptr->right; } else{ /*欲刪除的節點在父節點左方,所以將父節點的左節點*/ /*指向欲刪除節點的左節點*/ parent->left = ptr->right; } } free(ptr); } else if (ptr->right==NULL){ if (parent == ptr){ root = root->left; } else{ if (pos==1){ /*欲刪除的節點在父節點右方,所以將父節點的右節點*/ /*指向欲刪除節點的左節點*/ parent->right = ptr->left; } else{ /*欲刪除的節點在父節點左方,所以將父節點的左節點*/ /*指向欲刪除節點的左節點*/ parent->left =ptr->left; } } free(ptr); } else{/*欲刪除節點有左右子樹*/ parent = ptr; next = ptr->left;/*從欲刪除節點之左子樹中尋找最大值之取代點*/ while(next->right!=NULL){ parent = next; next = next->right; } ptr->data = next->data;/*取代!*/ if (parent == ptr){/*取代點之父節點即為欲刪除點,表示取代點無右子樹*/ ptr->left = next->left; } else{ parent->right = next->left;/*將取代點之左子樹接上其父節點的右方*/ } free(next); } } return root; } ``` ### Sort (排序資料) 由於Binary Search Tree具有==Data(**L**)<Data(**V**)<Data(**R**)== 之特性,只要利用Inorder Traversal的方式,即可得到由小到大的排序。 ```C void inorder_traversal(BST_Node* root){ if (root != NULL){ inorder_traversal(root->left); printf(" %d",root->data); inorder_traversal(root->right); } } ``` ### 範例 最後,將以上所有函式利用下面的範例來做總結: ```C= #include <stdio.h> #include <stdlib.h> #include <assert.h> //建構Binary Search Tree的Structure struct binary_search_tree{ int data; struct binary_search_tree *left; struct binary_search_tree *right; }; typedef struct binary_search_tree BST_Node; void inorder_traversal(BST_Node* root){ if (root != NULL){ inorder_traversal(root->left); printf(" %d",root->data); inorder_traversal(root->right); } } BST_Node* InsertNode(BST_Node* root, int val){ /*略*/ } BST_Node* FindParent(BST_Node* root, int value, int* pos){ /*略*/ } BST_Node* DeleteNode(BST_Node* root, int value){ /*略*/ } int main(){ int i; int data[] = {10, 20, 5, 8, 30, 15, 3, 18, 2, 1}; int ndata = sizeof(data)/sizeof(int); BST_Node *head = NULL; for (i=0; i<ndata; i++) /*建立Binary Search Tree*/ head = InsertNode(head, data[i]); printf("In order listing:\n"); /*輸出Inorder Traversal之排序*/ inorder_traversal(head); printf("\n"); assert(FindNode(head, 15)); /*assert判斷是否有錯誤,如果有錯,立即中止程式*/ assert(!FindNode(head, 11)); head = DeleteNode(head,5); /*刪除節點5*/ printf("After delete the node 5:\n"); inorder_traversal(head); /*輸出Inorder Traversal之排序*/ return 0; } ``` 結果如下: :::success In order listing: 1 2 3 5 8 10 15 18 20 30 After delete the node 5: 1 2 3 8 10 15 18 20 30 :::