[C++] 二分探索ツリー (K、KV ツリー) 再帰的および非再帰的メソッドのシミュレーション実装


序文

ここに画像の説明を挿入

1.Kツリー

K モデル: K モデルはキー コードとして key のみを持ち、構造体に格納する必要があるのは Key だけであり、検索する必要がある値はキー コードです。

1. ノードの定義

template<class K>
struct BSTreeNode
{
    
    
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K&key)
		:_left(nullptr),_right(nullptr),_key(key)
	{
    
    }
};

2.コンストラクター

template<class K>
class BSTree {
    
    
	typedef BSTreeNode<K> Node;
public:
	BSTree() {
    
    
		_root = nullptr;
	}

3.コンストラクターのコピー

BSTree(const BSTree<K>& t) {
    
    
		_root = Copy(t._root);
	}
Node* Copy(Node* root) {
    
    
		if (root == nullptr) {
    
    
			return nullptr;
		}
		//递归进行拷贝
		Node* copyroot = new Node(root->_key);
		copyroot->_left = Copy(root->_left);
		copyroot->_right = Copy(root->_right);
		return copyroot;
	}

4. 代入演算子のオーバーロード

BSTree<K>& operator=(BSTree<K> t) {
    
    
//先拷贝出t,让_root指向t的位置,进行交换
//后续调用析构函数直接析构t._root
		swap(_root, t._root);
		return *this;
	}

5. デストラクター

~BSTree() {
    
    
		Destroy(_root);
	}
	void Destroy(Node*& root) {
    
    
		if (root == nullptr) {
    
    
			return;
		}
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
		root = nullptr;
	}

6. 二分探索木で検索する(find)

二分探索木の探索
a. ルートから比較検索を開始し、ルートより大きい場合は右へ、ルートより小さい場合は左へ探索します。
b. ほとんどの場合、高さを検索します。空になった場合は、まだ見つかっていないため、この値は存在しません。

1. 非再帰的

bool Find(const K& key) {
    
    
		Node* cur = _root;
		while (cur) {
    
    
			if (cur->_key < key) {
    
    
				//当前值比要查找的值小
				//到右树去查找
				cur = cur->_right;
			}
			else if (cur->_key > key) {
    
    
				cur = cur->_left;
			}
			else {
    
    
				//相等,说明找到了
				return true;
			}
		}
		return false;
	 }

2. 再帰

bool _FindR(Node*root,const K& key) {
    
    
		if (root == nullptr) {
    
    
			//说明已经找完没找到
			return false;
		}
		if (root->_key < key) {
    
    
			return _FindR(root->_right, key);
		}
		else if (root->_key > key) {
    
    
			return _FindR(root->_left, key);
		}
		else {
    
    
			return true;
		}
	}

7. 二分探索木への挿入(Insert)

. 二分探索木への挿入
具体的な挿入手順は以下の通り:
a. 木が空の場合はノードを直接追加し、ルートポインタに割り当てる
b. 木が空でない場合は挿入位置を探す二分探索木のプロパティに従って、新しいノードを挿入します。

1. 非再帰的

bool Insert(const K& key) {
    
    
		if (_root == nullptr) {
    
    
			_root = new Node(key);
			//树为空先建立结点
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) {
    
    //寻找要插入位置
			if (cur->_key < key) {
    
    
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key) {
    
    
				parent = cur;
				cur = cur->_left;
			}
			else {
    
    
				//树中已经有key值不能再插入
				return false;
			}
		}
		cur = new Node(key);//cur为要插入结点
		if (parent->_key < key) {
    
    
			//判断cur插在父结点的左数还是右树
			parent->_right = cur;
		}
		else {
    
    
			parent->_left = cur;
		}
		return true;//插入成功
	 }

2. 再帰

ここに画像の説明を挿入

bool InsertR(const K&key) {
    
    
		return _InsertR(_root, key);
	}
	bool _InsertR(Node*& root, const K& key) {
    
    
		//这里为结点指针的引用,可以不需要父结点直接修改
		if (root == nullptr) {
    
    
			root = new Node(key);
			return true;
		}
		if (root->_key < key) {
    
    
			return 	_InsertR(root->_right, key);
		}
		else if (root->_key > key) {
    
    
			return _InsertR(root->_left, key);
		}
		else {
    
    return false;}
	}

8.二分探索木の削除(Erase)

まず、要素が二分探索木に存在するかどうかを確認し、存在しない場合は要素を返します。存在しない場合、削除対象のノードは次の 4 つの状況に分けられ
ます
。 b. 削除するノードは左の子ノードのみ
c. 削除するノードは右の子ノードのみ
d. 削除するノードは左右の子ノード の
4つの状況があるようです。実際の状況 a は状況 b または c とマージできるため、実際の削除プロセスは
次のようになります:
ケース b: ノードを削除し、削除されたノードの親ノードが左側の子ノードを指すようにします。削除されたノードの直接削除
ケース c: ノードを削除し、削除されたノードの親ノードが削除されたノードの左側の子ノードを指すようにします。親ノードは、削除されたノードの右側の子ノードを指すようにします。直接削除ケース d
:右側のサブツリーで中位の最初のノード (最小のキー コード) を見つけ、そのノードが削除されるまでその値を入力し、ノード置換メソッドの削除の問題に対処します。

ここに画像の説明を挿入

ここに画像の説明を挿入

1. 非再帰的

	bool Erase(const K& key) {
    
    
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur) {
    
    //寻找要删除结点
			if (cur->_key < key) {
    
    
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key) {
    
    
				parent = cur;
				cur = cur->_left;
			}
			
			else {
    
    //找到要删除结点
				//1.   要删除节点左边为空
				if (cur->_left == nullptr) {
    
    
					if (cur == _root) {
    
    
						//要删除结点为根节点,则改变根节点位置
						_root = _root->_right;
					}
					else {
    
    
						//判断要删除结点在父结点的左子树还是右子树
						if (parent->_right == cur) {
    
    
							//在父结点右子树则让其指向删除结点的右子树
							parent->_right = cur->_right;
						}
						else {
    
    
							parent->_left = cur->_right;
						}
					}
				}
				//2.     要删除节点右边为空
				else if (cur->_right == nullptr) {
    
    
					if (cur == _root) {
    
    
						//要删除结点为根节点,则改变根节点位置
						_root = _root->_left;
					}
					else {
    
    
						//判断要删除结点在父结点的左子树还是右子树
						if (parent->_right == cur) {
    
    
							parent->_right = cur->_left;
						}
						else {
    
    
							parent->_left = cur->_left;
						}
					}
				}
				else {
    
    //3.要删除结点左右子树都不为空
					Node* parent = cur;
					Node* leftMax = cur->_left;
					//寻找可替代结点,替代过好要满足二叉搜索树的性质
					//要删除结点左子树的最大值,或者右子树的最小值
					while (leftMax->_right) {
    
    
						//寻找左子树的最大值
						parent = leftMax;
						leftMax = leftMax->_right;
					}
					swap(leftMax->_key, cur->_key);
					//替代结点与被删除结点的值交换
					//这样leftMax就为要被删除的结点
					if (parent->_left == leftMax) {
    
    
						//判断leftMax在父结点的左子树还是右子树
						parent->_left = leftMax->_left;
					}
					else {
    
    
						parent->_right = leftMax->_left;
					}//改变指向
					cur = leftMax;
				}
				delete cur;
				return true;
			}	
		}
		return false;
 }

2. 再帰

bool EraseR(const K&key) {
    
    
		return _EraseR(_root, key);
	}

bool _EraseR(Node*& root, const K& key) {
    
    
		//这里为结点指针的引用,可以不需要父结点直接修改
		if (root == nullptr) {
    
    
			return false;
		}
		if (root->_key < key) {
    
    
			return _Erase(root->_right, key);
		}
		else if (root->_key > key) {
    
    
			return _Erase(root->_left, key);
		}//寻找要删除结点
		else {
    
    
			Node* del = root;
			//1. 要删除节点左边为空
			if (root->_left == nullptr) {
    
    
				root = root->_right;
			}
			//2. 要删除节点右边为空
			else if (root->_right == nullptr) {
    
    
				root = root->_left;
			}
			else {
    
    
				//3.要删除结点左右都不为空
				Node* leftMax = root->_left;
				while (leftMax->_right) {
    
    
					//寻找替代结点
					leftMax = leftMax->_right;
				}
				swap(root->_key, leftMax->_key);
				//交换值后,要删除的结点为leftMax,其在root的左子树
				return _Erase(root->_left, key);
			}
			delete del;
			return true;
		}
	}

9. インオーダートラバーサル(InOrder)

void InOrder() {
    
    
		_InOrder(_root);
		cout << endl;
	}
void _InOrder(Node* root) {
    
    
		if (root == nullptr) {
    
    
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

2.KVツリー

KV モデル: 各キー key には対応する値 Value、つまり <Key, Value> のキーと値のペアがあります。

namespace key_value {
    
    
	template<class K, class V>
	struct BSTreeNode
	{
    
    
		BSTreeNode<K, V>* _left;
		BSTreeNode<K, V>* _right;
		K _key;
		V _value;

		BSTreeNode(const K& key, const V& value)
			:_left(nullptr), _right(nullptr), _key(key), _value(value)
		{
    
    }
	};

	template<class K, class V>
	class BSTree {
    
    
		typedef BSTreeNode<K, V> Node;
	public:
		BSTree() {
    
    
			_root = nullptr;
		}

		BSTree(const BSTree<K, V>& t) {
    
    
			_root = Copy(t._root);
		}

		BSTree<K, V>& operator=(BSTree<K, V> t) {
    
    
			swap(_root, t._root);
			return *this;
		}

		~BSTree() {
    
    
			Destroy(_root);
		}



		bool InsertR(const K& key, const V& value) {
    
    
			return _InsertR(_root, key, value);
		}

		bool EraseR(const K& key) {
    
    
			return _Erase(_root, key);
		}

		Node* FindR(const K& key) {
    
    
			return 	_FindR(_root, key);
		}
		void InOrder() {
    
    
			_InOrder(_root);
			cout << endl;
		}



	private:
		Node* Copy(Node* root) {
    
    
			if (root == nullptr) {
    
    
				return nullptr;
			}
			Node* copyroot = new Node(root->_key);
			copyroot->_left = Copy(root->_left);
			copyroot->_right = Copy(root->_right);
			return copyroot;
		}

		void Destroy(Node*& root) {
    
    
			if (root == nullptr) {
    
    
				return;
			}
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
			root = nullptr;
		}

		Node* _FindR(Node* root, const K& key) {
    
    
			if (root == nullptr) {
    
    
				return nullptr;
			}
			if (root->_key < key) {
    
    
				return _FindR(root->_right, key);
			}
			else if (root->_key > key) {
    
    
				return _FindR(root->_left, key);
			}
			else {
    
    
				return root;
			}

		}

		bool _InsertR(Node*& root, const K& key, const V& value) {
    
    
			if (root == nullptr) {
    
    
				root = new Node(key, value);
				return true;
			}
			if (root->_key < key) {
    
    
				return _InsertR(root->_right, key, value);
			}
			else if (root->_key > key) {
    
    
				return _InsertR(root->_left, key, value);
			}
			else {
    
    
				return false;
			}
		}

		bool _Erase(Node*& root, const K& key) {
    
    
			if (root == nullptr) {
    
    
				return false;
			}
			if (root->_key < key) {
    
    
				return _Erase(root->_right, key);
			}
			else if (root->_key > key) {
    
    
				return _Erase(root->_left, key);
			}
			else {
    
    
				Node* del = root;
				if (root->_left == nullptr) {
    
    
					root = root->_right;
				}
				else if (root->_right == nullptr) {
    
    
					root = root->_left;
				}
				else {
    
    
					Node* leftMax = root->_left;
					while (leftMax->_right) {
    
    
						leftMax = leftMax->_right;
					}
					swap(root->_key, leftMax->_key);
					return _Erase(root->_left, key);
				}
				delete del;
				return true;
			}
		}


		void _InOrder(Node* root)
		{
    
    
			if (root == NULL){
    
    return;}
			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}

	private:
		Node* _root;
	};
}

二分探索木のパフォーマンス

ここに画像の説明を挿入

最高の検索高さの O(N) 倍

おすすめ

転載: blog.csdn.net/m0_74774759/article/details/132210067