二叉搜索樹
二叉搜索樹又被稱爲二叉查找樹、二叉排序樹。其具備以下性質:
1> 每一個節點都有一個作爲搜索依據的關鍵碼(key),並且互不相同
2>左節點的關鍵碼都小於根結點的關鍵碼
3>右節點的關鍵碼都大於根結點的關鍵碼
4>每個子樹都滿足二叉搜索樹
具備以上性質,所以二叉搜索樹的中序遍歷是有序的。
2.二叉搜索樹的構造
每個節點有一個key值和value值,key值是用來區分節點的,每個節點的key值都不相同。每個節點都
有指向左和右的指針。
template<class K, class V> struct BSTreeNode { BSTreeNode(const K&key, const V&value) :_left(NULL) , _right(NULL) , _key(key) , _value(value) {} BSTreeNode<K, V>* _left; BSTreeNode<K, V>* _right; K _key; V _value; }; template<class K, class V> class BSTree { typedef BSTreeNode<K, V> Node; public: BSTree() :_root(NULL) {} ~BSTree() { if (_root) { clear(); } } protected: Node* _root; };
3.二叉搜索樹的插入
如果插入的時候是一顆空樹,則new一個根結點出來,之後插入的時候根據key值來進行判斷插入的位
置,每插入一個值都要從根節點開始判斷,要插入的key值比根結點的key值小,則走左樹,比根節點的
key值大,則走右樹。直到找到正確的位置進行插入,返回true。當然與根節點的值相等的話返回false。
1>非遞歸的插入
bool insert(const K& key, const V&value)
{//遵循左小於根右大於根,每個子樹都滿足
if (_root == NULL)
{
_root = new Node(key, value);
return true;
}
Node*parent = NULL;
Node*cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key>key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
if (parent->_key > key)
{
parent->_left = new Node(key, value);
}
else
parent->_right = new Node(key, value);
return true;
}
2>遞歸插入
//進行遞歸插入,注意_insertR接收的root參數是引用類型的,
bool insertR(const K&key, const V&value)
{
return _insertR(_root, key, value);
}
protected:
bool _insertR(Node* &root, const K&key, const V&value)
{
if (root == NULL)//此時的root肯定是要插入的key值正確的位置,因爲root是引用的
{
root = new Node(key, value);
return true;
}
if (root->_key > key)
{
_insertR(root->_left, key, value);
}
else if (root->_key < key)
{
_insertR(root->_right, key, value);
}
else
{
return false;
}
}
4.二叉搜索樹的查找
二叉樹最壞的查找情況時間複雜度爲O(n);平均時間複雜度爲O(log2(n))。
Node* find(const K&key)//找到的話返回該節點 { assert(_root); Node* cur = _root; while (cur) { if (cur->_key > key) { cur = cur->_left; } else if (cur->_key < key) { cur = cur->_right; } else { return cur; } } return NULL; }
5.二叉搜索樹的刪除
二叉搜索樹的刪除可分爲三種情況:
1>要刪除的節點的左樹爲空,右樹可爲空可不爲空,這個節點可能爲根節點,也可能爲其他節點。
刪一個普通的左樹爲空的節點。讓該節點的父親節點指向該節點的右節點,刪除該節點。
刪除這個樹的8這個節點
2>要刪除的節點的右樹爲空,左樹可爲空可不爲空,這個節點可能爲根節點,也可能爲其他節點。
刪除的方法與右節點同理。這裏不舉例子了。
3>要刪除的節點左右都不爲空。
首先找到該節點右樹的最左節點,然後與其交換,再刪除最左節點。因爲該節點右樹的最左節點比
該節點左樹的節點都大,比該節點右樹的節點都小,滿足二叉搜索樹,所以用其與刪除的節點交換。
1>非遞歸的刪除 bool remove(const K&key) { if (_root == NULL) return false; Node* cur = _root; Node* parent = NULL; //找到要刪除的節點 while (cur) { if (cur->_key < key) { parent = cur; cur = cur->_right; } else if (cur->_key>key) { parent = cur; cur = cur->_left; } else { break; } } if (cur == NULL) { return false; } Node* del; //1.cur->left==NULL if (cur->_left == NULL) { del = cur; //刪除的是根節點 if (parent == NULL) { _root = cur->_right; } else { if (parent->_left == cur) { parent->_left = cur->_right; } else { parent->_right = cur->_right; } } } //2.cur->right=NULL else if (cur->_right == NULL) { del = cur; if (parent == NULL) { _root = cur->_left; } else { if (parent->_left == cur) { parent->_left = cur->_left; } else { parent->_right = cur->_left; } } } else //左右不爲空 { parent = cur; //找到右樹的最左節點進行替換,這樣的話還是滿足二叉搜索樹 Node* firstLeftN = cur->_right; while (firstLeftN->_left) { parent= firstLeftN; firstLeftN = firstLeftN->_left; } del = firstLeftN; cur->_key = firstLeftN->_key; cur->_value = firstLeftN->_value; if (parent->_left == firstLeftN) { parent->_left = firstLeftN->_right; } else { parent->_right = firstLeftN->_right; } } delete del; return true; } 2>遞歸的刪除 bool removeR(const K&key) { return _removeR(_root, key); } bool _removeR(Node*& root, const K&key) { if (root == NULL) { return false; } if (root->_key < key) { _removeR(root->_right, key); } else if (root->_key>key) { _removeR(root->_left, key); } else { Node *del = root; //1.要刪的節點左爲空 if (root->_left == NULL) { root = root->_right; } //右爲空 else if (root->_right == NULL) { root = root->_left; } //左右不爲空 else { Node*firstLeftN = root->_right; while (firstLeftN->_left) { firstLeftN = firstLeftN->_left; } swap(root->_key, firstLeftN->_key); swap(root->_value, firstLeftN->_value); return _removeR(root->_right,key); } delete del; return true; } }