C++ 實現 二叉搜索樹

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. 二叉搜索樹的缺陷

如果在構造二叉搜索樹期間,數據序列有序或者接近有序:則該樹會退化爲單支樹。
如果二叉搜索樹退化爲單支樹,則該樹會失去平衡。感覺是對平衡二叉樹的拋磚引玉。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章