作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!
二叉搜索樹的深度與搜索效率
我們在樹, 二叉樹, 二叉搜索樹中提到,一個有n個節點的二叉樹,它的最小深度為log(n),最大深度為n。比如下面兩個二叉樹:
深度為n的二叉樹
深度為log(n)的二叉樹
這兩個二叉樹同時也是二叉搜索樹(參考樹, 二叉樹, 二叉搜索樹)。注意,log以2為基底。log(n)是指深度的量級。根據我們對深度的定義,精確的最小深度為floor(log(n)+1)。
我們將處於同一深度的節點歸為一層。如果除最后一層外的其他層都被節點填滿時,二叉樹有最小深度log(n)。
二叉搜索樹的深度越小,那么搜索所需要的運算時間越小。一個深度為log(n)的二叉搜索樹,搜索算法的時間復雜度也是log(n)。然而,我們在二叉搜索樹中已經實現的插入和刪除操作並不能讓保持log(n)的深度。如果我們按照8,7,6,5,4,3,2,1的順序插入節點,那么就是一個深度為n的二叉樹。那么,搜索算法的時間復雜度為n。
n和log(n)的時間復雜度意味着什么呢?時間復雜度代表了完成算法所需要的運算次數。時間復雜度越小,算法的速度越快。
可以看到,隨着元素的增加,log(n)的時間復雜度的增長要遠小於n。所以,我們自然希望二叉搜索樹能盡可能保持log(n)的深度。在上面深度為n的例子中,我們發現,每個節點只有左節點被填滿。樹的每一層都有很多空位。能不能盡可能減少每一層的空位呢? (相應的,減少樹的深度)
“緊致”的樹
一種想法是先填滿一層,再去填充下一層,這樣就是一個完全二叉樹(complete binary tree)。這樣的二叉樹實現插入算法會比較復雜。我們將介紹一種思路相似,但比較容易實現的樹狀數據結構——AVL樹。
AVL樹
AVL樹是根據它的發明者G. M. Adelson-Velskii和E. M. Landis命名的。它是一種特殊的二叉搜索樹。AVL樹要求: 任一節點的左子樹深度和右子樹深度相差不超過1
(空樹的深度為0。注意,有的教材中,采用了不同的深度定義方法,所以空樹的深度為-1)
下面是AVL樹:
AVL樹
AVL樹的特性讓二叉搜索樹的節點實現平衡(balance):節點相對均勻分布,而不是偏向某一側。因此,AVL樹的搜索算法復雜度是log(n)的量級。
我們在二叉搜索樹中定義的操作,除了插入,都可以用在AVL樹上 (假設使用懶惰刪除)。如果進行插入操作,有可能會破壞AVL樹的性質,比如:
插入2: 破壞AVL樹
觀察節點5,它的左子樹深度為2,右子樹深度為0,所以左右兩個子樹深度相差為2,不再是AVL樹。由於2的加入,從節點6,1,5,3到2的層數都增加1。6, 1, 5節點的AVL性質都被破壞。如果從節點2向上回溯,節點5是第一個被破壞的。從節點3開始的子樹深度加1,這是造成6, 1, 5的AVL性質被破壞的本質原因。我們將5和3之間的路徑畫成虛線(就好像掛了重物,邊被拉斷一樣)。
我們可以通過單旋照(single rotation),調整以5為根節點的子樹,來修正因為插入一個元素而引起的對AVL性質的破壞。如下:
Single rotation: 左側超重,向右轉
通過單旋轉,3成為新的根節點,2,5稱為3的左右子節點。子樹重新成為AVL樹。該子樹的深度減小1,這將自動修正2帶給節點6,1的“超負荷”。
單旋轉效果如下:
向右單旋轉
特別要注意的是,為了保持二叉樹的性質,子樹B過繼給了節點5。
向左單旋轉與之類似。作為練習,可以嘗試繪制向左單旋轉的示意圖。
但如果插入的節點不是2,而是4,會是如何呢?
插入4
嘗試單旋轉,會發現無法解決問題。以5為根節點的子樹向右單旋轉后,樹將以3為根節點,4,5為子節點。4比3大,卻是3的左子節點,顯然,這依然不符合二叉搜索樹的性質。但基於和上面相似的原則(調整以5為根節點的樹),我們發現有一個簡單的解決方式:
double rotation
上面的操作被稱作雙旋轉(double rotation)。雙旋轉實際上是進行兩次單旋轉: 4為根節點的子樹先進行一次向左的單旋轉,然后將5為根節點的子樹進行了一次向右的單旋轉。這樣恢復了樹的ACL性質。
對於AVL樹,可以證明,在新增一個節點時,總可以通過一次旋轉恢復AVL樹的性質。
當我們插入一個新的節點時,在哪里旋轉?是用單旋轉還是雙旋轉?
我們按照如下基本步驟進行:
1. 按照二叉搜索樹的方式增加節點,新增節點稱為一個葉節點。
2. 從新增節點開始,回溯到第一個失衡節點(5)。
(如果回溯到根節點,還沒有失衡節點,就說明該樹已經符合AVL性質。)
3. 找到斷的邊(5->3),並確定斷弦的方向(5的左側)
4. 以斷邊下端(3)為根節點,確定兩個子樹中的哪一個深度大(左子樹還是右子樹)。
(這兩棵子樹的深度不可能相等,而且深度大的子樹包含有新增節點。想想為什么)
5. 如果第2和第3步中的方向一致(都為左或者都為右),需要單旋轉以失衡節點為根節點的子樹。
否則,雙旋轉以失衡節點為根節點的子樹。
下面是AVL樹的插入算法實現如下:
/* By Vamei */
/* binary search tree */ #include <stdio.h> #include <stdlib.h> typedef struct node *position; typedef int ElementTP; struct node { int depth; position parent; ElementTP element; position lchild; position rchild; }; /* pointer => root node of the tree */ typedef struct node *TREE; position insert_value(TREE, ElementTP); int depth(TREE); static void insert_node_to_nonempty_tree(TREE, position); static void update_root_depth(TREE); static TREE recover_avl(TREE, position); static int depth_diff(TREE); static position insert_leaf(TREE, ElementTP); static void insert_node_to_nonempty_tree(TREE, position); static TREE left_single_rotate(TREE); static TREE left_double_rotate(TREE); static TREE right_single_rotate(TREE); static TREE right_double_rotate(TREE); void main(void) { TREE tr; position np; ElementTP element; tr = NULL; tr = insert_value(tr, 18); tr = insert_value(tr, 5); printf("root: %d\n", tr->element); printf("depth: %d\n", depth(tr)); tr = insert_value(tr, 2); printf("root: %d\n", tr->element); printf("depth: %d\n", depth(tr)); tr = insert_value(tr, 4); printf("root: %d\n", tr->element); printf("depth: %d\n", depth(tr)); printf("root->lchild: %d\n", tr->lchild->element); tr = insert_value(tr, 3); printf("root: %d\n", tr->element); printf("depth: %d\n", depth(tr)); printf("root->lchild: %d\n", tr->lchild->element); printf("root->lchild->lchild: %d\n", tr->lchild->lchild->element); } /* * insert value * */ position insert_value(TREE tr, ElementTP value) { position new; /* insert a value to a binary search tree */
new = insert_leaf(tr, value); update_root_depth(new); if (tr == NULL) { tr = new; } else { tr = recover_avl(tr, new); } return tr; } /* * get the depth of the tree * use this function to access depth */
int depth(TREE tr) { if (tr == NULL) { return 0; } else { return tr->depth; } } //======================================== // static functions: for internal use
/* * traverse the path from new node to root node * make one rotation, recover AVL and stop */
static TREE recover_avl(TREE tr, position np) { int myDiff; while (np != NULL) { update_root_depth(np); myDiff = depth_diff(np); if (myDiff > 1 || myDiff < -1) { if (myDiff > 1) { /* left rotate needed */
if(depth_diff(np->rchild) > 0) { np = left_single_rotate(np); } else { np = left_double_rotate(np); } } if (myDiff < -1) { if(depth_diff(np->lchild) < 0) { np = right_single_rotate(np); } else { np = right_double_rotate(np); } } /* if rotation changes root node */
if (np->parent == NULL) tr = np; break; } np = np->parent; } return tr; } /* * difference of rchild->depth and lchild->depth */
static int depth_diff(TREE tr) { if (tr == NULL) { return 0; } else { return depth(tr->rchild) - depth(tr->lchild); } } /* * left single rotation * return the new root */
static TREE left_single_rotate(TREE tr) { TREE newRoot, parent; parent = tr->parent; newRoot = tr->rchild; /* detach & attach */
if (newRoot->lchild != NULL) newRoot->lchild->parent = tr; tr->rchild = newRoot->lchild; update_root_depth(tr); /* raise new root node */ newRoot->lchild = tr; newRoot->parent = parent; if (parent != NULL) { if (parent->lchild == tr) { parent->lchild = newRoot; } else { parent->rchild = newRoot; } } tr->parent = newRoot; update_root_depth(newRoot); return newRoot; } /* * right single rotation * return the new root */
static TREE right_single_rotate(TREE tr) { TREE newRoot, parent; parent = tr->parent; newRoot = tr->lchild; /* detach & attach */
if (newRoot->rchild != NULL) newRoot->rchild->parent = tr; tr->lchild = newRoot->rchild; update_root_depth(tr); /* raise new root node */ newRoot->rchild = tr; newRoot->parent = parent; if (parent != NULL) { if (parent->lchild == tr) { parent->lchild = newRoot; } else { parent->rchild = newRoot; } } tr->parent = newRoot; update_root_depth(newRoot); return newRoot; } /* * left double rotation * return */
static TREE left_double_rotate(TREE tr) { right_single_rotate(tr->rchild); return left_single_rotate(tr); } /* * right double rotation * return */
static TREE right_double_rotate(TREE tr) { left_single_rotate(tr->lchild); return right_single_rotate(tr); } /* * update tr->depth * assume lchild->depth and rchild->depth are correct */
static void update_root_depth(TREE tr) { int maxChildDepth; int depLChild, depRChild; if (tr==NULL) return; else { depLChild = depth(tr->lchild); depRChild = depth(tr->rchild); maxChildDepth = depLChild > depRChild ? depLChild : depRChild; tr->depth = maxChildDepth + 1; } } /* * insert a new value into the tree as a leaf * return address of the new node */
static position insert_leaf(TREE tr, ElementTP value) { position np; /* prepare the node */ np = (position) malloc(sizeof(struct node)); np->element = value; np->parent = NULL; np->lchild = NULL; np->rchild = NULL; if (tr != NULL) { insert_node_to_nonempty_tree(tr, np); } return np; } /* * insert a node to a non-empty tree * called by insert_value() */
static void insert_node_to_nonempty_tree(TREE tr, position np) { /* insert the node */
if(np->element <= tr->element) { if (tr->lchild == NULL) { /* then tr->lchild is the proper place */ tr->lchild = np; np->parent = tr; return; } else { insert_node_to_nonempty_tree(tr->lchild, np); } } else if(np->element > tr->element) { if (tr->rchild == NULL) { tr->rchild = np; np->parent = tr; return; } else { insert_node_to_nonempty_tree(tr->rchild, np); } } }
輸出如下:
root: 18
depth: 2
root: 5
depth: 2
root: 5
depth: 3
root->lchild: 2
depth: 3
root->lchild: 3
root->lchild->lchild: 2
(空行是額外加的)
總結:
AVL樹: 平衡,深度相差不超過1
單旋轉,雙旋轉
歡迎繼續閱讀“紙上談兵: 算法與數據結構”系列。