C++ Binary Search Tree (BST)-Implementierung (nicht rekursive Version und rekursive Version) und Anwendung


Binäre Suchbäume können entweder als aufsteigende oder absteigende Version implementiert werden.
In diesem Artikel wird die aufsteigende Version implementiert.

1. Eigenschaften des binären Suchbaums

Der binäre Suchbaum ist eine besondere Art von Binärbaum.
Seine Eigenschaften sind:

1. Alle Knoten des linken Teilbaums sind kleiner als der Wert des Wurzelknotens
2. Alle Knoten des rechten Teilbaums sind größer als der Wert des Wurzelknotens
3. Der linke und der rechte Teilbaum sind beide binäre Suchbäume
4. Die in -Order-Durchlaufsequenz ist geordnet 5.
Im Allgemeinen erlauben binäre Suchbäume keine doppelten Werte.

Natürlich ist der binäre Suchbaum standardmäßig in aufsteigender Reihenfolge, aber er kann auch in absteigender Reihenfolge implementiert werden.
Ändern Sie einfach das 1. und 2. Element.
Das erste Element wird so geändert, dass die Knoten des linken Teilbaums größer als der Wurzelknoten sind und das
zweite Element. Die Knoten, die in den rechten Teilbaum geändert werden, müssen kleiner als der Wurzelknoten sein.
Der zu diesem Zeitpunkt implementierte binäre Suchbaum ist in absteigender Reihenfolge.
Fügen Sie hier eine Bildbeschreibung ein
Beispiel: Dieser Baum ist ein binärer Suchbaum.

2. Der allgemeine Rahmen, den wir erreichen wollen

#pragma once
#include <iostream>
using namespace std;
//BST排升序:左孩子小于我,  右孩子大于我
//排降序:   左孩子大于我,  右孩子小于我

//节点的结构体
template <class K>
struct BSTreeNode
{
    
    
	BSTreeNode<K>* _left = nullptr;
	BSTreeNode<K>* _right = nullptr;
	K _key;
	BSTreeNode(const K& key)
		:_key(key)
	{
    
    }
};

template<class K>
class BSTree
{
    
    
	typedef BSTreeNode<K> Node;
public:
	//非递归实现insert.find,erase
	bool Insert(const K& key);

	Node* Find(const K& key);

	bool Erase(const K& key);

	//析构函数  后续遍历析构
	~BSTree();
	
	//C++11新语法
	BSTree() = default;//强制生成默认构造
	
	//拷贝构造
	//先序遍历构造
	BSTree(const BSTree<K>& bst);

	//赋值运算符重载:现代版本
	BSTree<K>& operator=(BSTree<K> bst);

	void InOrder()
	{
    
    
		_InOrder(_root);
	}

	//递归实现insert.find,erase
	Node* FindR(const K& key)
	{
    
    
		return _FindR(_root,key);
	}

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

	bool EraseR(const K& key)
	{
    
    
		return _EraseR(_root, key);
	}
	
private:
	//拷贝构造函数的子函数
	Node* _Copy(const Node* root);
	//析构函数的子函数
	void _Destroy(Node*& root);
	//中序遍历的子函数
	void _InOrder(Node* root);
	//find的子函数
	Node* _FindR(Node* root, const K& key);
	//insert的子函数
	bool _InsertR(Node*& root, const K& key);
	//erase的子函数
	bool _EraseR(Node*& root, const K& key);
	
	//给根节点_root缺省值nullptr
	Node* _root = nullptr;
};

Dies ist die Version des Schlüsselmodells. Schließlich müssen wir eine Schlüsselwertversion ändern.

template <class K>

Der Grund, warum die Vorlage hier an K übergeben wird, ist, dass sie nur zum K-Modell passt, sodass
das R hier in T nicht verwendet wird: Rekursion (Rekursion auf Englisch)

//给根节点_root缺省值nullptr
Node* _root = nullptr;

Hier wird der Standardwert nullptr direkt an den Wurzelknoten _root übergeben. Der vom Compiler standardmäßig generierte Konstruktor verwendet diesen Standardwert.
Hier ist ein zusätzlicher Punkt:

//C++11新语法:给default这个关键字增加了一个含义
BSTree() = default;//强制生成默认构造

3.Einfügen

Nachdem wir die Eigenschaften des binären Suchbaums kennengelernt haben, schauen wir uns an, wie man einen Wert einfügt.
Fügen Sie hier eine Bildbeschreibung ein
Hinweis:
1. Beim Durchlaufen, um die einzufügende Position zu finden, muss der übergeordnete Knoten aufgezeichnet werden, sonst kann er nicht eingefügt werden.
2. Beim Einfügen Am Ende müssen Sie beurteilen, ob der Wert mit dem übergeordneten Knoten übereinstimmt. Das Größenverhältnis der Knoten, damit Sie wissen, ob Sie ihn links oder rechts einfügen müssen

So können wir Code wie diesen schreiben

插入成功,返回true
插入失败(说明插入了重复值),返回false
bool Insert(const K& key)
{
    
    
	if (_root == nullptr)
	{
    
    
		_root = new Node(key);
		return true;
	}
	Node* cur = _root;
	Node* parent = _root;//记录父亲,因为要能够插入
	while (cur)
	{
    
    
		//要插入的值小于父亲,往左找
		if (cur->_key > key)
		{
    
    
			parent = cur;
			cur = cur->_left;
		}
		//要插入的值大于父亲,往右找
		else if (cur->_key < key)
		{
    
    
			parent = cur;
			cur = cur->_right;
		}
		//出现了重复元素,BST搜索二叉树不允许出现重复值,因此不允许插入,返回false
		else
		{
    
    
			return false;
		}
	}
	//此时cur为空,说明找到了空位置 在此位置插入value
	cur = new Node(key);
	//要插入的元素小于父亲,插入到左侧
	if (parent->_key > key)
	{
    
    
		parent->_left = cur;
	}
	//要插入的元素大于父亲,插入到右侧
	else
	{
    
    
		parent->_right = cur;
	}
	//插入成功
	return true;
}

4.InOrdnung und Suche

1.InOrder

Die Inorder-Durchquerung von InOrder ist genau die gleiche wie die Inorder-Durchquerung eines gewöhnlichen Binärbaums. Der
einzige Unterschied besteht darin, dass zur Implementierung Rekursion verwendet wird und _root eine private Variable ist, auf die die Außenwelt nicht zugreifen kann. Daher ein Sub -Funktion ist gekapselt, damit die Unterfunktion die Aufgabe rekursiv abschließen kann. Die Hauptfunktion kann von der Außenwelt aufgerufen werden und die Unterfunktionen müssen der Außenwelt nicht bereitgestellt werden
. Ebenso die späteren rekursiven Versionen Die Funktionen „Einfügen“, „Erase“ und „Suchen“ kapseln alle eine Unterfunktion, was der gleichen Idee wie InOrder entspricht.

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

2.Suchen

Nach dem Erlernen des Einfügens ist Finden für uns sehr einfach.
Um einen Wertschlüssel zu finden,
ist 1.key kleiner als der Wert des aktuellen Knotens. Suchen Sie links nach
2.key, der größer als der Wert des aktuellen Knotens ist. Schauen Sie nach rechts für
3.key ist gleich dem aktuellen Knoten. Wenn der Wert gefunden wird, wird der Knoten zurückgegeben.
4. Der aktuell zu durchsuchende Knoten ist ein leerer Knoten und nullptr wird zurückgegeben, was darauf hinweist, dass die Suche fehlgeschlagen ist.

Node* Find(const K& key)
{
    
    
	Node* cur = _root;
	while (cur)
	{
    
    
		if (cur->_key > key)
		{
    
    
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
    
    
			cur = cur->_right;
		}
		else
		{
    
    
			return cur;
		}
	}
	//此时cur为空说明没有找到
	return nullptr;
}

An diesem Punkt können wir mit diesem binären Suchbaum spielen.
Fügen Sie hier eine Bildbeschreibung ein
Es ist ersichtlich, dass das Durchlaufen in der Reihenfolge tatsächlich in Ordnung ist.

5.Löschen

Das vorherige Einfügen und Suchen ist relativ einfach, aber das nächste Löschen ist komplizierter. Das Löschen ist
in vier Situationen unterteilt:
Für den zu löschenden Knoten
1. Der Knoten hat kein linkes und kein rechtes Kind.
Fügen Sie hier eine Bildbeschreibung ein
Die endgültige Löschung erfolgte hier jedoch falsch. Da 14 immer noch auf 13 zeigt und 13 freigegeben wurde, wird der _left-Zeiger von 14 zu einem wilden Zeiger. Was sollen wir tun?
Zu diesem Zeitpunkt müssen wir nur den übergeordneten Knoten des Knotens erstellen (d. h. 14) Zeigen Sie zuerst auf Null
und dann können Sie sicher löschen. 13.
Richtige Version:
Fügen Sie hier eine Bildbeschreibung ein
2. Der Knoten hat kein linkes Kind, aber ein rechtes Kind.
Zu diesem Zeitpunkt müssen Sie nur das rechte Kind des Knotens anvertrauen Sein Vater
Fügen Sie hier eine Bildbeschreibung ein
. 3. Der Knoten hat ein linkes Kind, aber kein rechtes Kind
. Sie müssen nur das linke Kind des Knotens dem Vater anvertrauen. Tatsächlich
kann die erste Kategorie in die zweite oder dritte Kategorie klassifiziert werden Kategorie
Fügen Sie hier eine Bildbeschreibung ein
4. Der Knoten hat sowohl ein linkes als auch ein rechtes Kind
Fügen Sie hier eine Bildbeschreibung ein
. Tatsächlich ist die 1 hier der gesamte Baum. Der Maximalwert kleiner als 3,
4 ist der Minimalwert größer als 3 im gesamten Baum.
Sie können alle Ersetzen Sie den Wert 3.

1 ist tatsächlich der Maximalwert des linken Teilbaums des zu löschenden Knotens (der Maximalwert ist der Knoten ganz rechts).
4 ist tatsächlich der Minimalwert des rechten Teilbaums des zu löschenden Knotens (der Maximalwert ist der Knoten ganz links).

Und sowohl 1 als auch 4 haben ein Merkmal: Es gibt höchstens ein Kind.
Zu diesem Zeitpunkt können Sie die zweite oder dritte Lösung verwenden, indem Sie 1 und 4 löschen.

Heute werden wir es tun, indem wir den Mindestwert des rechten Teilbaums ermitteln

Hinweis: Die Rekursion kann nicht zum späteren Löschen von 3 verwendet werden, da es sich nach dem Austausch nicht mehr um einen binären Suchbaum handelt und es keine Garantie dafür gibt, dass der Wert gefunden werden kann.

In der obigen Diskussion haben wir jedoch nur die Situation besprochen, in der der Knoten einen Vater hat. Wir
haben die folgende Situation nicht besprochen:
5. Was möchte ich löschen, ist der Wurzelknoten?
(1). Der Wurzelknoten hat keinen linken Kind oder rechtes Kind.

Node* tmp = _root;
_root=nullptr;
delete tmp;

Fügen Sie hier eine Bildbeschreibung ein
(2). Der Wurzelknoten hat nur ein Kind
. Denn wir wissen: Der linke Teilbaum und der rechte Teilbaum eines binären Suchbaums sind beide binäre Suchbäume.
Beispielsweise hat der Wurzelknoten nur ein linkes Kind und kein rechtes Kind Zu
diesem Zeitpunkt müssen wir nur die Wurzel lassen Der Knoten entspricht dem Wurzelknoten des linken Teilbaums (dh dem linken untergeordneten Element des Wurzelknotens). Vor dem
Fügen Sie hier eine Bildbeschreibung ein
Löschen des Wurzelknotens:
Fügen Sie hier eine Bildbeschreibung ein
Nach dem Löschen des Wurzelknotens:
Fügen Sie hier eine Bildbeschreibung ein
Es Es ist ersichtlich, dass es nach dem Löschen immer noch ein binärer Suchbaum ist, wenn Sie dies tun . Auf die
gleiche Weise hat der Knoten nur das rechte Kind. Wenn es kein linkes Kind gibt,
müssen Sie nur den Wurzelknoten gleich der Wurzel machen Knoten des rechten Teilbaums (d. h. das rechte Kind des Wurzelknotens).

Auf die gleiche Weise kann die erste Situation auch als zweite Situation
(3) klassifiziert werden. Der Wurzelknoten hat 2 Kinder.
Fügen Sie hier eine Bildbeschreibung ein
Vor dem Löschen:
Fügen Sie hier eine Bildbeschreibung ein
Nach dem Löschen:
Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein
Es wird jedoch auch in zwei Situationen unterteilt
: 1. Weil gesucht wird das jüngste Kind des rechten Teilbaums. Der linke Knoten
bedeutet, dass ganz nach links gesucht wird, sodass ich am Ende nur mein rechtes Kind zum linken Kind meines Vaters machen muss.

2. Aber wenn Sie nicht den ganzen Weg suchen und es direkt finden,
das heißt, ich bin zu diesem Zeitpunkt das richtige Kind meines Vaters, dann muss mein richtiges Kind das richtige Kind meines Vaters werden.

Die oben gezeigte Situation gehört zur zweiten Situation.
Daher können wir einen solchen Code schreiben.
Die darin enthaltenen Kommentare sind sehr detailliert. Wenn Sie ihn nicht sehr gut verstehen,
können Sie den Code lesen und Bilder zeichnen, um ihn besser zu verstehen.

//删除成功,返回true
//删除失败,说明没有这个元素,返回false
bool Erase(const K& key)
{
    
    
	//1.没有左孩子,没有右孩子 可以归为2,3中的任意一类
	//2.有右孩子,没有左孩子
	//3.有左孩子,没有右孩子
	//4.有左孩子,也有右孩子
	Node* cur = _root;
	Node* parent = cur;//父亲
	while (cur)
	{
    
    
		//往左找
		if (cur->_key > key)
		{
    
    
			parent = cur;
			cur = cur->_left;
		}
		//往右找
		else if (cur->_key < key)
		{
    
    
			parent = cur;
			cur = cur->_right;
		}
		//找到了
		else
		{
    
    
			//1.有右孩子,没有左孩子
			//此时只需要让他的右孩子代替它的位置即可(也就是把自己的右孩子给父亲,然后删除自己即可)
			if (cur->_left == nullptr)
			{
    
    
				//要删除的是_root,且_root没有左孩子
				//那么让右孩子变成root即可
				if (cur == _root)
				{
    
    
					_root = cur->_right;
					delete cur;
				}
				//说明我是父亲的左孩子
				if (cur == parent->_left)
				{
    
    
					//就让我的右孩子成为父亲的左孩子
					parent->_left = cur->_right;
					delete cur;
				}
				//说明我是父亲的右孩子
				else
				{
    
    
					//就让我的右孩子成为父亲的右孩子
					parent->_right = cur->_right;
					delete cur;
				}
			}
			//2.有左孩子,没有右孩子
			else if (cur->_right == nullptr)
			{
    
    
				//要删除的是_root,且_root没有左孩子
				//那么让右孩子变成root即可
				if (cur == _root)
				{
    
    
					_root = cur->_left;
					delete cur;
				}
				//说明我是父亲的左孩子
				if (cur == parent->_left)
				{
    
    
					//就让我的左孩子成为父亲的左孩子
					parent->_left = cur->_left;
					delete cur;
				}
				//说明我是父亲的右孩子
				else
				{
    
    
					//就让我的左孩子成为父亲的右孩子
					parent->_right = cur->_left;
					delete cur;
				}
			}
			//3.有左孩子,也有右孩子
			//我既可以让左子树的最大值替代我,也可以让右子树的最小值替代我
			//这里就找右子树的最小值吧,右子树的最小值就是右子树的最左侧节点
			//找到右子树中的最小值,将他的值跟我交换,然后删除刚才那个节点
			//注意:"删除刚才那个节点"的操作不能使用递归,因为交换后就不是BST了,就无法保证能够找到那个值了
			else
			{
    
    
				parent = cur;
				Node* MinOfRight = cur->_right;
				while (MinOfRight->_left)
				{
    
    
					parent = MinOfRight;
					MinOfRight = MinOfRight->_left;
				}
				//开始交换
				swap(cur->_key, MinOfRight->_key);
				//然后删除MinOfRight
				//1.的确向下查找了
				//此时MinOfRight就是parent的左孩子
				//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的左孩子,然后就可以删除了
				if (parent->_left == MinOfRight)
				{
    
    
					parent->_left = MinOfRight->_right;
					delete MinOfRight;
				}
				//2.没有继续往下查找
				//此时MinOfRight就是parent的右孩子
				//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的右孩子,然后就可以删除了
				else
				{
    
    
					parent->_right = MinOfRight->_right;
					delete MinOfRight;
				}
			}
			//删除成功
			return true;
		}
	}
	//此时cur为空说明没有找到
	return false;
}

6. Rekursive Version von Suchen, Einfügen, Löschen

1.FindR

Die rekursive Version von Find ist sehr einfach:
Angenommen, der zu findende Wert ist Key.
Wenn der Wert des aktuellen Knotens == key: gefunden wird, geben Sie einfach den aktuellen Knoten zurück.
Wenn der Wert des aktuellen Knotens > key: it bedeutet, dass der Wert des aktuellen Knotens zu groß ist, gehen Sie nach links. Suchen
Wenn der Wert des aktuellen Knotens <key: ist, bedeutet dies, dass der Wert des aktuellen Knotens zu klein ist, suchen Sie nach rechts

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

2.R einfügen

Wenn der aktuelle Knoten ein leerer Knoten ist: Dies bedeutet, dass eine leere Position gefunden wurde, fügen Sie ihn einfach ein.
Wenn der Wert des aktuellen Knotens > Schlüssel: Dies bedeutet, dass der aktuelle Knotenwert zu groß ist, suchen Sie nach links Einfügeposition.
Wenn der Wert des aktuellen Knotens < key: bedeutet, dass der aktuelle Knotenwert zu klein ist. Suchen Sie nach rechts nach der Einfügeposition.
Wenn der Wert des aktuellen Knotens == key: bedeutet, dass er wiederholt wird , geben „false“ zurück und es können keine doppelten Elemente eingefügt werden.

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;
	}
	else if (root->_key > key)
	{
    
    
		return _InsertR(root->_left, key);
	}
	else if(root->_key < key)
	{
    
    
		return _InsertR(root->_right, key);
	}
	else
	{
    
    
		return false;
	}
}


Das Besondere daran ist, dass Sie den übergeordneten Knoten nicht übergeben müssen , solange Sie eine Referenz hinzufügen
. Da root der Alias ​​des linken oder rechten untergeordneten Knotens des vorherigen Knotens ist, wirkt sich eine Änderung von root auf den linken oder rechten Knoten aus Kind des vorherigen Knotens.

Besonders clever ist der hier als Parameter angegebene Wert.

3.LöschenR

Hier ist die rekursive Version von Erase.
Nachdem der zu löschende Knoten mit MinOfRight ausgetauscht wurde, ist der rechte Teilbaum ein binärer Suchbaum.
Daher kann er beim späteren Löschen von MinOfRight wiederverwendet werden. Löschen Sie MinOfRight einfach direkt im rechten Teilbaum.
Und zum Löschen der Wurzel. Das Gleiche gilt für Knoten

Referenzen werden hier weiterhin als Parameter verwendet. Der Clou daran ist, dass dies besonders praktisch ist, wenn der Zeiger geändert wird. Es ist nicht erforderlich, den übergeordneten Knoten zu übergeben.

bool EraseR(const K& key)
{
    
    
	return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{
    
    
	if (root == nullptr)
	{
    
    
		return false;
	}
	//1.往左找,在左子树里面删除key
	if (root->_key > key)
	{
    
    
		return _EraseR(root->_left, key);
	}
	//2.往右找,在右子树里面删除key
	else if (root->_key < key)
	{
    
    
		return _EraseR(root->_right, key);
	}
	// 当前的根节点
	else
	{
    
    
		//root不仅仅是root,root是父亲的孩子的别名
		//因此只需要改变root就可以改变父亲的孩子了
		if (root->_left == nullptr)
		{
    
    
			//不要忘了保存root
			Node* del = root;
			root = root->_right;//这里不是迭代,而是修改指向,是把我的右孩子托付给父亲
			delete del;
			return true;
		}
		else if (root->_right == nullptr)
		{
    
    
			Node* del = root;
			root = root->_left;
			delete del;
			return true;
		}
		else
		{
    
    
			Node* MinOfRight = root->_right;
			while (MinOfRight->_left)
			{
    
    
				MinOfRight = MinOfRight->_left;
			}
			swap(root->_key, MinOfRight->_key);
			//注意:现在是递归版本,参数可以传入节点,此时这棵树不是BST,但是root的右子树是BST
			//所以此时递归删除root->_right上的key值即可
			//而且也适用于直接删除根节点的情况
			_EraseR(root->_right, key);
		}
	}
	return true;
}

7. Zerstörung, Kopierkonstruktion, Überlastung des Zuweisungsoperators

1. Zerstörung

Genau wie bei der Zerstörung von Binärbäumen wird bei der Post-Order-Traversal-Zerstörung
immer noch die rekursive Version verwendet.

//析构函数  后续遍历析构
~BSTree()
{
    
    
	_Destroy(_root);
}
void _Destroy(Node*& root)
{
    
    
	if (root == nullptr) return;
	_Destroy(root->_left);
	_Destroy(root->_right);
	delete root;
	root = nullptr;
}

2.Konstruktion kopieren

Bei der Vorbestellungs-Traversalkonstruktion
wird zuerst der Wurzelknoten erstellt, dann rekursiv der linke Teilbaum und der rechte Teilbaum erstellt und
schließlich der Wurzelknoten zurückgegeben.

//拷贝构造
//先序遍历构造
BSTree(const BSTree<K>& bst)
{
    
    
	_root = _Copy(bst._root);
}
Node* _Copy(const Node* root)
{
    
    
	if (root == nullptr)
	{
    
    
		return nullptr;
	}
	Node* NewRoot = new Node(root->_key);
	NewRoot->_left = _Copy(root->_left);
	NewRoot->_right = _Copy(root->_right);
	return NewRoot;
}

3. Überlastung der Zuweisungsoperation

Nachdem Sie die Kopierstruktur implementiert haben, können Sie
sie direkt auf moderne Weise schreiben.

//赋值运算符重载
BSTree<K>& operator=(BSTree<K> bst)
{
    
    
	std::swap(_root, bst._root);
	return *this;
}

8. Vollständiger Code des Schlüsselmodells

template <class K>
struct BSTreeNode
{
    
    
	BSTreeNode<K>* _left = nullptr;
	BSTreeNode<K>* _right = nullptr;
	K _key;
	BSTreeNode(const K& key)
		:_key(key)
	{
    
    }
};

template<class K>
class BSTree
{
    
    
	typedef BSTreeNode<K> Node;
public:
	//非递归实现insert.find,erase
	bool Insert(const K& key)
	{
    
    
		if (_root == nullptr)
		{
    
    
			_root = new Node(key);
			return true;
		}
		Node* cur = _root;
		Node* parent = _root;//记录父亲,因为要能够插入
		while (cur)
		{
    
    
			//要插入的值小于父亲,插入到左子树当中
			if (cur->_key > key)
			{
    
    
				parent = cur;
				cur = cur->_left;
			}
			//要插入的的值大于父亲,插入到右子树当中
			else if (cur->_key < key)
			{
    
    
				parent = cur;
				cur = cur->_right;
			}
			//出现了重复元素,BST搜索二叉树不允许出现重复值,因此不允许插入,返回false
			else
			{
    
    
				return false;
			}
		}
		//此时cur为空,在此位置插入value
		cur = new Node(key);
		//要插入的元素小于父亲,插入到左子树当中
		if (parent->_key > key)
		{
    
    
			parent->_left = cur;
		}
		//要插入的元素大于父亲,插入到右子树当中
		else
		{
    
    
			parent->_right = cur;
		}
		//插入成功
		return true;
	}

	Node* Find(const K& key)
	{
    
    
		Node* cur = _root;
		while (cur)
		{
    
    
			if (cur->_key > key)
			{
    
    
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
    
    
				cur = cur->_right;
			}
			else
			{
    
    
				return cur;
			}
		}
		//此时cur为空说明没有找到
		return nullptr;
	}

	bool Erase(const K& key)
	{
    
    
		//1.没有左孩子,没有右孩子 可以归为2,3中的任意一类
		//2.有右孩子,没有左孩子
		//3.有左孩子,没有右孩子
		//4.有左孩子,也有右孩子
		Node* cur = _root;
		Node* parent = cur;//父亲
		while (cur)
		{
    
    
			//往左找
			if (cur->_key > key)
			{
    
    
				parent = cur;
				cur = cur->_left;
			}
			//往右找
			else if (cur->_key < key)
			{
    
    
				parent = cur;
				cur = cur->_right;
			}
			//找到了
			else
			{
    
    
				//1.有右孩子,没有左孩子
				//此时只需要让他的右孩子代替它的位置即可(也就是把自己的右孩子给父亲,然后删除自己即可)
				if (cur->_left == nullptr)
				{
    
    
					//要删除的是_root,且_root没有左孩子
					//那么让右孩子变成root即可
					if (cur == _root)
					{
    
    
						_root = cur->_right;
						delete cur;
					}
					//说明我是父亲的左孩子
					if (cur == parent->_left)
					{
    
    
						//就让我的右孩子成为父亲的左孩子
						parent->_left = cur->_right;
						delete cur;
					}
					//说明我是父亲的右孩子
					else
					{
    
    
						//就让我的右孩子成为父亲的右孩子
						parent->_right = cur->_right;
						delete cur;
					}
				}
				//2.有左孩子,没有右孩子
				else if (cur->_right == nullptr)
				{
    
    
					//要删除的是_root,且_root没有左孩子
					//那么让右孩子变成root即可
					if (cur == _root)
					{
    
    
						_root = cur->_left;
						delete cur;
					}
					//说明我是父亲的左孩子
					if (cur == parent->_left)
					{
    
    
						//就让我的左孩子成为父亲的左孩子
						parent->_left = cur->_left;
						delete cur;
					}
					//说明我是父亲的右孩子
					else
					{
    
    
						//就让我的左孩子成为父亲的右孩子
						parent->_right = cur->_left;
						delete cur;
					}
				}
				//3.有左孩子,也有右孩子
				//我既可以让左子树的最大值替代我,也可以让右子树的最小值替代我
				//这里就找右子树的最小值吧,右子树的最小值就是右子树的最左侧节点
				//找到右子树中的最小值,将他的值跟我交换,然后删除刚才那个节点
				//注意:"删除刚才那个节点"的操作不能使用递归,因为交换后就不是BST了,就无法保证能够找到那个值了
				else
				{
    
    
					parent = cur;
					Node* MinOfRight = cur->_right;
					while (MinOfRight->_left)
					{
    
    
						parent = MinOfRight;
						MinOfRight = MinOfRight->_left;
					}
					//开始交换
					swap(cur->_key, MinOfRight->_key);
					//然后删除MinOfRight
					//1.的确向下查找了
					//此时MinOfRight就是parent的左孩子
					//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的左孩子,然后就可以删除了
					if (parent->_left == MinOfRight)
					{
    
    
						parent->_left = MinOfRight->_right;
						delete MinOfRight;
					}
					//2.没有继续往下查找
					//此时MinOfRight就是parent的右孩子
					//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的右孩子,然后就可以删除了
					else
					{
    
    
						parent->_right = MinOfRight->_right;
						delete MinOfRight;
					}
				}
				//删除成功
				return true;
			}
		}
		//此时cur为空说明没有找到
		return false;
	}

	//析构函数  后续遍历析构
	~BSTree()
	{
    
    
		_Destroy(_root);
	}
	//C++11新语法
	BSTree() = default;//强制生成默认构造
	//拷贝构造
	//先序遍历构造
	BSTree(const BSTree<K>& bst)
	{
    
    
		_root = _Copy(bst._root);
	}

	//赋值运算符重载
	BSTree<K>& operator=(BSTree<K> bst)
	{
    
    
		std::swap(_root, bst._root);
		return *this;
	}

	void InOrder()
	{
    
    
		_InOrder(_root);
		cout << endl;
	}

	//递归实现insert.find,erase
	Node* FindR(const K& key)
	{
    
    
		return _FindR(_root,key);
	}

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

	bool EraseR(const K& key)
	{
    
    
		return _EraseR(_root, key);
	}
private:
	Node* _Copy(const Node* root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return nullptr;
		}
		Node* NewRoot = new Node(root->_key);
		NewRoot->_left = _Copy(root->_left);
		NewRoot->_right = _Copy(root->_right);
		return NewRoot;
	}

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

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

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

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

	bool _EraseR(Node*& root, const K& key)
	{
    
    
		if (root == nullptr)
		{
    
    
			return false;
		}
		//1.往左找
		if (root->_key > key)
		{
    
    
			return _EraseR(root->_left, key);
		}
		//2.往右找
		else if (root->_key < key)
		{
    
    
			return _EraseR(root->_right, key);
		}
		// 删除
		else
		{
    
    
			//root不仅仅是root,root是父亲的孩子的别名,让root成为root的右孩子即可
			if (root->_left == nullptr)
			{
    
    
				Node* del = root;
				root = root->_right;//这里不是迭代,而是修改指向,托孤
				delete del;
				return true;
			}
			else if (root->_right == nullptr)
			{
    
    
				Node* del = root;
				root = root->_left;
				delete del;
				return true;
			}
			else
			{
    
    
				Node* MinOfRight = root->_right;
				while (MinOfRight->_left)
				{
    
    
					MinOfRight = MinOfRight->_left;
				}
				swap(root->_key, MinOfRight->_key);
				//注意:现在是递归版本,参数可以传入节点,此时这棵树不是BST,但是root的右子树是BST
				//所以此时递归删除root->_right上的key值即可
				//而且对于直接删除_root也没有任何影响
				_EraseR(root->_right, key);
			}
		}
		return true;
	}
	Node* _root = nullptr;
};

9. Anwendung des binären Suchbaums

1. Schlüsselmodell

Fügen Sie hier eine Bildbeschreibung ein

2. Schlüsselwertmodell

Fügen Sie hier eine Bildbeschreibung ein
Als nächstes ändern wir den Code des Schlüsselmodells in das Schlüsselwertmodell. Wir
müssen nur Folgendes ändern:
1.BSTreeNode-Knoten
2.Einfügen
3.InOrder-Drucken.
Es sind keine weiteren Änderungen erforderlich.

namespace kv
{
    
    
	template <class K,class V>
	struct 
	{
    
    
		BSTreeNode<K,V>* _left = nullptr;
		BSTreeNode<K,V>* _right = nullptr;
		K _key;
		V _value;
		BSTreeNode(const K& key,const V& value)
			:_key(key)
			,_value(value)
		{
    
    }
	};

	template<class K,class V>
	class BSTree
	{
    
    
		typedef BSTreeNode<K,V> Node;
	public:
		//非递归实现insert.find,erase
		bool Insert(const K& key,const V& value)
		{
    
    
			if (_root == nullptr)
			{
    
    
				_root = new Node(key,value);
				return true;
			}
			Node* cur = _root;
			Node* parent = _root;//记录父亲,因为要能够插入
			while (cur)
			{
    
    
				//要插入的值小于父亲,插入到左子树当中
				if (cur->_key > key)
				{
    
    
					parent = cur;
					cur = cur->_left;
				}
				//要插入的的值大于父亲,插入到右子树当中
				else if (cur->_key < key)
				{
    
    
					parent = cur;
					cur = cur->_right;
				}
				//出现了重复元素,BST搜索二叉树不允许出现重复值,因此不允许插入,返回false
				else
				{
    
    
					return false;
				}
			}
			//此时cur为空,在此位置插入value
			cur = new Node(key,value);
			//要插入的元素小于父亲,插入到左子树当中
			if (parent->_key > key)
			{
    
    
				parent->_left = cur;
			}
			//要插入的元素大于父亲,插入到右子树当中
			else
			{
    
    
				parent->_right = cur;
			}
			//插入成功
			return true;
		}

		Node* Find(const K& key)
		{
    
    
			Node* cur = _root;
			while (cur)
			{
    
    
				if (cur->_key > key)
				{
    
    
					cur = cur->_left;
				}
				else if (cur->_key < key)
				{
    
    
					cur = cur->_right;
				}
				else
				{
    
    
					return cur;
				}
			}
			//此时cur为空说明没有找到
			return nullptr;
		}

		bool Erase(const K& key)
		{
    
    
			//1.没有左孩子,没有右孩子 可以归为2,3中的任意一类
			//2.有右孩子,没有左孩子
			//3.有左孩子,没有右孩子
			//4.有左孩子,也有右孩子
			Node* cur = _root;
			Node* parent = cur;//父亲
			while (cur)
			{
    
    
				//往左找
				if (cur->_key > key)
				{
    
    
					parent = cur;
					cur = cur->_left;
				}
				//往右找
				else if (cur->_key < key)
				{
    
    
					parent = cur;
					cur = cur->_right;
				}
				//找到了
				else
				{
    
    
					//1.有右孩子,没有左孩子
					//此时只需要让他的右孩子代替它的位置即可(也就是把自己的右孩子给父亲,然后删除自己即可)
					if (cur->_left == nullptr)
					{
    
    
						//要删除的是_root,且_root没有左孩子
						//那么让右孩子变成root即可
						if (cur == _root)
						{
    
    
							_root = cur->_right;
							delete cur;
						}
						//说明我是父亲的左孩子
						if (cur == parent->_left)
						{
    
    
							//就让我的右孩子成为父亲的左孩子
							parent->_left = cur->_right;
							delete cur;
						}
						//说明我是父亲的右孩子
						else
						{
    
    
							//就让我的右孩子成为父亲的右孩子
							parent->_right = cur->_right;
							delete cur;
						}
					}
					//2.有左孩子,没有右孩子
					else if (cur->_right == nullptr)
					{
    
    
						//要删除的是_root,且_root没有左孩子
						//那么让右孩子变成root即可
						if (cur == _root)
						{
    
    
							_root = cur->_left;
							delete cur;
						}
						//说明我是父亲的左孩子
						if (cur == parent->_left)
						{
    
    
							//就让我的左孩子成为父亲的左孩子
							parent->_left = cur->_left;
							delete cur;
						}
						//说明我是父亲的右孩子
						else
						{
    
    
							//就让我的左孩子成为父亲的右孩子
							parent->_right = cur->_left;
							delete cur;
						}
					}
					//3.有左孩子,也有右孩子
					//我既可以让左子树的最大值替代我,也可以让右子树的最小值替代我
					//这里就找右子树的最小值吧,右子树的最小值就是右子树的最左侧节点
					//找到右子树中的最小值,将他的值跟我交换,然后删除刚才那个节点
					//注意:"删除刚才那个节点"的操作不能使用递归,因为交换后就不是BST了,就无法保证能够找到那个值了
					else
					{
    
    
						parent = cur;
						Node* MinOfRight = cur->_right;
						while (MinOfRight->_left)
						{
    
    
							parent = MinOfRight;
							MinOfRight = MinOfRight->_left;
						}
						//开始交换
						swap(cur->_key, MinOfRight->_key);
						//然后删除MinOfRight
						//1.的确向下查找了
						//此时MinOfRight就是parent的左孩子
						//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的左孩子,然后就可以删除了
						if (parent->_left == MinOfRight)
						{
    
    
							parent->_left = MinOfRight->_right;
							delete MinOfRight;
						}
						//2.没有继续往下查找
						//此时MinOfRight就是parent的右孩子
						//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的右孩子,然后就可以删除了
						else
						{
    
    
							parent->_right = MinOfRight->_right;
							delete MinOfRight;
						}
					}
					//删除成功
					return true;
				}
			}
			//此时cur为空说明没有找到
			return false;
		}

		//析构函数  后续遍历析构
		~BSTree()
		{
    
    
			_Destroy(_root);
		}
		//C++11新语法
		BSTree() = default;//强制生成默认构造
		//拷贝构造
		//先序遍历构造
		BSTree(const BSTree<K,V>& bst)
		{
    
    
			_root = _Copy(bst._root);
		}

		//赋值运算符重载
		BSTree<K,V>& operator=(BSTree<K,V> bst)
		{
    
    
			std::swap(_root, bst._root);
			return *this;
		}

		void InOrder()
		{
    
    
			_InOrder(_root);
			cout << endl;
		}

		//递归实现insert.find,erase
		Node* FindR(const K& key)
		{
    
    
			return _FindR(_root, key);
		}

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

		bool EraseR(const K& key)
		{
    
    
			return _EraseR(_root, key);
		}
	private:
		Node* _Copy(const Node* root)
		{
    
    
			if (root == nullptr)
			{
    
    
				return nullptr;
			}
			Node* NewRoot = new Node(root->_key);
			NewRoot->_left = _Copy(root->_left);
			NewRoot->_right = _Copy(root->_right);
			return NewRoot;
		}

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

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

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

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

		bool _EraseR(Node*& root, const K& key)
		{
    
    
			if (root == nullptr)
			{
    
    
				return false;
			}
			//1.往左找
			if (root->_key > key)
			{
    
    
				return _EraseR(root->_left, key);
			}
			//2.往右找
			else if (root->_key < key)
			{
    
    
				return _EraseR(root->_right, key);
			}
			// 删除
			else
			{
    
    
				//root不仅仅是root,root是父亲的孩子的别名,让root成为root的右孩子即可
				if (root->_left == nullptr)
				{
    
    
					Node* del = root;
					root = root->_right;//这里不是迭代,而是修改指向,托孤
					delete del;
					return true;
				}
				else if (root->_right == nullptr)
				{
    
    
					Node* del = root;
					root = root->_left;
					delete del;
					return true;
				}
				else
				{
    
    
					Node* MinOfRight = root->_right;
					while (MinOfRight->_left)
					{
    
    
						MinOfRight = MinOfRight->_left;
					}
					swap(root->_key, MinOfRight->_key);
					//注意:现在是递归版本,参数可以传入节点,此时这棵树不是BST,但是root的右子树是BST
					//所以此时递归删除root->_right上的key值即可
					//而且对于直接删除_root也没有任何影响
					_EraseR(root->_right, key);
				}
			}
			return true;
		}
		Node* _root = nullptr;
	};
}

Testen wir es.
Zum einen zählt man, wie oft ein Wort vorkommt.
Zum anderen handelt es sich um ein Wörterbuch für Englisch-Chinesisch-Übersetzungen.

void TestBSTree()
{
    
    
	string strs[] = {
    
     "apple","Banana","Grape","Mango","apple","Banana" ,"apple","Mango" ,"Mango" ,"Mango" ,"Mango" };
	// 统计单词出现的次数
	kv::BSTree<string, int> countTree;
	for (auto str : strs)
	{
    
    
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
    
    
			countTree.Insert(str, 1);
		}
		else
		{
    
    
			ret->_value++;
		}
	}
	countTree.InOrder();

	//英汉互译的词典
	kv::BSTree<string, string> dict;
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");
	dict.Insert("BST", "二叉搜索树");
	dict.Insert("KV", "key-value模型");

	string str;
	while (cin >> str)
	{
    
    
		auto ret = dict.Find(str);
		if (ret)
		{
    
    
			cout << str << ":" << ret->_value << endl;
		}
		else
		{
    
    
			cout << "单词拼写错误" << endl;
		}
	}
}

Fügen Sie hier eine Bildbeschreibung ein

Das Obige ist die gesamte Implementierung (nicht rekursive Version und rekursive Version) und Anwendung des C++ Binary Search Tree (BST). Ich hoffe, es kann für alle hilfreich sein!

Supongo que te gusta

Origin blog.csdn.net/Wzs040810/article/details/135041350
Recomendado
Clasificación