二叉尋找樹(5),二叉尋找樹
在本系列的第一篇文章中,已經介紹過了二叉尋找樹的一些性質:
- 節點的左子樹中任意節點值小於根節點
- 節點的右子樹中任意節點值大於根節點
- 左右子樹都必須是二叉尋找樹,不允許存在重複節點。
基於上面的這些性質,自然的就得到了這種判斷方式:樹中的每個節點都有一個特定的值。
假設樹的節點定義為:
struct Node{ int key; Node *left; Node *right;};
方法1 (實現簡單,但卻是錯誤的)對於每個節點,檢測它的左孩子節點是否小於它,且右孩子節點是否大於它。
bool isBST(Node* node){ if (node == NULL) return true; //如果左孩子大於根節點,則不是BST if (node->left != NULL && node->left->key> node->key) return false; //如果右孩子節點小於根節點,則不是BST if (node->right != NULL && node->right->key < node->key) return false; //遞迴的判斷 if (!isBST(node->left) || !isBST(node->right)) return false; //檢測完畢,所有條件通過,則是BST return true;}但是,這種判斷方法是錯誤的,如下面例子所示,節點4處於根節點3的左子樹中,但是函數檢測到這棵樹是BST.
3
/ \
2 5
/ \
1 4
方法2 (結果正確,但是效率比較低)對於每個節點,檢測左子樹中的最大值是否比它小,而右子樹的最小值比它大。
//如果是BST,則返回truebool isBST(Node * node ){ if ( node == NULL) return true; //如果左子樹最大值大於根節點,則返回false if ( node->left != NULL && maxValue( node->left) > node->key) return false; //如果右子樹最小值小於根節點,則返回false if ( node->right != NULL && minValue( node->right) < node->key) return false; //遞迴判斷 if (!isBST( node->left) || !isBST( node->right)) return( false); //所有檢測都通過,是BST return true;}其中,maxValue以及minValue函數,分別返回一顆非空樹中的最大值和最小值。
方法3 (正確並且有效)方法2因為要重複的遍曆樹中的部分資料,效率比較低。更好的方案是每個節點只遍曆一次。 方法3的巧妙之處在於限定了子樹中節點值的範圍,從而每個節點只需訪問一次。節點值的初始範圍可限定為INT_MIN以及INT_MAX。
//判斷是否為BST
bool isBST(Node* node)
{
return(isBSTUtil(node, INT_MIN, INT_MAX));
}
//如果是一顆二叉尋找樹,且值範圍在[min,max],則返回true
bool isBSTUtil(Node* node, int min, int max)
代碼實現:
#include <iostream>struct Node{ int key; Node *left; Node *right;};bool isBSTUtil(Node * node, int min, int max);//判斷是否為BSTbool isBST(Node * node ){ return(isBSTUtil( node, INT_MIN, INT_MAX));}//如果是一顆二叉尋找樹,且值範圍在[min, max],則返回truebool isBSTUtil(Node * node , int min , int max ){ //空樹也是BST if ( node == NULL) return true; //如果節點值違反了最大/最小約束條件,則不是BST if ( node->key < min || node->key > max) return false; //遞迴檢查子樹 return isBSTUtil( node->left, min, node->key - 1) && isBSTUtil( node->right, node->key + 1, max);}// 建立一個新的BST節點Node *createNewNode(int item ){ Node *temp = new Node; temp->key = item; temp->left = temp->right = NULL; return temp;}int main(){ /* tree1的定義 4 / \ 2 5 / \ 1 3 */ Node *root = createNewNode(4); root->left = createNewNode(2); root->right = createNewNode(5); root->left->left = createNewNode(1); root->left->right = createNewNode(3); if (isBST(root)) std::cout << "tree1 is BST\n"; else std::cout << "tree1 is not BST\n"; /* tree2的定義 4 / \ 2 5 / / 1 3 */ root = createNewNode(4); root->left = createNewNode(2); root->left->left = createNewNode(1); root->right = createNewNode(5); root->right->left = createNewNode(3); if (isBST(root)) std::cout << "tree2 is BST\n"; else std::cout << "tree2 is not BST\n"; return 0;}輸出:
tree1 is BST
tree2 is not BST
時間複雜度: O(n)
輔助空間 : 如果不考慮函數調用棧的大小,則為O(1), 否則為O(n)
方法4(使用中序遍曆)1) 對樹進行中序遍曆,將結果儲存在temp數組中。
3) 檢測temp數組中元素是否為升序排列。如果是,則這棵樹為BST.
時間複雜度: O(n)
方法4還可以進一步的最佳化,我們可以避免使用這個額外的數組。在中序遍曆時,可以儲存前驅節點,如果當前節點小於前驅節點,則這棵樹不是BST.
//判斷是否為BSTbool isBST(Node* root){ static Node *prev = NULL; // 中序遍曆這棵樹,並儲存前驅節點至prev中 if (root) { if (!isBST(root->left)) return false; // 檢測節點值的合法性 if (prev != NULL && root->key <= prev->key) return false; prev = root; //右子樹 return isBST(root->right); } return true;}
更多參考:
http://en.wikipedia.org/wiki/Binary_search_tree
http://cslibrary.stanford.edu/110/BinaryTrees.html