C++---二叉搜索树

Posted Moua

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++---二叉搜索树相关的知识,希望对你有一定的参考价值。

目录

一、二叉搜索树的概念

二、二叉搜索树的操作

1、查找

2、插入

3、删除

三、二叉搜索树的实现

四、二叉搜索树的两种模型

1、K模型

2、KV模型

五、二叉搜索树性能分析


一、二叉搜索树的概念

二叉搜索树又称二叉排序树,它是一颗空树或者是具有以下性质的树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

二、二叉搜索树的操作

1、查找

基本思路:

  • 根节点为空,返回false
  • 根节点不为空,比较根节点和要找的值是否相等,相等返回true。
  • 根节点和要找的值不想等时,判断跟节点和要找的值的关系。
  • 如果根节点小于要找到的值,则在右子树找
  • 如果根节点大于要找的值,在左子树找。

2、插入

  • 树为空,直接插入

  • 树不为空,按照二叉搜索树的特点,找到插入的位置,插入节点。

3、删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

  •  要删除的结点无孩子结点:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
  • 要删除的结点只有左孩子结点:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
  • 要删除的结点只有右孩子结点:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
  • 要删除的结点有左、右孩子结点:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,即找到右子树的最左节点进行替换。要注意的是,有可能删除的节点的孩子节点就是最左节点。

三、二叉搜索树的实现

#pragma once
#include<iostream>

template<class k>
struct BSTNode
{
	k value;
	struct BSTNode<k>* left;
	struct BSTNode<k>* right;

	BSTNode(const k& val)
		:value(val), left(nullptr), right(nullptr)
	{}
};

template<class k>
class BSTree
{
	typedef BSTNode<k> Node;
public:
	bool Insert(const k& val)
	{
		//根节点为空格,直接插入
		if (root == nullptr)
		{
			root = new Node(val);
			return true;
		}

		//根节点不为空,先找到合适的位置,在插入
		Node* parent = root;
		Node* cur = root;
		while (cur)
		{
			//如果找到相同节点,则不需要插入
			if (cur->value == val)
			{
				return false;
			}
			else if (cur->value < val)
			{
				parent = cur;
				cur = cur->right;
			}
			else
			{
				parent = cur;
				cur = cur->left;
			}
		}
		//找到了,创建节点进行插入
		Node* tmp = new Node(val);
		if (parent->value < val)
		{
			parent->right = tmp;
		}
		else
		{
			parent->left = tmp;
		}

		return true;
	}

	const Node* find(const k& val)
	{
		Node* cur = root;
		while (cur)
		{
			if (cur->value == val)
			{
				return cur;
			}
			else if (cur->value < val)
			{
				cur = cur->right;
			}
			else
			{
				cur = cur->left;
			}
		}
		return nullptr;
	}

	bool Erase(const k& val)
	{
		//查找
		Node* parent = nullptr;
		Node* cur = root;
		while (cur)
		{
			if (cur->value > val)
			{
				//在左子树
				parent = cur;
				cur = cur->left;
			}
			else if (cur->value < val)
			{
				//在右子树找
				parent = cur;
				cur = cur->right;
			}
			else
			{
				//找到了,删除
				//左子树为空
				if (cur->left == nullptr)
				{
					if (cur == root)
						root = cur->right;
					//父节点指向右子树
					else
					{
						if (cur == parent->left)
						{
							parent->left = cur->right;
						}
						else
						{
							parent->right = cur->right;
						}
					}
				}
				else if (cur->right == nullptr)
				{
					if (cur == root)
						root = cur->left;
					else
					{
						if (cur == parent->left)
							parent->left = cur->left;
						else
							parent->right = cur->left;
					}
				}
				else
				{
					//左右子树都不为空,找右树的最小节点
					Node* minNode = cur->right;
					Node* minNodeParent = cur;
					while (minNode->left)
					{
						minNodeParent = minNode;
						minNode = minNode->left;
					}

					//将要删除的节点和该节点交换
					cur->value = minNode->value;
					//删除交换后的节点
					if (minNodeParent->left == minNode)
						minNodeParent->left = minNode->right;
					else
						minNodeParent->right = minNode->right;

					//删除节点
					delete minNode;
				}
				return true;
			}
		}
		return false;
	}
public:
	Node* root = nullptr;
};

#include "BSTree.h"

void print(BSTNode<int>* node)
{
	if (node == nullptr)
		return;
	std::cout << node->value<<" ";

	print(node->left);
	print(node->right);
}

void test()
{
	BSTree<int> bt;
	bt.Insert(5);
	bt.Insert(2);
	bt.Insert(6);
	bt.Insert(8);
	bt.Insert(1);
	bt.Insert(6);
	bt.Insert(8);
	bt.Insert(8);

	//中序打印二叉树
	print(bt.root);

	if (bt.find(2) != nullptr)
		std::cout << "找到了" << std::endl;
	bt.Erase(2);
	print(bt.root);


}

int main()
{
	test();
	return 0;
}

四、二叉搜索树的两种模型

1、K模型

K模型是指,在二叉搜索树的节点中,只有key值作为关键码。关键码即为搜索到的价值。

  • 例如,将一组数据以二叉搜索树的结构进行存储,需要找某一个数据时直接在二叉搜索树中查找该值是否存在,如果存在则输出。
  • 在这个示例中,二叉搜索树的每一个节点中都只存储了一个数作为key值。

2、KV模型

KV模型是指,每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。

  • 例如,英汉语词典中,给定一个英语单词要求输出汉语意思。这里,英语单词就是key,该英语单词对应的汉语就是Value.
  • 在这个二叉搜树中,每个节点需要存两个关键码key和value,即英语单词和对应的中文。
  • 在输入一个英语单词时,只需要根据输入的英语单词在二叉搜索树中进行查找,并输出相应的中文意思。
  • 需要注意的是,在KV模型中,只需要根据key值在二叉树中进行查找。

实现一个KV模型的英译汉词典

#pragma once
#include<iostream>
#include<string>
#include<stack>

template<class K,class V>
struct Node
{
	K key;
	V value;
	Node<K, V>* left;
	Node<K, V>* right;

	//构造函数,通过指定的key和value值创建节点
	Node(const K& _key, const V& _val)
		:key(_key), value(_val)
		, left(nullptr), right(nullptr)
	{}
};

template<class K,class V>
class BSTreeKV
{
	typedef Node<K, V> Node;
public:
	//默认构造一个空的,只有当插入节点时才创建root节点
	//插入一个节点
	void insert(const K& key, const V& value)
	{
		Node* newNode = new Node(key, value);
		//如果根节点为空,直接插入
		if (root == nullptr)
			root = newNode;
		else
		{
			//找到一个合适的插入位置,进行插入
			Node* cur = root;
			while (cur->left || cur->right)
			{
				if (cur->key > newNode->key)
				{
					if (cur->left == nullptr)
						break;
					cur = cur->left;
				}
				else if (cur->key < newNode->key)
				{
					if (cur->right == nullptr)
						break;
					cur = cur->right;
				}
				else 
				{
					//已经存在该节点, 不重复插入
					return;
				}
			}
			//判断prev和cur的关系,将newNode插入
			if (cur->key > newNode->key)
			{
				cur->left = newNode;
			}
			else
			{
				cur->right = newNode;
			}
		}
	}

	//查找
	void find(const K& k)
	{
		Node* cur = root;
		while (cur)
		{
			if (cur->key > k)
			{
				cur = cur->left;
			}
			else if (cur->key < k)
			{
				cur = cur->right;
			}
			else
			{
				std::cout << k << "->"<<cur->value<<std::endl;
				break;
			}
		}
		if (cur == nullptr)
		{
			std::cout << "没有找到该节点" << std::endl;
		}
	}

	//删除
	void erase(const K& k)
	{
		Node* parent = nullptr;
		Node* cur = root;
		while (cur)
		{
			if (cur->key > k)
			{
				parent = cur;
				cur = cur->left;
			}
			else if (cur->key < k)
			{
				parent = cur;
				cur = cur->right;
			}
			else
			{
				//找到了要删除的节点
				//左子树为或者右子树为空,根节点指向cur的;另一个孩子节点
				if (cur->left == nullptr)
				{
					if (parent->left == cur)
					{
						parent->left = cur->right;
					}
					else
						parent->right = cur->right;
				}
				else if (cur->right == nullptr)
				{
					if (parent->left == cur)
					{
						parent->left = cur->left;
					}
					else
						parent->right = cur->left;
				}
				//左右子树都不为空,找最左节点进行替换
				else
				{
					Node* tmp = cur;
					while (tmp->left)
					{
						parent = tmp;
						tmp = tmp->left;
					}
					//交换cur和tmp
					cur->key = tmp->key;
					cur->value = tmp->value;
					if (tmp->right)
					{
						parent->left = tmp->right;
					}
					else
						parent->left = nullptr;
					delete tmp;
				}
				//删除成功
				std::cout << "删除成功" << std::endl;
				break;
			}
		}
		if (cur == nullptr)
		{
			std::cout << "没有找到要删除的节点"<<std::endl;
		}
	}

	//非递归---前序遍历
	void prevPrint()
	{
		std::stack<Node*> st;
		Node* cur = root;
		while (cur || !st.empty())
		{
			//打印并将左路节点入栈
			while (cur)
			{
				st.push(cur);
				std::cout << "<" << cur->key << "," << cur->value << ">" << " ";
				cur = cur->left;
			}
			//左路节点出栈并将右孩子节点入栈
			Node* tmp = st.top();
			st.pop();
			cur = tmp->right;
		}
		std::cout << std::endl;
	}
private:
	Node* root = nullptr;
};


五、二叉搜索树性能分析

二叉搜索树中,无论是插入一个节点还是删除一个节点都首先要进行查找。二叉树的查找时间复杂度为树的深度的函数。

最优情况:该二叉搜索树是一个完全二叉树,深度为logN,时间复杂度为O(logN)

最差情况:该二叉树时一个单支树,深度为N,时间复杂度为O(N)

以上是关于C++---二叉搜索树的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode810. 黑板异或游戏/455. 分发饼干/剑指Offer 53 - I. 在排序数组中查找数字 I/53 - II. 0~n-1中缺失的数字/54. 二叉搜索树的第k大节点(代码片段

二叉搜索树 Rust实现

精选力扣500题 第52题 LeetCode 98. 验证二叉搜索树c++/java详细题解

使用解除引用的c ++二叉搜索树

精选力扣500题 第63题 剑指 Offer 36. 二叉搜索树与双向链表c++/java详细题解

[C/C++]详解STL容器5--二叉搜索树的介绍及模拟实现