1.二叉搜索樹的特性
1.對於任意節點,比其左子樹中任意節點都大,比其右子樹中任意節點都小。
2.最左側的節點一定是最小的,最右側的節點一定是最大的。
3.中序遍歷:是一個有序序列。
2.二叉搜索樹的模擬實現
首先需要一個節點的結構體,裏面包含一個節點所擁有的指向左孩子右孩子的指針pleft和pright,還有當前節點的數據域data
這裏我準備在二叉搜索樹中實現這幾個接口:查找,插入,刪除,找最右最左元素,中序遍歷。所有的接口都通過下圖這棵樹來舉例驗證。
查找
若根節點不爲空:
如果當前根節點data==查找的data 則返回true
如果當前根節點data>查找data,往左子樹查找
如果當前根節點data<查找data,往右子樹查找
否則返回false
代碼實現
//節點結構體
template<class T>
struct BSTNode
{
BSTNode(const T& data=T())
:_pleft(nullptr)
,_pright(nullptr)
,_data(data)
{}
BSTNode<T>* _pleft;//左孩子
BSTNode<T>* _pright;//右孩子
T _data;//數據域
};
typedef BSTNode<T> Node;
Node* Find(const T& data)
{
Node* pcur = _proot;
while (nullptr != pcur)
{
if (pcur->_data > data)
{
pcur = pcur->_pleft;
}
else if (pcur->_data < data)
{
pcur = pcur->_pright;
}
else
{
return pcur;
}
}
return nullptr;
}
假如要查找數據域爲8的節點,那麼先從根節點開始遍歷
插入
插入分爲兩步:1.查找待插入節點的插入位置(注意這裏的查找方法不能使用上述的查找方法) 2.插入新節點
如果是空樹,則直接插入,返回true
如果是非空樹,先找待插入節點在二叉搜索樹樹中的位置,從當前根節點pur開始查找,當pcur不爲空的時候
若當前根節點data>插入data,往左子樹查找
若當前根節點data<插入data,往右子樹查找
若當前根節點data==插入data,數據存在,返回
最後跳出循環時,pcur的位置就是新節點要插入的位置
bool Insert(const T& data)
{
//1.查找插入位置
//空樹
if (nullptr == _proot)
{
_proot = new Node(data);
return true;
}
//非空樹
Node* pcur = _proot;
Node* parent = nullptr;//循環結束時,pcur找到的位置是空的,所以需要一個parent在上面循環中來記錄待插入位置的父節點
while (pcur)
{
parent = pcur;//更新parent
if (pcur->_data > data)
{
pcur = pcur->_pleft;
}
else if (pcur->_data < data)
{
pcur = pcur->_pright;
}
else
{
return false;
}
}
//2.插入新節點
pcur = new Node(data);
if (parent->_data > data)
{
parent->_pleft = pcur;
}
else
{
parent->_pright = pcur;
}
return true;
}
刪除
空樹—退出
非空樹—1.查找刪除節點的位置,2.刪除刪除時,有三種情況
a.刪除節點無孩子節點且無左孩子
b.刪除節點無右孩子
c.刪除節點左右孩子都有
bool Delete(const T& data)
{
//空樹
if (nullptr == _proot)
{
return false;
}
//非空樹
//1.查找位置
Node* pcur = _proot;
Node* parent = nullptr;
while (pcur)
{
if (pcur->_data == data)
{
break;
}
else if (pcur->_data > data)
{
parent = pcur;
pcur = pcur->_pleft;
}
else
{
parent = pcur;
pcur = pcur->_pright;
}
}
if (pcur == nullptr)
return false;
//2.刪除節點
//有三種情況:
/*a.刪除節點無孩子節點且無左孩子
b.刪除節點無右孩子
c.刪除節點左右孩子都有
*/
//a.刪除節點無孩子節點且無左孩子
Node* pdelNode = pcur;
if (nullptr == pcur->_pleft)
{
//根節點
if (nullptr == parent)
{
_proot = parent->_pright;
}
//非根節點
else
{
//刪除節點是父節點的左節點
if (pcur == parent->_pleft)
{
parent->_pleft = pcur->_pright;
}
//刪除節點是父節點的右節點
else
{
parent->_pright = pcur->_pright;
}
}
}
//b.刪除節點無右孩子,只有左孩子
else if (nullptr == pcur->_pright)
{
//根節點
if (nullptr == parent)
{
parent = parent->_pleft;
}
//非根節點
else
{
//刪除節點是父節點的左節點
if (pcur == parent->_pleft)
{
parent->_pleft = pcur->_pleft;
}
//刪除節點是父節點的右節點
else
{
parent->_pright = pcur->_pleft;
}
}
}
//c.刪除節點左右孩子都有
//在pCur的左子樹中找一個替代節點-->一定是左子樹中最大的節點(最右側節點)
//或者右子樹,最小,最左側
//將替代節點中的內容賦值給待刪除節點,然後刪除替代節點
else
{
Node* pdel = pcur->_pleft;
parent = pcur;
while (pdel->_pright)
{
parent = pdel;
pdel = pdel->_pright;
}
pcur->_data = pdel->_data;
//刪除替代節點
//替代節點是父節點的左節點
if (parent->_pleft == pdel)
{
parent->_pleft = pdel->_pleft;
}
//替代節點是父節點的右節點
else
{
parent->_pright = pdel->_pleft;
}
pdelNode = pdel;
}
delete pdelNode;
return true;
}
找最左元素
Node* MostLeft()
{
if (nullptr == _proot)
{
return nullptr;
}
Node* pcur = _proot;
while (pcur->_pleft)
{
pcur = pcur->_pleft;
}
return pcur;
}
找最右元素
Node* MostRight()
{
if (nullptr == _proot)
{
return nullptr;
}
Node* pcur = _proot;
while (pcur->_pright)
{
pcur = pcur->_pright;
}
return pcur;
}
中序遍歷
public:
void Inorder()//將該接口封裝,保護代碼
{
_Inorder(_proot);
}
private:
void _Inorder(Node* proot)
{
if (proot)
{
_Inorder(proot->_pleft);
cout << proot->_data << " ";
_Inorder(proot->_pright);
}
}
說明
以上方法都寫在一個二叉搜索樹類裏面,該類中還有一些用來初始化的構造方法和類的成員變量。
class BSTree
{
public:
BSTree()//記得初始化根節點
:_proot(nullptr)
{}
private:
Node* _pRoot;
3. 二叉搜索樹的應用
- K模型
檢測某個單詞是否拼寫正確,樹節點的值域是正確單詞,用單詞在樹中查找,找到則正確,找不到則錯誤。 - K-V模型
文件中包含了多個ip地址 ,知道每個ip地址出現的次數 <ip,次數>。
template<class K, class V>
struct BSTNode1
{
BSTNode1(const K& key, const V& value)
: _pLeft(nullptr)
, _pRight(nullptr)
, _key(key)
, _value(value)
{}
BSTNode1<T>* _pLeft;
BSTNode1<T>* _pRight;
K _key;
V _value;
};
template<class K, class V>
class BSTree1
{
typedef BSTNode1<K, V> Node1;
public:
BSTree1()
: _pRoot(nullptr)
{}
//
Node1* Find(const K& key)
{
Node1* pCur = _pRoot;
while (pCur)
{
if (key == pCur->_key)
return pCur;
else if (key < pCur->_key)
pCur = pCur->_pLeft;
else
pCur = pCur->_pRight;
}
return nullptr;
}
bool Insert(const K& key, const V& value)
{
if (nullptr == _pRoot)
{
_pRoot = new Node1(key, value);
return true;
}
Node1* pCur = _pRoot;
Node1* pParent = nullptr;
while (pCur)
{
pParent = pCur;
if (key < pCur->_key)
pCur = pCur->_pLeft;
else if (key > pCur->_key)
pCur = pCur->_pRight;
else
return true;
}
pCur = new Node1(key, value);
if (key < pParent->_key)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
return true;
}
private:
Node1* _pRoot;
};
4. 二叉搜索樹的缺陷
如果在構造二叉搜索樹期間,數據序列有序或者接近有序:則該樹會退化爲單支樹。
如果二叉搜索樹退化爲單支樹,則該樹會失去平衡。感覺是對平衡二叉樹的拋磚引玉。