#算法實現#—二叉搜索樹的C++實現

二叉搜索樹上的基本操作花費的時間與這棵樹的高度成正比,對於有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/

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