[C++] Simulieren Sie die Implementierung eines binären Suchbaums (mit Quellcode und Testfällen)

binärer Suchbaum

I. Einleitung

2. Simulationsimplementierung

1. Erstellen Sie einen einzelnen Knoten des Baums

2. Konzept des binären Suchbaums

3.Konstruktor und Destruktor

4. Aufgaben- und Kopieraufbau

5. Implementieren Sie das Einfügen

6. Implementieren Sie die Löschung

7. Implementieren Sie die Suche

8. Implementieren Sie die Durchquerung

3. Quellcode und einige Testfälle


I. Einleitung

        Binäre Suchbäume unterscheiden sich von gewöhnlichen Binärbäumen: Zusätzlich zur schnellen Einfügefunktion einer verknüpften Liste bietet sie auch die Möglichkeit, so schnell wie ein Array zu suchen. Dieser Artikel simuliert hauptsächlich die Funktionen zum Hinzufügen, Löschen, Suchen und Durchlaufen eines binären Suchbaums. Die Compilerumgebung ist VS2019.

2. Simulationsimplementierung

1. Erstellen Sie einen einzelnen Knoten des Baums

        Der einzelne Knoten eines binären Suchbaums ist derselbe wie ein gewöhnlicher Binärbaum. Er verwendet eine Struktur zum Speichern von Inhalten und verwendet linke und rechte Zeiger, um auf den linken und rechten Teilbaum zu zeigen. Der Baum muss nur den anfänglichen Wurzelknoten speichern :

// 树节点
// K表示数据存储类型,V表示对类容的进一步扩展
// 扩展可以是注释,也可以是其他内容
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:
private:
    Node* _root;
}

2. Konzept des binären Suchbaums

Es gibt zwei Unterschiede zwischen binären Suchbäumen und gewöhnlichen Binärbäumen:

(1) Alle Wurzelknoten des binären Suchbaums sind größer als die in seinem linken Teilbaum und seinen Teilbäumen gespeicherten Werte und kleiner als die in seinem eigenen rechten Teilbaum und seinen Teilbäumen gespeicherten Werte;

(2) Knoten mit demselben Wert können im binären Suchbaum nicht vorhanden sein.

3.Konstruktor und Destruktor

        Da es im anfänglichen Speicher des Baums nur einen Wurzelknoten gibt, kann der Wurzelknoten im Konstruktor leer bleiben; während der Destruktor des Binärbaums eine Tiefendurchquerung erfordert, da auf den Wurzelknoten nach der Verwendung von pre nicht erneut zugegriffen werden kann -Reihenfolge und in der Reihenfolge löschen. Wenn es den Teilbaum erreicht, führt dies zu schwerwiegenden Datenverlusten.

// 初始化
BSTree()
	:_root(nullptr)
{}

// 析构
~BSTree()
{
	Destory(_root);
}

void Destory(Node*& del)
{
	if (del == nullptr)
		return;

	Destory(del->left);
	Destory(del->right);

	delete del;
	del = nullptr;
}

4. Aufgaben- und Kopieraufbau

        Die Zuweisung zu einem Binärbaum ähnelt der Kopierkonstruktion oder ist genau dasselbe. Sowohl die Zuweisung als auch die Kopierkonstruktion erzeugen eine Zeichenfolge desselben Baums für neu.

        Es gibt jedoch zwei Implementierungsmethoden: Die eine ist die traditionelle Schreibweise, bei der eine Kopierfunktion erstellt wird, der Baum, auf den *this zeigt, und der Baum, auf den die Parameter verweisen, übergeben werden und diese einzeln durch Durchquerung der Breite zuerst kopiert werden. Der andere ist einfach und grob und wird übergeben. Der Parameter ist der Baum, der durch Kopieren der tatsächlichen Parameter erstellt wird. Tauschen Sie einfach den Wurzelknoten von *this mit dem erstellten Baum aus. Wenn die Funktion endet, wird der Stapelrahmen zerstört und die formalen Parameter werden automatisch aufgerufen Der Destruktor. Zu diesem Zeitpunkt werden die formalen Parameter ausgetauscht. * Ignorieren Sie einfach den Wurzelknoten des Baums, auf den dieser verweist. Dies gilt natürlich nur für die Zuweisung, da der Kopierkonstruktor aufgerufen werden muss.

        Die spezifische Implementierung ist wie folgt:

// 拷贝构造
BSTree(const BSTree<K, V>& t)
{
	_root = Copy(t._root);
}

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

5. Implementieren Sie das Einfügen

        Lassen Sie uns zunächst die Anforderungen eines binären Suchbaums analysieren, der erfordert, dass sein linker Teilbaum kleiner als der Wurzelknoten und sein rechter Teilbaum größer als der Wurzelknoten ist. Daher ist jeder Vergleich ein Größenscreening. Nach dem Vergleich der Größen Geben Sie das Verhältnis ein. Der linke Baum mit einem kleineren Wurzelknoten oder der Eintritt in einen größeren rechten Baum verengt den Bereich ebenfalls kontinuierlich, bis der Größenbereich nicht mehr reduziert werden kann. Zu diesem Zeitpunkt kann der Vergleich nicht fortgesetzt werden, d. h. der Teilbaum Der nach dem Vergleich eingegebene Wurzelknoten ist leer. Fügen Sie zu diesem Zeitpunkt einfach einen leeren Knoten ein.

// 插入
bool Insert(const K& key, const V& value)
{
	// 排除空
	if (_root == nullptr)
	{
		Node* New = new Node(key, value);
		_root = New;

		return true;
	}

	// 往下查找
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (key > cur->_key)
		{
			parent = cur;
			cur = cur->right;
		}
		else if (key < cur->_key)
		{
			parent = cur;
			cur = cur->left;
		}
		else
			return false;
	}

	// 放置新节点
	cur = new Node(key, value);
	if (key > parent->_key)
	{
		parent->right = cur;
	}
	else
	{
		parent->left = cur;
	}

	return true;
}

6. Implementieren Sie die Löschung

        Das Löschen eines binären Suchbaums ist am problematischsten. Lassen Sie uns es Stück für Stück analysieren. Es gibt vier Fälle gelöschter Knoten: Sowohl der linke als auch der rechte Teilbaum sind leer; nur der linke Baum ist leer; nur der rechte Baum ist leer; beide Die linken und rechten Teilbäume sind leer. Nicht null.

        Wenn sowohl der linke als auch der rechte Teilbaum leer sind, zeigen Sie einfach direkt auf den übergeordneten Knoten des gelöschten Knotens, um ihn zu leeren.

        Wenn nur der linke Teilbaum oder der rechte Teilbaum leer ist, verweisen Sie den übergeordneten Knoten auf den linken Teilbaum oder den rechten Teilbaum des gelöschten Knotens – diese Implementierung wird als Hosting-Methode bezeichnet.

        Wenn der linke und der rechte Baum zu diesem Zeitpunkt nicht leer sind, auf welchen Knoten sollten wir den Vaterknoten zu diesem Zeitpunkt zeigen lassen? Wir haben zwei Möglichkeiten: Nehmen Sie den Maximalwert des linken Baums des gelöschten Knotens oder nehmen Sie den Minimalwert des rechten Baumknotens. Aber sie alle haben ein Problem: Was soll nach dem Verschieben des Knotens nach oben mit seinem Unterbaum geschehen? Hier nehmen wir als Beispiel den Maximalwert des linken Baums. Wenn wir den Maximalwert des linken Baums eines gelöschten Knotens ermitteln, weist dieser die Eigenschaft auf, dass es keinen rechten Teilbaum, sondern höchstens den linken Teilbaum gibt. Wir brauchen nur Übergeben Sie den linken Baum dieses Knotens an diesen Knoten. Der übergeordnete Knoten kann gehostet werden. (Es ist fast dasselbe, wenn man den Minimalwert des rechten Baums nimmt)

// 删除
bool Erase(const K& key)
{
	Node* parent = nullptr;

	Node* cur = _root;
	while (cur)
	{
		if (key > cur->_key)
		{
			parent = cur;
			cur = cur->right;
		}
		else if (key < cur->_key)
		{
			parent = cur;
			cur = cur->left;
		}
		// 找到要删除的节点时
		else
		{
			// 左右节点为空:托管
			if (cur->left == nullptr)
			{
				// 判断根节点
				if (cur == nullptr)
				{
					_root = cur->right;
				}
				// 此时不知道cur为左节点还是右节点
				if (parent->right = cur)
				{
					parent->right = cur->right;
				}
				else
				{
					parent->left = cur->right;
				}
			}
			else if (cur->right == nullptr)
			{
				// 判断根节点
				if (cur == nullptr)
				{
					_root = cur->left;
				}
				// 此时不知道cur为左节点还是右节点
				if (parent->right = cur)
				{
					parent->right = cur->left;
				}
				else
				{
					parent->left = cur->left;
				}
			}
			// 左右节点都不为空:替代法
			else
			{
				// 获取左子树最大值
				parent = cur;
				Node* LeftMax = cur->left;
				while (LeftMax->right)
				{
					parent = LeftMax;
					LeftMax = LeftMax->right;
				}

				// 交换节点内容
				std::swap(cur->_key, LeftMax->_key);
				std::swap(cur->_value, LeftMax->_value);

				// 处理LeftMax的左子树
				if (parent->left == LeftMax)
				{
					parent->left = LeftMax->left;
				}
				else
				{
					parent->right = LeftMax->left;
				}

				cur = LeftMax;
			}

			delete cur;
			cur = nullptr;

			return true;
		}
	}
	return false;
}

7. Implementieren Sie die Suche

        Zur Suche gibt es nichts zu sagen. Wir haben es bereits implementiert. Hier setzen wir den Rückgabewert auf den gefundenen Knotenzeiger.

// 查找
Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (key > cur->_key)
		{
			cur = cur->right;
		}
		else if (key < cur->_key)
		{
			cur = cur->left;
		}
		else
			return cur;
	}
    
    // 没找到
	return nullptr;
}

8. Implementieren Sie die Durchquerung

        Das Durchlaufen eines binären Suchbaums ist sehr einfach und hängt von seinen Eigenschaften ab. Verwenden Sie einfach das Durchlaufen in der Reihenfolge. Der Code ist wie folgt implementiert:

// 中序遍历
void Inorder()
{
	_Inorder(_root);
	cout << endl;
}

void _Inorder(Node* node)
{
	if (node == nullptr)
		return;
	_Inorder(node->left);
	cout << node->_key << ":" << node->_value << " ";
	_Inorder(node->right);
}

3. Quellcode und einige Testfälle

#pragma once

// 树节点
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)
	{}
};

// 搜索二叉树
// 1.所有根节点比左树大,比右树小
// 2.不能有相同的值节点
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()
	{
		Destory(_root);
	}

	// 插入
	bool Insert(const K& key, const V& value)
	{
		// 排除空
		if (_root == nullptr)
		{
			Node* New = new Node(key, value);
			_root = New;

			return true;
		}

		// 往下查找
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->left;
			}
			else
				return false;
		}

		// 放置新节点
		cur = new Node(key, value);
		if (key > parent->_key)
		{
			parent->right = cur;
		}
		else
		{
			parent->left = cur;
		}

		return true;
	}

	// 查找
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				cur = cur->right;
			}
			else if (key < cur->_key)
			{
				cur = cur->left;
			}
			else
				return cur;
		}

		return nullptr;
	}

	// 删除
	bool Erase(const K& key)
	{
		Node* parent = nullptr;

		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->left;
			}
			// 找到要删除的节点
			else
			{
				// 左右节点为空:托管
				if (cur->left == nullptr)
				{
					// 判断根节点
					if (cur == nullptr)
					{
						_root = cur->right;
					}
					// 此时不知道cur为左节点还是右节点
					if (parent->right = cur)
					{
						parent->right = cur->right;
					}
					else
					{
						parent->left = cur->right;
					}
				}
				else if (cur->right == nullptr)
				{
					// 判断根节点
					if (cur == nullptr)
					{
						_root = cur->left;
					}
					// 此时不知道cur为左节点还是右节点
					if (parent->right = cur)
					{
						parent->right = cur->left;
					}
					else
					{
						parent->left = cur->left;
					}
				}
				// 左右节点都不为空:替代法
				else
				{
					// 获取左子树最大值
					parent = cur;
					Node* LeftMax = cur->left;
					while (LeftMax->right)
					{
						parent = LeftMax;
						LeftMax = LeftMax->right;
					}

					// 交换节点内容
					std::swap(cur->_key, LeftMax->_key);
					std::swap(cur->_value, LeftMax->_value);

					// 处理LeftMax的左子树
					if (parent->left == LeftMax)
					{
						parent->left = LeftMax->left;
					}
					else
					{
						parent->right = LeftMax->left;
					}

					cur = LeftMax;
				}

				delete cur;
				cur = nullptr;

				return true;
			}
		}
		return false;
	}

	// 中序遍历
	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}

private:
	Node* _root;

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		Node* NewRoot = new Node(root);
		NewRoot->_key = root->_key;

		NewRoot->left = Copy(root->left);
		NewRoot->right = Copy(root->right);
		return NewRoot;
	}

	void Destory(Node*& del)
	{
		if (del == nullptr)
			return;

		Destory(del->left);
		Destory(del->right);

		delete del;
		del = nullptr;
	}

	void _Inorder(Node* node)
	{
		if (node == nullptr)
			return;
		_Inorder(node->left);
		cout << node->_key << ":" << node->_value << " ";
		_Inorder(node->right);
	}#pragma once

// 树节点
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)
	{}
};

// 搜索二叉树
// 1.所有根节点比左树大,比右树小
// 2.不能有相同的值节点
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()
	{
		Destory(_root);
	}

	// 插入
	bool Insert(const K& key, const V& value)
	{
		// 排除空
		if (_root == nullptr)
		{
			Node* New = new Node(key, value);
			_root = New;

			return true;
		}

		// 往下查找
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->left;
			}
			else
				return false;
		}

		// 放置新节点
		cur = new Node(key, value);
		if (key > parent->_key)
		{
			parent->right = cur;
		}
		else
		{
			parent->left = cur;
		}

		return true;
	}

	// 查找
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				cur = cur->right;
			}
			else if (key < cur->_key)
			{
				cur = cur->left;
			}
			else
				return cur;
		}

		return nullptr;
	}

	// 删除
	bool Erase(const K& key)
	{
		Node* parent = nullptr;

		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->left;
			}
			// 找到要删除的节点
			else
			{
				// 左右节点为空:托管
				if (cur->left == nullptr)
				{
					// 判断根节点
					if (cur == nullptr)
					{
						_root = cur->right;
					}
					// 此时不知道cur为左节点还是右节点
					if (parent->right = cur)
					{
						parent->right = cur->right;
					}
					else
					{
						parent->left = cur->right;
					}
				}
				else if (cur->right == nullptr)
				{
					// 判断根节点
					if (cur == nullptr)
					{
						_root = cur->left;
					}
					// 此时不知道cur为左节点还是右节点
					if (parent->right = cur)
					{
						parent->right = cur->left;
					}
					else
					{
						parent->left = cur->left;
					}
				}
				// 左右节点都不为空:替代法
				else
				{
					// 获取左子树最大值
					parent = cur;
					Node* LeftMax = cur->left;
					while (LeftMax->right)
					{
						parent = LeftMax;
						LeftMax = LeftMax->right;
					}

					// 交换节点内容
					std::swap(cur->_key, LeftMax->_key);
					std::swap(cur->_value, LeftMax->_value);

					// 处理LeftMax的左子树
					if (parent->left == LeftMax)
					{
						parent->left = LeftMax->left;
					}
					else
					{
						parent->right = LeftMax->left;
					}

					cur = LeftMax;
				}

				delete cur;
				cur = nullptr;

				return true;
			}
		}
		return false;
	}

	// 中序遍历
	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}

private:
	Node* _root;

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		Node* NewRoot = new Node(root);
		NewRoot->_key = root->_key;

		NewRoot->left = Copy(root->left);
		NewRoot->right = Copy(root->right);
		return NewRoot;
	}

	void Destory(Node*& del)
	{
		if (del == nullptr)
			return;

		Destory(del->left);
		Destory(del->right);

		delete del;
		del = nullptr;
	}

	void _Inorder(Node* node)
	{
		if (node == nullptr)
			return;
		_Inorder(node->left);
		cout << node->_key << ":" << node->_value << " ";
		_Inorder(node->right);
	}

public:
	void TestBSTree1()
	{
		BSTree<string, string> dict;
		dict.Insert("insert", "插入");
		dict.Insert("sort", "排序");
		dict.Insert("right", "右边");
		dict.Insert("date", "日期");

		string str;
		while (cin >> str)
		{
			BSTreeNode<string, string>* ret = dict.Find(str);
			if (ret)
			{
				cout << ret->_value << endl;
			}
			else
			{
				cout << "无此单词" << endl;
			}
		}
	}

	void TestBSTree2()
	{
		// 统计水果出现的次数
		string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		BSTree<string, int> countTree;
		for (auto& str : arr)
		{
			auto ret = countTree.Find(str);
			if (ret == nullptr)
			{
				countTree.Insert(str, 1);
			}
			else
			{
				ret->_value++;
			}
		}

		countTree.Inorder();
	}
};
};

おすすめ

転載: blog.csdn.net/qq_74641564/article/details/132069880