C++ 이진 검색 트리(BST) 구현(비재귀 버전 및 재귀 버전) 및 응용


이진 검색 트리는 오름차순 버전과 내림차순 버전으로 구현될 수 있으며,
이 기사에서는 오름차순 버전을 구현합니다.

1. 이진 검색 트리의 특징

이진 검색 트리는 특별한 종류의 이진 트리로,
그 특징은 다음과 같습니다:

1. 왼쪽 하위 트리의 모든 노드는 루트 노드의 값보다 작습니다.
2. 오른쪽 하위 트리의 모든 노드는 루트 노드의 값보다 큽니다
. 3. 왼쪽 및 오른쪽 하위 트리는 모두 이진 검색 트리입니다.
4. - 순서 순회 시퀀스는 5로 정렬됩니다
. 일반적으로 이진 검색 트리는 중복 값을 허용하지 않습니다.

물론 이진 탐색 트리는 기본적으로 오름차순이지만 내림차순으로도 구현할 수 있으니
1, 2번째 항목만 변경하면 됩니다.
첫 번째 항목은 루트 노드보다 왼쪽 하위 트리의 노드가 더 커지도록 변경됩니다. 오른쪽
하위 트리로 변경되는 노드들은 루트 노드보다 작아야 하며,
이때 구현된 이진 탐색 트리는 내림차순으로 되어 있다.
여기에 이미지 설명을 삽입하세요.
예를 들어 이 트리는 이진 탐색 트리이다.

2. 우리가 달성하고자 하는 일반적인 프레임워크

#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;
};

이것은 키 모델의 버전입니다. 마지막으로 키-값 버전을 수정해야 합니다.

template <class K>

여기서 K에게 템플릿을 준 이유는 K 모델에만 맞기 때문이므로
여기 T에서 R은 사용하지 않습니다. : recursion (영어로 recursion)

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

여기서 기본값 nullptr은 루트 노드 _root에 직접 제공됩니다. 기본적으로 컴파일러에 의해 생성된 생성자는 이 기본값을 사용합니다.
추가 사항은 다음과 같습니다.

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

3.삽입

이진 탐색 트리의 특징을 익힌 후, 값을 삽입하는 방법을 살펴보자.
여기에 이미지 설명을 삽입하세요.
참고:
1. 삽입할 위치를 찾기 위해 순회할 때 상위 노드를 기록해야 하며, 그렇지 않으면 삽입할 수 없다
. 마지막에는 값이 부모 노드와 일치하는지 판단해야 하며, 노드의 크기 관계를 통해 왼쪽에 삽입할지 오른쪽에 삽입할지 알 수 있습니다.

그래서 우리는 이런 코드를 작성할 수 있습니다

插入成功,返回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.주문 및 찾기

1.순서대로

InOrder의 중위 순회는 일반 이진 트리의 중위 순회와 완전히 동일합니다. 유일한
차이점은 재귀를 사용하여 구현하고 _root는 외부 세계에서 접근할 수 없는 전용 변수입니다. 따라서 하위 -함수는 하위 함수가 작업을 재귀적으로 완료할 수 있도록 캡슐화됩니다. 주 함수는 외부 세계에서 호출할 수 있으며 하위 함수는 외부 세계에 제공할 필요가 없습니다
. 마찬가지로 최신 재귀 버전도 Insert, Erase 및 Find는 모두 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.찾기

Insert를 배운 후 Find는 매우 간단합니다.
값 키를 찾으려면
1.key가 현재 노드의 값보다 작습니다. 왼쪽을 보면
2.key가 현재 노드의 값보다 큽니다. 3.key 의 right는
현재 노드와 같습니다. value를 찾으면 노드를 반환합니다.
4. 검색할 현재 노드는 빈 노드이고 nullptr이 반환되어 검색이 실패했음을 나타냅니다.

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;
}

이 시점에서 우리는 이진 탐색 트리를 가지고 놀 수 있는데,
여기에 이미지 설명을 삽입하세요.
순차 순회가 실제로 순서대로 이루어지고 있음을 알 수 있습니다.

오.Erase

이전 삽입과 찾기는 비교적 간단하지만 다음 지우기는 더 복잡합니다. 지우기는
4가지 상황으로 나뉩니다.
삭제할 노드의 경우
1. 노드에 왼쪽 자식과 오른쪽 자식이 없습니다.
여기에 이미지 설명을 삽입하세요.
그러나 여기서 최종 삭제는 다음과 같습니다. , 14는 여전히 13을 가리키고 13은 해제되었기 때문에 14의 _left 포인터는 와일드 포인터가 됩니다. 어떻게 해야 할까요?
이때 노드의 부모만 만들면 됩니다(즉, 14) 먼저 null을 가리킨
다음 안전하게 삭제할 수 있습니다. 13.
올바른 버전:
여기에 이미지 설명을 삽입하세요.
2. 노드에는 왼쪽 자식이 없지만 오른쪽 자식이 있습니다.
이때는 노드의 오른쪽 자식만 위임하면 됩니다. 3. 노드
여기에 이미지 설명을 삽입하세요.
왼쪽 자식은 있지만 오른쪽 자식은 없습니다
. 노드의 왼쪽 자식만 아버지에게 맡기면 됩니다. 실제로
첫 번째 범주는 두 번째 범주 또는 세 번째 범주로 분류될 수 있습니다. 카테고리
여기에 이미지 설명을 삽입하세요.
4. 노드에는 왼쪽 자식과 오른쪽 자식이 모두 있습니다
여기에 이미지 설명을 삽입하세요.
. 실제로 여기서 1은 전체 트리입니다. 3,
4보다 작은 최대값은 전체 트리에서 3보다 큰 최소값입니다.
그들은 모두 가능합니다 . 값 3을 바꿉니다.

1은 실제로 삭제될 노드의 왼쪽 하위 트리의 최대값(최대값은 가장 오른쪽 노드)
4는 실제로 삭제될 노드의 오른쪽 하위 트리의 최소값(최대값은 가장 왼쪽 노드)

그리고 1번과 4번 모두 자식이 최대 1명이라는 특징이 있는데,
이때 1번과 4번을 삭제하면 두 번째나 세 번째 해결 방법을 사용할 수 있습니다.

오늘은 오른쪽 하위 트리의 최소값을 찾아보겠습니다.

참고: 교환 후에는 더 이상 이진 검색 트리가 아니며 값을 찾을 수 있다는 보장이 없기 때문에 재귀를 사용하여 나중에 3을 삭제할 수 없습니다.

그러나 위의 논의에서는 노드에 아버지가 있는 상황만 논의했으며
다음과 같은 상황은 논의하지 않았습니다:
5. 삭제하려는 것은 루트 노드입니까?
(1) 루트 노드에는 남은 것이 없습니다. 아이 또는 오른쪽 아이.

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

여기에 이미지 설명을 삽입하세요.
(2) 루트 노드에는 하나의 자식만 있습니다
. 이진 검색 트리의 왼쪽 하위 트리와 오른쪽 하위 트리는 모두 이진 검색 트리입니다.
예를 들어 루트 노드에는 왼쪽 자식만 있고 오른쪽 자식은 없습니다. 이때
루트 노드가 왼쪽 하위 트리의 루트 노드(즉, 루트 노드의 왼쪽 자식)와 같도록 하기만 하면 됩니다.
여기에 이미지 설명을 삽입하세요.
루트 노드를 삭제하기 전:
여기에 이미지 설명을 삽입하세요.
루트 노드를 삭제한 후
여기에 이미지 설명을 삽입하세요.
: 이렇게 하면 삭제 후에도 여전히 이진 검색 트리가 된다는 것을 알 수 있습니다.
같은 방식으로 노드에는 오른쪽 노드만 있습니다. 왼쪽 자식이 없으면
루트 노드를 루트와 동일하게 만들기만 하면 됩니다. 오른쪽 하위 트리의 노드(즉, 루트 노드의 오른쪽 자식)

같은 방법으로 첫 번째 상황도 두 번째 상황으로 분류할 수 있다
(3).루트 노드에는 2개의 자식이 있다.
여기에 이미지 설명을 삽입하세요.
삭제 전:
여기에 이미지 설명을 삽입하세요.
삭제 후:
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
그러나 또한 두 가지 상황으로 나누어진다
: 1. 검색 대상이 되기 때문에 오른쪽 하위 트리의 가장 최근 자식 왼쪽 노드는
왼쪽 끝까지 검색한다는 의미이므로 결국 내 오른쪽 자식을 아버지의 왼쪽 자식으로 만들기만 하면 됩니다.

2. 그러나 샅샅이 찾아보지 않고 직접 찾아내면,
즉 지금 이 순간 내가 내 아버지의 올바른 자녀라면, 내 올바른 자녀는 내 아버지의 올바른 자녀가 되어야 합니다.

위에서 설명한 상황은 두 번째 상황에 속하므로
이러한 코드를 작성할 수 있습니다.
주석이 매우 자세하게 설명되어 있습니다. 잘 이해되지 않으면
코드를 읽고 그림을 그려서 이해하면 됩니다.

//删除成功,返回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. 찾기, 삽입, 지우기의 재귀 버전

1.FindR

Find의 재귀 버전은 매우 간단합니다.
찾을 값이 Key라고 가정합니다.
현재 노드 == key:의 값이 발견되면 현재 노드를 반환합니다.
현재 노드의 값 > key: it인 경우 현재 노드의 값이 너무 크다는 의미는 왼쪽으로 검색
현재 노드의 값이 <key: 현재 노드의 값이 너무 작다는 의미는 오른쪽으로 검색

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

현재 노드가 빈 노드인 경우: 빈 위치를 찾았다는 의미이므로 그냥 삽입하면 됩니다.
현재 노드의 값 > 키인 경우: 현재 노드의 값이 너무 크다는 의미이므로 왼쪽에서 삽입 위치
현재 노드의 값 < key: 현재 노드의 값이 너무 작다는 의미 , 삽입 위치를 오른쪽으로 보면
현재 노드의 값 == key: 반복된다는 의미 , false를 반환하고 중복된 요소를 삽입할 수 없습니다.

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;
	}
}

여기서 특히 영리한 점은 참조를 추가하는 한
부모 노드를 전달할 필요가 없다는 것입니다
. 루트는 이전 노드의 왼쪽 또는 오른쪽 자식의 별칭이므로 루트를 변경하면 왼쪽 또는 오른쪽에 영향을 미칩니다. 이전 노드의 자식.

여기에 매개변수로 인용된 값은 특히 영리합니다.

3.지우기

여기 재귀 버전의 삭제가 있는데
삭제할 노드를 MinOfRight로 교환한 후 오른쪽 하위 트리가 이진 검색 트리이므로
나중에 MinOfRight 삭제 시 재사용할 수 있으며 오른쪽 하위 트리에서 직접 MinOfRight를 삭제하면 됩니다.
그리고 루트를 삭제하는 경우 노드에도 동일하게 적용됩니다.

여기서는 참조가 여전히 매개변수로 사용됩니다. 영리한 점은 포인터를 수정할 때 특히 편리하다는 것입니다. 상위 노드를 전달할 필요가 없습니다.

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. 파괴, 복사 생성, 할당 연산자 오버로딩

1. 파괴

이진 트리의 파괴와 마찬가지로 사후 순회 파괴도
여전히 재귀 버전을 사용합니다.

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

2.복사공사

선주문 순회 구성은
루트 노드를 먼저 구성한 다음 왼쪽 하위 트리와 오른쪽 하위 트리를 재귀적으로 구성하고
마지막으로 루트 노드를 반환합니다.

//拷贝构造
//先序遍历构造
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. 할당 작업 오버로드

복사 구조를 구현한 후
현대적인 방식으로 직접 작성할 수 있습니다.

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

8. 주요 모델의 전체 코드

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. 이진 검색 트리의 적용

1.주요 모델

여기에 이미지 설명을 삽입하세요.

2.키-값 모델

여기에 이미지 설명을 삽입하세요.
다음으로 Key 모델의 코드를 Key-Value 모델로 변경합니다. 1.BSTreeNode 노드 2.insert 3.InOrder 인쇄
만 변경하면 됩니다 . 다른 변경은 필요하지 않습니다.



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;
	};
}

테스트해 보겠습니다.
하나는 단어가 나오는 횟수를 세는 것이고,
다른 하나는 영중 번역 사전입니다.

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;
		}
	}
}

여기에 이미지 설명을 삽입하세요.

위 내용은 C++ BST(Binary Search Tree)의 전체 구현(비재귀적 버전과 재귀적 버전) 및 적용 내용입니다. 모든 분들께 도움이 되었으면 좋겠습니다!

추천

출처blog.csdn.net/Wzs040810/article/details/135041350