C ++功実務マニュアルXXVII(二分探索木と解釈の原則の簡単な実装)

バイナリ検索ツリーの説明:

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树

バイナリ検索ツリーの操作

1.二叉搜索树的查找: 
  若根结点不为空:
  如果根节点的val == 查找的val直接返回true
  如果根节点的val >  查找的val那就去根节点的右子树去寻找
  如果根节点的val <  查找的val那就去根节点的左子树去寻找
  否则返回false
2.二叉搜索树的插入
二叉树的插入位置只有三种可能
  1》左子树为空的结点
  2》右子树为空的结点
  3》叶子结点
插入的结点一定不会是左右孩子都在的结点,这个是由二叉树的性质来决定的,要是还是没有办法理解的话可以画个图看一看

a.删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
b.删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
c.在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题

削除二分探索木、イラスト

ここに画像を挿入説明
ここに画像を挿入説明

template<class T>
struct BSTNode
{
	BSTNode(T _value = T())
	:left(nullptr), right(nullptr)
	, value(_value)
	{}

	BSTNode<T>* left;
	BSTNode<T>* right;
	T value;
};
template<class T>
class BSTree
{
	typedef BSTNode<T> Node;
public:
	BSTree()
		:root(nullptr)
	{}
	~BSTree()
	{
		_destory(root);
	}
	BSTree(const BSTree& tree)
	{
		this->root = _copy(tree.root);
	}
	Node* _copy(Node* _root)
	{
		if (_root == nullptr)
			return nullptr;
		Node* temp = new Node(_root->value);
		temp->left  = _copy(_root->left);
		temp->right = _copy(_root->right);
		return temp;
	}
	BSTree& operator=(const BSTree& tree)
	{
		if (this != &tree)
		{
			_destory(this->root);
			this->root =  _copy(tree.root);
		}
		return *this;
	}
	bool Insert(const T& val)
	{
		if (root == nullptr)
		{
			root = new Node(val);
			return true;
		}
		else
		{
			Node* pCur = root;  //用来查找结点
			Node* pParent = nullptr;  //用来记录当前正在查找结点的双亲结点
			while (pCur)
			{
				pParent = pCur;
				if (pCur->value > val)
					pCur = pCur->left;
				else if (pCur->value < val)
					pCur = pCur->right;
				else
					return false;  //默认是不能插入重复的元素
			}
			pCur = new Node(val);
			if (pParent->value > val)
				pParent->left = pCur;
			else
				pParent->right = pCur;
		}
		return true;
	}
	Node* Find(const T& val)
	{
		if (root == nullptr)
			return nullptr;
		Node* pCur = root;
		while (pCur)
		{
			if (pCur->value > val)
				pCur = pCur->left;
			else if (pCur->value < val)
				pCur = pCur->right;
			else
				return pCur;
		}
		return nullptr;
	}
	bool Erase(const T& val)
	{
		if (root == nullptr)
			return false;
		//遍历整个二叉搜索树,找val
		Node* pCur = root;
		Node* pParent = nullptr;
		while (pCur)
		{
			
			if (pCur->value == val)
				break;
			else if (pCur->value > val)
			{
				pParent = pCur;     //这一快只能这么写,之前我把pParent = pCur放在外面,但是但是错了,这里的逻辑就是这样,不理解的话可以画图
				pCur = pCur->left;
			}
			else
			{
				pParent = pCur;
				pCur = pCur->right;
			}
		}
		//不存在val
		if (pCur == nullptr)
			return false;
		//1.待删除结点的左孩子为空
		//2.待删除结点的右孩子为空
		//3.待删除结点的左右孩子都不为空
		//4.待删除结点的左右孩子都为空
		if (pCur->left == nullptr && pCur->right == nullptr)  //左右孩子都为空
		{
			if (pParent->left == pCur)
				pParent->left = nullptr;
			else
				pParent->right = nullptr;
			delete pCur;

		}
		else if (pCur->left == nullptr)  //待删除的结点是左孩子为空的情况
		{
			if (pCur->value == val)  //刚好根节点就是要删除的结点
			{                        
				root = pCur->right;   //因为没有左孩子所以树的头指针,直接指向根节点的右孩子就可以
			}
			else  //删除的结点不是头结点
			{
				//判断删除结点是双亲结点的左孩子还是右孩子
				if (pParent->left == pCur)
					pParent->left = pCur->right;  //如果是删除结点是双亲结点的左孩子,那就让删除节点的双亲结点的左孩子指向删除结点的右孩子
				else
					pParent->right = pCur->right; //反之相反
			}
			delete pCur;
		}
		else if (pCur->right == nullptr)    //待删除结点的右孩子为空
		{
			if (pCur->value == val)  
			{
				root = pCur->left;   
			}
			else  //删除的结点不是头结点
			{
				//判断删除结点是双亲结点的左孩子还是右孩子
				if (pParent->left == pCur)
					pParent->left = pCur->left;  //如果是删除结点是双亲结点的左孩子,那就让删除节点的双亲结点的左孩子指向删除结点的右孩子
				else
					pParent->right = pCur->left; //反之相反
			}
			delete pCur;
		}
		else  //待删除结点的左右节点都不为空的时候
		{
			if (pCur->left != nullptr && pCur->right != nullptr)
			{
				//去找待删除结点的右孩子中的最小值,或者左孩子中的最大值,其实都一样,这里我们就使用右孩子中的最小值
				Node* p = pCur->right;
				pParent = pCur;  //标记双亲结点,这里的标记主要是为了防止没有进入下面while循环的情况
				while (p->left)
				{
					pParent = p; //标记双亲结点
					p = p->left;
				}
				pCur->value = p->value;//替换
				//下面这两种情况比较特殊需要拿出来讨论
				if (p == pParent->left)
				{
					pParent->left = p->right;
				}
				else
				{
					pParent->right = p->right;
				}
				delete p;
			}
			return true;
		}
		return false;
	}
	void Inorder()
	{
		_Inorder(this->root);
	}
private:
	void _Inorder(Node* tree)
	{
		if (tree == nullptr)
			return;
		_Inorder(tree->left);
		cout << tree->value << " ";
		_Inorder(tree->right);
	}
	void _destory(Node*& tree) //为了在内部改变外部的指针,这里使用的就是指针的引用
	{
		if (tree)
		{
			_destory(tree->left);
			_destory(tree->right);
			delete tree;
			tree = nullptr;
		}
	}
private:
	Node* root;
};
int main()
{

	BSTree<int> BST;
	BST.Insert(1);
	BST.Insert(3);
	BST.Insert(7);
	BST.Insert(5);
	BST.Insert(4);
	BST.Insert(6);
	BST.Insert(2);
	BST.Inorder();
	BST.Erase(1);
	cout << endl;
	BST.Inorder();
	cout << endl;
	BST.Erase(2);
	BST.Inorder();
	cout << endl;
	BST.Erase(5);
	BST.Inorder();
	cout << endl;
	BST.Erase(4);
	BST.Inorder();
	cout << endl;
	cout << BST.Find(7)<< endl;
	BSTree<int> BST2;
	BST2 = BST;
	BST2.Inorder();
	BSTree<int> BST3(BST2);
	BST3.Inorder();

	return 0;
}

まず、このコードの顔に私は問題の分析を登場:
ある削除の場合におけるノード、左と右のサブツリーが空ではありませんされた場合は、まず、
ここに画像を挿入説明
二つの絵を描くために必要な
ここに画像を挿入説明
ここに画像を挿入説明
両方のケースでは、与えられています。

これは、次の2つのコードが続き
ここに画像を挿入説明
、削除ノードは、コード、挿入および欠失であり、必要に挿入、ノードを見つけることに関するこれら2つのコード、一つのコード挿入ノード上適切なノードを見つける必要があり、左右に適切なノードノード
プロセスの右のノードを見つけるために、現在のトラバーサルを記録するための時間を必要とする、親ノードノードが、異なる保存親ノード方法。
最後pCureの検索が空ノードであり、本当にしたい挿入ノードでは、左または右pCureのpParentの親ノードのを挿入することです。
ノード削除時に親ノード現在トラバースノードを保存する必要が、ノードを削除するときと同じでない場合、ツリー全体を横断する、ツリー全体をトラバースするために同じ必要性をで、pCureはもはや空ではありませんこれら2つのコードが同じではないので、ノードが、本当のポイントは、ノードの親ノードを削除することを削除する必要があり、pParent。

ここに画像を挿入説明
ここに画像を挿入説明
控除コードは何が問題です。

2つの数のパフォーマンス分析フォーク

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log n;
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:n / 2;

質問:シングル木の枝に縮退している場合、パフォーマンスのバイナリ検索ツリーが失われました。これを向上させることができ、キーコードを挿入するどのような順序に関係なく、あなたは最高のバイナリ検索ツリーのパフォーマンスをすることができますか?
AVL(バランスのバイナリ検索ツリー)

公開された230元の記事 ウォン称賛28 ビュー9309

おすすめ

転載: blog.csdn.net/weixin_43767691/article/details/103734579