二叉搜索樹上的基本操作花費的時間與這棵樹的高度成正比,對於有n個節點的搜索二叉樹平均高度O(lg n),但是這樣的情況並不能保證。
1)、二叉搜索樹(BST)性質:
1. 若任意根節點存在左子樹,則左子樹的所有節點均小於等於根節點。
2. 若任意根節點存在右子樹,則右子樹的所有節點均大於等於根節點。
3. 子樹同樣是二叉搜索樹。
搜索樹的實現包括:插入操作、查找操作、刪除操作、遍歷操作。
2)、二叉搜索樹的分塊實現:
①、結構體搭建
//創建二叉樹結構體 ,左孩子, 右孩子, 父節點,實例化操作;
struct STreeNode {
//成員
TreeKeyType key;
pSTreeNode pLeftChild;
pSTreeNode pRightChild;
//實例化操在這裏插入代碼片作
STreeNode (TreeKeyType Value){
key = Value;
pLeftChild = NULL;
pRightChild = NULL;
}
};
②、創建一個二叉搜索數的類,用來管理獨有的方法
//創建類來管理操作方法
class CBinTree {
public :
CBinTree();;
~CBinTree();
void Insert(TreeKeyType Value);
void Insert(pSTreeNode pNode, TreeKeyType Value);
pSTreeNode Search(TreeKeyType Value);
pSTreeNode Search(pSTreeNode pNode, TreeKeyType Value);
void Delete(TreeKeyType Value);
pSTreeNode getSuccessor(pSTreeNode Value); //刪除操作
void Preorder(); // 前序遍歷,非遞歸方法(借用堆棧)
void Inorder(); // 中序遍歷,非遞歸方法(借用堆棧)
void Postorder(); // 後序遍歷,非遞歸方法(借用堆棧)
void PreorderRecursively(pSTreeNode pNode); // 前序遍歷,遞歸調用
void InorderRecursively(pSTreeNode pNode); // 中序遍歷,遞歸調用
void PostorderRecursively(pSTreeNode pNode); // 後序遍歷,遞歸調用
pSTreeNode GetMaxKey(); // 獲得二叉查找樹中元素值最大的節點
pSTreeNode GetMinKey(); // 獲得二叉查找樹中元素值最小的節點
public:
pSTreeNode pRoot; //定義一個根節點
};
③、插入
{15, 3, 20, 8, 10} 是如何順序插入的,在紙上實現一遍以自己加深記憶。
當我想插入10這個元素時,需要根節點和val = 10 進行比較;
if(node - > 左孩子存在)
if (val < node.val) {
將下一個左孩子作爲根節點繼續比較 ;
}
if(node - > 右孩子存在)
if (val > node.val) {
將下一個右孩子作爲根節點繼續比較 ;
}
程序如下
void CBinTree::Insert(pSTreeNode pNode, TreeKeyType Value) {
//目前的節點數據大於輸入值
if (pNode->key > Value)
{
//如果左孩子空了,插入左孩子處,否則進入遞歸調用
if (pNode->pLeftChild == NULL)
pNode->pLeftChild = new STreeNode(Value);
else
Insert(pNode->pLeftChild, Value);
}else {
if (pNode->pRightChild == NULL)
//找到葉子節點就進行插入
pNode->pRightChild = new STreeNode(Value);
else {
Insert(pNode->pRightChild, Value);
}
}
}
④、查找操作
查找操作最爲簡單,從根節點開始遍歷,val大,向右子樹開始繼續尋找,否則就向左子樹開始繼續尋找。這裏也用了遞歸調用的方法,看起來一目瞭然。
程序如下:
//入口函數
pSTreeNode CBinTree::Search(TreeKeyType Value) {
return Search(pRoot, Value);
}
pSTreeNode CBinTree::Search(pSTreeNode pNode,TreeKeyType Value) {
if (pNode == NULL) {
return NULL;
}
if (pNode->key == Value) {
return pNode;
}else {
if (pNode->key < Value) {
return Search(pRoot->pRightChild, Value);
}else {
return Search(pRoot->pLeftChild, Value);
}
}
}
⑤、刪除操作
二叉樹的刪除操作最爲麻煩,再刪除之前,對着搜索二叉樹的圖進行試探,並且從其他大佬們那學到的,刪除操作最好考慮全部的可能性,之後再看是否能優化:
1.刪除葉子節點;
2.刪除節點只有單獨的右子樹;
3.刪除節點只有單獨的左子樹;
4.刪除節點左子樹右子樹均存在;
首先找到需要刪除的節點pFindNode,並定義一個pParentNode變量來標記刪除節點的父節點。
情況1,如果刪除的時葉子節點,需要將標記的pParentNode直接指向NULL即可刪除,結束後釋放pFindNode內存;
情況2,3,只存在單獨的子樹,當刪除節點pFindNode不存在左子樹的情況下,其右子樹均大於pFindNode,那麼,如果pFindNode是父節點pParentNode的左孩子,說明下面的一切都比父節點小,請都歸順於pParentNode,成爲他的左子樹吧,因爲你們都比他小,同理刪除節點是父節點的有孩子,那麼全部歸順爲右子樹。
當刪除節點pFindNode不存在右子樹的情況下,其左子樹均小於pFindNode,那麼,如果pFindNode是父節點pParentNode的左孩子,說明下面的一切都比父節點小,請都歸順於pParentNode,成爲他的左子樹吧,因爲你們都比他小。
情況4,刪除的節點同時擁有左右子樹,則重點是找到刪除元素的後繼節點,將後繼節點(剛好比pFindNode大的數)爲首,重新構造搜索二叉樹子樹,再將其銜接到pParentNode節點下。
如果你理解了刪除的內涵,你會發現,情況4的複雜在於後繼節點不能直接繼承,需要找到間接繼承人。
如圖,後繼點 9 一定是沒有左子樹的(因爲他就是最左的),他的右子樹,此時需要繼承在sucParent的左子樹下,因爲他是恆小於sucParent的。
//刪除操作,二叉樹刪除操作最爲麻煩,起碼要考慮四種方式
//1.葉子節點2.單獨的左子樹3.單獨的右子樹3.左右子樹都存在
void CBinTree::Delete(TreeKeyType Value) {
pSTreeNode pParentNode = pRoot;
pSTreeNode pFindNode = pRoot;
// 找到Value元素對應的節點
while (pFindNode != NULL)
{
if (pFindNode->key == Value)
break;
pParentNode = pFindNode;
if (pFindNode->key > Value)
pFindNode = pFindNode->pLeftChild;
else
pFindNode = pFindNode->pRightChild;
}
if (pFindNode == NULL)
return;
// ‘獨生子家庭’ 和 ‘丁克’同時討論,這兩種輕情況優化再一起,通過一個pTemp來存儲後繼節點的頭
if (pFindNode->pLeftChild == NULL || pFindNode->pRightChild == NULL)
{
// 一個子結點爲空或者兩個子結點都爲空
pSTreeNode pTemp = NULL;
if (pFindNode->pLeftChild != NULL)
pTemp = pFindNode->pLeftChild;
else if (pFindNode->pRightChild != NULL)
pTemp = pFindNode->pRightChild;
if (pParentNode->pLeftChild == pFindNode)
pParentNode->pLeftChild = pTemp;
else
pParentNode->pRightChild = pTemp;
delete pFindNode;
pFindNode = NULL;
}
else
{
// 刪除這邊主要是找到後繼節點作爲交換節點,其他的重新構成一個樹
// 再整體進行繼承
pSTreeNode successor = getSuccessor(pFindNode);
if (pFindNode == pRoot) {
pRoot = successor;
}else if (pFindNode == pParentNode->pLeftChild) {
pParentNode->pLeftChild = successor;
}else {
pParentNode->pRightChild = successor;
}
successor->pLeftChild = pFindNode->pLeftChild;
}
}
pSTreeNode CBinTree::getSuccessor(pSTreeNode delNode) {
pSTreeNode pTemp = delNode->pRightChild; //此處定義右孩子是爲了找到後繼節點
//所謂的後繼節點其實就是右孩子的最左孩子---**比刪除節點大的最小節點**;
pSTreeNode sucParent = NULL;
pSTreeNode successor = pTemp;
while (pTemp != NULL) {
//找到後繼節點並建立新的索引
sucParent = successor;
successor = pTemp;
pTemp = pTemp->pLeftChild;
}
if (successor != delNode->pRightChild) {
sucParent->pLeftChild = successor->pRightChild;//都是比父節點小的,左邊分支的右子樹永遠比這個小根節點小
successor->pRightChild = delNode->pRightChild;
}
//如果右孩子本來就和刪除元素相等就不用變化了
return successor;
}
⑥、獲取最值操作
搜做二叉樹的獲取最值只與其深度有關,這也是爲甚麼平衡搜索二叉樹的出現原因。
1.最右即爲最大;
2.最左即爲最小;
程序如下:
pSTreeNode CBinTree::GetMaxKey() {
pSTreeNode pMax = pRoot;
while (pMax->pLeftChild != NULL) {
if (pMax->pLeftChild != NULL)
pMax = pMax->pLeftChild;
}
return pMax;
}
pSTreeNode CBinTree::GetMinKey() {
pSTreeNode pMin = pRoot;
while (pMin->pRightChild != NULL) {
if (pMin->pRightChild != NULL)
pMin = pMin->pRightChild;
}
return pMin;
}
⑦、二叉樹的遍歷實現
遞歸實現先序遍歷不談,只說一點
在這裏插入圖片描述
對於遞歸上圖所示的二叉樹實際內部訪問順序爲:
F C A A A C D B B B D D C F E H H H E G M M M G G E F
先序遍歷 Preorder 實際上是遞歸順序中每個節點的第一次出現的順序;
中序遍歷 Inorder 實際上是遞歸順序中每個節點的第二次出現的順序;
後續遍歷Postorder實際上是遞歸順序中每個節點的第三次出現的順序;
遞歸實現程序如下:
//遞歸試前序遍歷實現輸出
void CBinTree::PreorderRecursively(pSTreeNode pNode) {
if (pNode == NULL)
return;
cout << "" << pNode->key << " ";
PreorderRecursively(pNode->pLeftChild);
PreorderRecursively(pNode->pRightChild);
}
//遞歸調用實現中序遍歷
void CBinTree::InorderRecursively(pSTreeNode pNode) {
if (pNode == NULL)
return;
InorderRecursively(pNode->pLeftChild);
cout << "" << pNode->key << " ";
InorderRecursively(pNode->pRightChild);
}
//遞歸調用實現後續遍歷
void CBinTree::PostorderRecursively(pSTreeNode pNode) {
if (pNode == NULL)
return;
PostorderRecursively(pNode->pLeftChild);
PostorderRecursively(pNode->pRightChild);
cout << "" << pNode->key << " ";
}
3)主程序清單
#include <iostream>
#include <stack>
using namespace std;
typedef int TreeKeyType;
typedef struct STreeNode* pSTreeNode;
//創建二叉樹結構體 ,左右父
struct STreeNode {
//成員
TreeKeyType key;
pSTreeNode pLeftChild;
pSTreeNode pRightChild;
//實例化操作
STreeNode (TreeKeyType Value){
key = Value;
pLeftChild = NULL;
pRightChild = NULL;
}
};
//創建類來管理操作方法
class CBinTree {
public :
CBinTree();;
~CBinTree();
void Insert(TreeKeyType Value);
void Insert(pSTreeNode pNode, TreeKeyType Value);
pSTreeNode Search(TreeKeyType Value);
pSTreeNode Search(pSTreeNode pNode, TreeKeyType Value);
void Delete(TreeKeyType Value);
void Preorder(); // 前序遍歷,非遞歸方法(借用堆棧)
void Inorder(); // 中序遍歷,非遞歸方法(借用堆棧)
void Postorder(); // 後序遍歷,非遞歸方法(借用堆棧)
void PreorderRecursively(pSTreeNode pNode); // 前序遍歷,遞歸調用
void InorderRecursively(pSTreeNode pNode); // 中序遍歷,遞歸調用
void PostorderRecursively(pSTreeNode pNode); // 後序遍歷,遞歸調用
pSTreeNode GetMaxKey(); // 獲得二叉查找樹中元素值最大的節點
pSTreeNode GetMinKey(); // 獲得二叉查找樹中元素值最小的節點
pSTreeNode getSuccessor(pSTreeNode Value);
void FreeMemory(pSTreeNode pNode); // 釋放內存
public:
pSTreeNode pRoot;
};
CBinTree::CBinTree()
{
pRoot = NULL;
}
CBinTree::~CBinTree()
{
if (pRoot == NULL)
return;
//FreeMemory(pRoot);
}
void CBinTree::Insert(TreeKeyType Value) {
if (pRoot == NULL) {
pRoot = new STreeNode(Value);
}
else {
Insert(pRoot, Value);
}
}
void CBinTree::Insert(pSTreeNode pNode, TreeKeyType Value) {
//目前的節點數據大於輸入值
if (pNode->key > Value)
{
//如果左孩子空了,插入左孩子處,否則進入遞歸調用
if (pNode->pLeftChild == NULL)
pNode->pLeftChild = new STreeNode(Value);
else
Insert(pNode->pLeftChild, Value);
}else {
if (pNode->pRightChild == NULL)
//找到葉子節點就進行插入
pNode->pRightChild = new STreeNode(Value);
else {
Insert(pNode->pRightChild, Value);
}
}
}
//search
pSTreeNode CBinTree::Search(TreeKeyType Value) {
return Search(pRoot, Value);
}
pSTreeNode CBinTree::Search(pSTreeNode pNode,TreeKeyType Value) {
if (pNode == NULL) {
return NULL;
}
if (pNode->key == Value) {
return pNode;
}else {
if (pNode->key < Value) {
return Search(pRoot->pRightChild, Value);
}else {
return Search(pRoot->pLeftChild, Value);
}
}
}
//刪除操作,二叉樹刪除操作最爲麻煩,起碼要考慮四種方式
//1.葉子節點2.單獨的左子樹3.單獨的右子樹3.左右子樹都存在
void CBinTree::Delete(TreeKeyType Value) {
pSTreeNode pParentNode = pRoot;
pSTreeNode pFindNode = pRoot;
// 找到Value元素對應的節點
while (pFindNode != NULL)
{
if (pFindNode->key == Value)
break;
pParentNode = pFindNode;
if (pFindNode->key > Value)
pFindNode = pFindNode->pLeftChild;
else
pFindNode = pFindNode->pRightChild;
}
if (pFindNode == NULL)
return;
// 處理Value元素的父節點和Value元素的節點
if (pFindNode->pLeftChild == NULL || pFindNode->pRightChild == NULL)
{
// 一個子結點爲空或者兩個子結點都爲空
pSTreeNode pTemp = NULL;
if (pFindNode->pLeftChild != NULL)
pTemp = pFindNode->pLeftChild;
else if (pFindNode->pRightChild != NULL)
pTemp = pFindNode->pRightChild;
if (pParentNode->pLeftChild == pFindNode)
pParentNode->pLeftChild = pTemp;
else
pParentNode->pRightChild = pTemp;
delete pFindNode;
pFindNode = NULL;
}
else
{
// 刪除這邊主要是找到後繼節點作爲交換節點,其他的重新構成一個樹
//整體進行替換
pSTreeNode successor = getSuccessor(pFindNode);
if (pFindNode == pRoot) {
pRoot = successor;
}else if (pFindNode == pParentNode->pLeftChild) {
pParentNode->pLeftChild = successor;
}else {
pParentNode->pRightChild = successor;
}
successor->pLeftChild = pFindNode->pLeftChild;
}
}
pSTreeNode CBinTree::getSuccessor(pSTreeNode delNode) {
pSTreeNode pTemp = delNode->pRightChild;
pSTreeNode sucParent = NULL;
pSTreeNode successor = pTemp;
while (pTemp != NULL) {
//找到後繼節點並建立新的索引
sucParent = successor;
successor = pTemp;
pTemp = pTemp->pLeftChild;
}
if (successor != delNode->pRightChild) {
sucParent->pLeftChild = successor->pRightChild;//都是比父節點小的,左邊分支的右子樹永遠比這個小根節點小
successor->pRightChild = delNode->pRightChild;
}
return successor;
}
pSTreeNode CBinTree::GetMaxKey() {
pSTreeNode pMax = pRoot;
while (pMax->pLeftChild != NULL) {
if (pMax->pLeftChild != NULL)
pMax = pMax->pLeftChild;
}
return pMax;
}
pSTreeNode CBinTree::GetMinKey() {
pSTreeNode pMin = pRoot;
while (pMin->pRightChild != NULL) {
if (pMin->pRightChild != NULL)
pMin = pMin->pRightChild;
}
return pMin;
}
//非遞歸算法實現先序遍歷
void CBinTree::Preorder() {
if (pRoot == NULL) {
cout << "二叉樹爲空" << endl;
return;
}
stack<pSTreeNode> StackTree;
pSTreeNode pNode = pRoot;
while (!StackTree.empty() || pNode != NULL) {
while (pNode != NULL) {
cout << " " << pNode->key << " ";
StackTree.push(pNode);
pNode = pNode->pLeftChild;
}
pNode = StackTree.top(); //提出棧頂元素,但是不刪除
StackTree.pop();
pNode = pNode->pRightChild;
}
}
//非遞歸算法實現中序遍歷
void CBinTree::Inorder() {
if (pRoot == NULL) {
cout << "二叉樹爲空" << endl;
return;
}
stack<pSTreeNode> StackTree;
pSTreeNode pNode = pRoot;
while (pNode != NULL || !StackTree.empty()) {
while (pNode != NULL) {
StackTree.push(pNode);
pNode = pNode->pLeftChild;
}
pNode = StackTree.top();
StackTree.pop();
cout << " " << pNode->key << " ";
}
//cout << endl;
}
//非遞歸算法的後序遍歷實現
//將先序遍歷的結果存入
void CBinTree::Postorder() {
if (pRoot == NULL) {
cout << "二叉樹爲空" << endl;
return;
}
stack<pSTreeNode> StackTree;
stack<pSTreeNode> StackHelp;
pSTreeNode pNode = pRoot;
StackTree.push(pNode);
while (!StackTree.empty()) {
pNode = StackTree.top();
StackTree.pop();
StackHelp.push(pNode);
if (pNode->pLeftChild != NULL) {
StackTree.push(pNode->pLeftChild);
}
if (pNode->pRightChild != NULL) {
StackTree.push(pNode->pRightChild);
}
}
while (!StackHelp.empty()) {
pNode = StackHelp.top();
StackHelp.pop();
cout << " " << pNode->key << " ";
}
//cout << endl;
}
//遞歸試前序遍歷實現輸出
void CBinTree::PreorderRecursively(pSTreeNode pNode) {
if (pNode == NULL)
return;
cout << "" << pNode->key << " ";
PreorderRecursively(pNode->pLeftChild);
PreorderRecursively(pNode->pRightChild);
}
//遞歸調用實現中序遍歷
void CBinTree::InorderRecursively(pSTreeNode pNode) {
if (pNode == NULL)
return;
InorderRecursively(pNode->pLeftChild);
cout << "" << pNode->key << " ";
InorderRecursively(pNode->pRightChild);
}
//遞歸調用實現後續遍歷
void CBinTree::PostorderRecursively(pSTreeNode pNode) {
if (pNode == NULL)
return;
PostorderRecursively(pNode->pLeftChild);
PostorderRecursively(pNode->pRightChild);
cout << "" << pNode->key << " ";
}
int main() {
CBinTree* pBinTree = new CBinTree();
if (pBinTree == NULL)
return 0;
// 1.實現插入數據,存儲成搜索二叉樹的形式
pBinTree->Insert(15);
pBinTree->Insert(3);
pBinTree->Insert(20);
pBinTree->Insert(8);
pBinTree->Insert(10);
pBinTree->Insert(18);
pBinTree->Insert(6);
pBinTree->Insert(1);
pBinTree->Insert(26);
//數據已經插入進去了,現在前序遍歷一遍
pSTreeNode pRoot = pBinTree->pRoot;
cout << "遞歸先序遍歷爲:" ;
pBinTree->PreorderRecursively(pRoot);
cout << endl;
cout << "非遞歸先序遍歷爲:";
pBinTree->Preorder();
cout << endl;
cout << "遞歸中序遍歷爲:";
pBinTree->InorderRecursively(pRoot);
cout << endl;
cout << "非遞歸中序遍歷爲:";
pBinTree->Inorder();
cout << endl;
cout << "遞歸後序遍歷爲:";
pBinTree->PostorderRecursively(pRoot);
cout << endl;
cout << "非遞歸後序遍歷爲:";
pBinTree->Postorder();
cout << endl;
pSTreeNode pMaxNode = pBinTree->GetMaxKey();
pSTreeNode pMinNode = pBinTree->GetMinKey();
cout <<"輸出最大值爲:" <<pMaxNode->key;
cout << "輸出最小值爲:" << pMinNode->key;
cout << endl;
int DeleteKey = 8;
pBinTree->Delete(DeleteKey);
cout << "刪除元素" << DeleteKey << "之後的遞歸前序遍歷:";
pBinTree->PreorderRecursively(pRoot);
cout << endl;
}s
這篇程序是從大佬那學習來的,爲了加深自己理解,在此總結記錄,新手,有錯請見諒。
參考文獻
/https://www.cnblogs.com/Renyi-Fan/p/8253136.html/
/https://blog.csdn.net/lining0420/article/details/76167901/