二叉搜索樹你懂了嗎?
什麼是二叉搜索樹?
二叉搜索樹就是當我們需要對一組非常龐大的數字進行搜索時,什麼樣的數據結構才能讓我們用盡量少的時間來搜索到我們想要找的數字呢?(當然也可以是其他數據類型,這裏我用數字來舉例)
這個時候我們想到了樹型結構,可是樹型結構也得需要一定的規律插入才能達到我們想要的效果呀。這個時候就誕生了二叉搜索樹。
二叉搜索樹規定,第一個插入的是頭結點,後來插入的值如果比當前結點的值小,就往左子樹走;如果比當前結點的值大,就往左子樹上走。以此類推,找到最合適的一個空結點進行插入。(當然如果你想讓大的在左邊也是可以的,這個取決於你)
理想的二叉搜索樹:
舉個栗子?
當個我按順序插入數字 {7}、{3, 9}、{1, 5, 8, 10}。(大括號的順序不可以打亂,大括號內的順序可以打亂)
我們心中的理想的二叉樹是這樣的:
這個時候我來搜索任意一個結點的時間複雜度是 O(Log2 N);
代碼實現:
#pragma once
#include <iostream>
template <class T>
struct BSTNode
{
BSTNode(const T& val = T())
:_val(val),
_left(nullptr),
_right(nullptr)
{}
T _val;
BSTNode<T>* _left;
BSTNode<T>* _right;
};
template <class T>
class BSTree
{
public:
typedef BSTNode<T> Node;
typedef Node* pNode;
BSTree(const pNode root = nullptr)
:_root(root)
{}
pNode Find(const T& val)
{
if (_root == nullptr)
return nullptr;
pNode cur = _root;
while (cur)
{
if (cur->_val == val)
{
return cur;
}
else if (cur->_val > val)
{
cur = cur->_left;
}
else if (cur->_val < val)
{
cur = cur->_right;
}
}
return nullptr;
}
bool Insert(const T& val)
{
if (_root == nullptr)
{
_root = new Node(val);
return true;
}
pNode cur = _root;
pNode parent = nullptr;
while (cur)
{
parent = cur;
if (cur->_val > val)
{
cur = cur->_left;
}
else if (cur->_val < val)
{
cur = cur->_right;
}
else
{
return false;
}
}
pNode newNode = new Node(val);
if (parent->_val > val)
{
parent->_left = newNode;
}
else
{
parent->_right = newNode;
}
return true;
}
bool Erase(const T& val)
{
if (_root == nullptr)
{
return false;
}
pNode cur = _root;
pNode parent = nullptr;
while (cur)
{
if (cur->_val == val)
{
break;
}
else if (cur->_val > val)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_val < val)
{
parent = cur;
cur = cur->_right;
}
}
if (cur->_left == nullptr && cur->_right == nullptr)
{
if (cur != _root)
{
if (parent->_left == cur)
parent->_left = nullptr;
else
parent->_right = nullptr;
}
else
{
_root = nullptr;
}
delete cur;
cur = nullptr;
}
else if (cur->_left == nullptr && cur->_right != nullptr)
{
if (cur != _root)
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else if (parent->_right == cur)
{
parent->_right = cur->_right;
}
}
else
{
_root = _root->_right;
}
delete cur;
cur = nullptr;
}
else if (cur->_left != nullptr && cur->_right == nullptr)
{
if (cur != _root)
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else if (parent->_right == cur)
{
parent->_right = cur->_left;
}
}
else
{
_root = _root->_left;
}
delete cur;
cur = nullptr;
}
else
{
pNode exchange = cur;
if (cur == _root)
cur = cur->_left;
pNode newNode = cur;
pNode nParent = nullptr;
while (newNode)
{
if (newNode->_right)
{
nParent = newNode;
newNode = newNode->_right;
}
else
break;
}
if (nParent == nullptr)
{
exchange->_left = newNode->_left;
}
exchange->_val = newNode->_val;
if (nParent)
nParent->_right = nullptr;
delete newNode;
newNode = nullptr;
}
return true;
}
void Inorder(void)
{
_Inorder(_root);
std::cout << std::endl;
}
private:
void _Inorder(const pNode root)
{
if (root)
{
_Inorder(root->_left);
std::cout << root->_val << " ";
_Inorder(root->_right);
}
}
pNode _root;
};
因爲註釋亂碼了,所以把註釋刪了,以後儘量嘗試寫英文註釋
這裏其實查找和插入都不復雜,這裏可以自己看很容易看懂。
比較複雜的是刪除,刪除分幾種情況:
- 當要刪除的
cur
(就是指要刪除的結點) 是葉子結點的時候,這個時候只需要將這個cur
的父結點對應這個cur
的那個指針置空,然後直接刪除即可。 - 當要刪除的
cur
只有左子樹的時候,這個時候就讓cur
的父結點對應cur
的指針指向cur
的左子樹,然後刪除cur
即可。 - 當要刪除的
cur
只有右子樹的時候,這與上一條類似。 - 當要刪除的
cur
左右子樹都存在的時候,有兩個方法,我們可以在cur
的左子樹裏找一個值最大的結點,或者在cur
的右子樹裏找一個值最小的結點。把找到的結點的值賦值給cur
,然後刪除這個找到的結點即可。(想想,不管是哪種尋找方法,都可以保持二叉搜索樹的性質不改變對嗎?比如在左子樹裏找一個值最大的結點"放在"cur
上,可以保證cur
的左子樹的所有結點的值都比cur
的值小,cur
右子樹的所有結點的值都比cur
大。)
注意:二叉搜索樹的中序遍歷是一個有序的。
二叉搜索樹的缺點:
但是,有這樣一個情況,如果插入一組有序的數字呢?就比如 {1, 2, 3, 4, 5, 6, 7, 8, 9}
這個時候二叉搜索樹是這樣的:
這個時候再去搜索任何一個結點的時間複雜度就又變成了 O(N)。這跟我們的初衷相違背,我們就是想要查找的時候能快一點。
所以這個時候就有大佬發明了 AVL樹。
下篇會介紹 AVL樹。
叮~ ?