c++的学习之路:24、 二叉搜索树概念

news2024/12/23 4:01:09

摘要

本章主要是讲一下二叉搜索树的实现

目录

摘要

 一、二叉搜索树概念

二、 二叉搜索树操作

1、二叉搜索树的查找

2、二叉搜索树的插入

3、二叉搜索树的删除

三、二叉搜索树的实现

1、插入

2、中序遍历

3、删除

4、查找

四、二叉搜索树的递归实现

1、插入

2、删除

3、查找

五、代码

test.c

BSTree.h

六、思维导图


 一、二叉搜索树概念

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

1、若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

2、若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

3、它的左右子树也分别为二叉搜索树

如下图所示的图片就是一个二叉搜索树。

二、 二叉搜索树操作

这个就是不在附图了,就是上面那个图,他的数值就是int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};这个数组所示的数值。

1、二叉搜索树的查找

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。

b、最多查找高度次,走到到空,还没找到,这个值不存在。

2、二叉搜索树的插入

a、树为空,则直接新增节点,赋值给root指针

b、树不空,按二叉搜索树性质查找插入位置,插入新节点

如下图所示

3、二叉搜索树的删除

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

a、要删除的结点无孩子结点

b、要删除的结点只有左孩子结点

c、要删除的结点只有右孩子结点

d、要删除的结点有左、右孩子结点

根据上述情况有下面几种情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除,情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除,情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除,也就是找个保姆

三、二叉搜索树的实现

1、插入

想要插入首先要构建节点,如下方块种代码,就是申请一个节点,这个就不用多说了,第二个快的代码就是申请插入,就是如果没有也就是空的时候就申请一个节点,然后判断需要插入的数值,如果大于跟节点就给给左,如果小于就是右边,然后在循环中进行下去,直到找到合适的位置进行插入,如果有相同的就返回false,测试插入成功在中序遍历进行打印查看。

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

typedef BSTreeNode<K> Node;
    bool Insert(const K& key)
    {
        if (_root == nullptr)
        {
            _root = new Node(key);
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
                return false;
            }
        }
        cur = new Node(key);
        if (parent->_key > key)
        {
            parent->_left = cur;
        }
        else
        {
            parent->_right = cur;
        }
        return true;
    } 

2、中序遍历

在下方块种的代码就是测试中序的,这个就没啥说的就是递归打印,但是有一点要注意,root节点是私密的在外面访问不到,没法使用,这里需要借用一个没有参数的函数进行打印,如下方代码所示,测试结果如图。

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

3、删除

这个就是优点麻烦了,他就是和上面所写操作中的三种情况,根据这三种情况进行写,首先就是寻找相等的,这个就没啥说的了,找到后就是判断左边为空和右边为空的两种情况,找到相等的时候,然后进行判断是否需要链接,这里是不管需不需要都进行链接,因为一种是后续有节点进行连接,一种是没有直接连接为空,刚好解决这两中情况,然后就是左右都不为空的时候,这个需要找个托孤的,就有点像我学Linux的时候遇到的孤儿进程被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//找到相等了
            {
                //左为空
                if (cur->_left == nullptr)
                {
                    if (cur == _root)//如果是根节点,就是左边都是空,只有右边的值
                    {
                        _root = cur->_right;
                    }
                    else//不是根节点
                    {
                        if (parent->_left == cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父
                        {
                            parent->_left = cur->_right;
                        }
                        else//如果cur在父节点的右边,就把cur左边的值托孤给父
                        {
                            parent->_right = cur->_right;
                        }

                    }
                    delete cur;
                }
                //右为空
                else if (cur->_right == nullptr)
                {
                    if (cur == _root)//如果是根节点,右边都是空,只有左边有值
                    {
                        _root = cur->_left;
                    }
                    else//不是根节点
                    {
                        if (parent->_left = cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父
                        {
                            parent->_left = cur->_left;
                        }
                        else//如果cur在父节点的右边,就把cur左边的值托孤给父
                        {
                            parent->_right = cur->_left;
                        }

                    }
                    delete cur;
                }
                else
                {
                    // 找右树最小节点替代,也可以是左树最大节点替代
                    Node* pminRight = cur;
                    Node* minRight = cur->_right;
                    while (minRight->_left)
                    {
                        pminRight = minRight;
                        minRight = minRight->_left;
                    }

                    cur->_key = minRight->_key;

                    if (pminRight->_left == minRight)
                    {
                        pminRight->_left = minRight->_right;
                    }
                    else
                    {
                        pminRight->_right = minRight->_right;
                    }
                    delete minRight;
                }
                return true;
            }
        }
        return false;
    }

4、查找

这个就是直接进行查找就好了,比根节点大就去右边找,小就是左边找,如下代码所示,测试如图。

bool Find(const K& key)
    {
        Node* cur = _root;
        while (cur)
        {
            if (cur->_key > key)
            {
                cur->_left;
            }
            else if(cur->_key<key)
            {
                cur->_right;
            }
            else
            {
                cout << cur->_key << endl;
                return true;
            }
        }
        return false;
    }

四、二叉搜索树的递归实现

1、插入

这里世界利用递归去插入,如果为空就创建一个节点,没有的判断是否比根小,小的话就去左边递归,大的就去右边递归,因为这个需要root节点所以和上面所说的中序一样进行利用函数进行使用直接传递key值就可以了,如下代码,这里的测试放在最后了,和上面测试一样的。

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

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

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 _EraseR(root->_right, key);
        }
        else if (root->_key > key)
        {
            return _EraseR(root->_left, key);
        }
        else
        {
            Node* del = root;

            // 开始准备删除
            if (root->_right == nullptr)
            {
                root = root->_left;
            }
            else if (root->_left == nullptr)
            {
                root = root->_right;
            }
            else
            {
                Node* maxleft = root->_left;
                while (maxleft->_right)
                {
                    maxleft = maxleft->_right;
                }

                swap(root->_key, maxleft->_key);

                return _EraseR(root->_left, key);
            }

            delete del;

            return true;
        }
    }

3、查找

这个递归的查找就是判断,如果没有找到节点为空的时候就返回false,找到了就返回true,然后大于的话就去左边,小于就去右边,如下方代码所示,测试如图。

bool FindR(const K& key)
    {
        return _FindR(_root, key);
    }

    bool _FindR(Node* root, const K& key)
    {
        if (root == nullptr)
            return false;

        if (root->_key == key)
            return true;

        if (root->_key < key)
            return _FindR(root->_right, key);
        else
            return _FindR(root->_left, key);
    }

五、代码

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
using namespace std;
#include "BSTree.h"

//void Test()
//{
//	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//	BSTree<int> t1;
//	
//	for (auto e : a)
//	{
//		t1.Insert(e);
//	}
//	t1.Find(8);
//	t1.InOrder();
//	cout << endl;
//
//	for (auto e : a)
//	{
//		t1.Erase(e);
//		t1.InOrder();
//		cout << endl;
//	}
//
//	t1.InOrder();
//	cout << endl;
//}

void Test()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> t1;
	
	for (auto e : a)
	{
		t1.InsertR(e);
	}
	cout<<t1.FindR(8)<<endl;
	t1.InOrder();
	cout << endl;

	for (auto e : a)
	{
		t1.EraseR(e);
		t1.InOrder();
		cout << endl;
	}

	t1.InOrder();
	cout << endl;
}

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

BSTree.h

#pragma once

template<class T>
struct BSTreeNode
{
	BSTreeNode<T>* _left;
	BSTreeNode<T>* _right;
	T _key;
	BSTreeNode(const T& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};
template<class K>
class BSTree
{
public:
	typedef BSTreeNode<K> Node;
	BSTree() = default; // 制定强制生成默认构造

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

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

	~BSTree()
	{
		Destroy(_root);
		//_root = nullptr;
	}
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				cur->_left;
			}
			else if(cur->_key<key)
			{
				cur->_right;
			}
			else
			{
				cout << cur->_key << endl;
				return true;
			}
		}
		return false;
	}
	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//找到相等了
			{
				//左为空
				if (cur->_left == nullptr)
				{
					if (cur == _root)//如果是根节点,就是左边都是空,只有右边的值
					{
						_root = cur->_right;
					}
					else//不是根节点
					{
						if (parent->_left == cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父
						{
							parent->_left = cur->_right;
						}
						else//如果cur在父节点的右边,就把cur左边的值托孤给父
						{
							parent->_right = cur->_right;
						}

					}
					delete cur;
				}
				//右为空
				else if (cur->_right == nullptr)
				{
					if (cur == _root)//如果是根节点,右边都是空,只有左边有值
					{
						_root = cur->_left;
					}
					else//不是根节点
					{
						if (parent->_left = cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父
						{
							parent->_left = cur->_left;
						}
						else//如果cur在父节点的右边,就把cur左边的值托孤给父
						{
							parent->_right = cur->_left;
						}

					}
					delete cur;
				}
				else
				{
					// 找右树最小节点替代,也可以是左树最大节点替代
					Node* pminRight = cur;
					Node* minRight = cur->_right;
					while (minRight->_left)
					{
						pminRight = minRight;
						minRight = minRight->_left;
					}

					cur->_key = minRight->_key;

					if (pminRight->_left == minRight)
					{
						pminRight->_left = minRight->_right;
					}
					else
					{
						pminRight->_right = minRight->_right;
					}
					delete minRight;
				}
				return true;
			}
		}
		return false;
	}
	void InOrder()
	{
		_InOrder(_root);
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << ' ';
		_InOrder(root->_right);
	}

	void Destroy(Node*& root)
	{
		if (root == nullptr)
			return;

		Destroy(root->_left);
		Destroy(root->_right);

		delete root;
		root = nullptr;
	}

	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
			return false;

		if (root->_key == key)
			return true;

		if (root->_key < key)
			return _FindR(root->_right, key);
		else
			return _FindR(root->_left, key);
	}

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

	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 _EraseR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _EraseR(root->_left, key);
		}
		else
		{
			Node* del = root;

			// 开始准备删除
			if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else
			{
				Node* maxleft = root->_left;
				while (maxleft->_right)
				{
					maxleft = maxleft->_right;
				}

				swap(root->_key, maxleft->_key);

				return _EraseR(root->_left, key);
			}

			delete del;

			return true;
		}
	}
private:
	Node* _root = nullptr;
};

六、思维导图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1602524.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java的maven项目导入本地jar包的三种方式

一、使用本地jar包 在项目中创建一个lib文件夹&#xff0c;将想要使用的本地jar包放进去 然后直接在pom.xml中添加下列依赖&#xff08;项目协作推荐&#xff09; <dependency><groupId>com.fpl</groupId><artifactId>spring</artifactId><…

牛客NC197 跳跃游戏(一)【中等 动态规划 Java、Go、PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/23407eccb76447038d7c0f568370c1bd 思路 答案说的merge区间就是每个A[i]的地方能跳到的最远坐标是A[i] [i]&#xff0c; 有一个maxReach&#xff0c;遍历一遍A[i], 不断刷新MaxReach, 如果某个i 位置比maxReac…

你觉得职场能力重要还是情商重要?

职场能力和情商都是职业成功的关键因素&#xff0c;它们在不同的情境和角色中扮演着不同的作用。很难简单地说哪一个更重要&#xff0c;因为它们通常是相辅相成的。 职场能力包括专业技能、知识水平、解决问题的能力、工作效率、创新思维等。这些能力是完成工作任务、达成职业目…

通讯录的实现(顺序表)

前言&#xff1a;上篇文章我们讲解的顺序表以及顺序表的具体实现过程&#xff0c;那么我们的顺序表在实际应用中又有什么作用呢&#xff1f;今天我们就基于顺序表来实现一下通讯录。 目录 一.准备工作 二.通讯录的实现 1.通讯录的初始化 2.插入联系人 3.删除联系人 4.…

一篇文章详细介绍Stable Diffusion模型原理及实现过程(附常用模型网站、下载方式)

目录 前言 何为Stable Diffusion模型&#xff1f; Stable Diffusion工作原理&#xff1a; Stable Diffusion模型的应用场景 Stable Diffusion免费使用网站 stability.ai: 本地部署 Stable Diffusion方法&#xff1a; StableDiffusion中文网 博主介绍&#xff1a;✌专注于前后端…

任务管理与守护进程

1.前台进程与后台进程 1.1守护进程 在上一章中&#xff0c;我们实现了一个Tcp服务器&#xff0c;但是这个服务器还存在一些问题&#xff0c;例如&#xff0c;我们将云服务器&#xff08;xshell&#xff09;关闭之后&#xff0c;服务器就无法使用了。 但是真正的服务器肯定不…

Stable Diffusion WebUI 控制网络 ControlNet 插件实现精准控图-详细教程

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 大家好&#xff0c;我是水滴~~ 本文主要介绍 Stable Diffusion WebUI 一个比较重要的插件 ControlNet&#xff08;控制网络&#xff09;&#xff0c;主…

第46篇:随机存取存储器(RAM)模块<五>

Q&#xff1a;本期我们使用Quartus软件的IP Catalog工具创建双端口RAM。 A&#xff1a;前期创建的RAM存储模块只有一个端口&#xff0c;同时为读/写操作提供地址。我们将再创建一个具有两个地址输入端口的RAM模块&#xff0c;分别为读操作和写操作提供地址。选择Basic Functio…

Ubuntu:VSCode中编译运行C++代码

版本&#xff1a;Ubuntu22.04.1 LTS 目录 1 安装VSCode并汉化 2 检查Ubuntu是否已经安装了 GCC 3 在VScode中安装C/C扩展 4 在VSCode中进行C/C配置 1 安装VSCode并汉化 安装VSCode&#xff08;参考之前博客Ubuntu&#xff1a;安装VSCode_ubuntu vscode-CSDN博客&#xff…

两数相加(链表)

2. 两数相加 - 力扣&#xff08;LeetCode&#xff09; 题解 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。…

深入理解 pytest Fixture 方法及其应用

当涉及到编写自动化测试时&#xff0c;测试框架和工具的选择对于测试用例的设计和执行非常重要。在Python 中&#xff0c;pytest是一种广泛使用的测试框架&#xff0c;它提供了丰富的功能和灵活的扩展性。其中一个很有用的功 能是fixture方法&#xff0c;它允许我们初始化测试环…

Ypay源支付最新免授权牛角魔改版

YPay是专为个人站长打造的聚合免签系统&#xff0c;拥有卓越的性能和丰富的功能。采用全新轻量化的界面UI&#xff0c;让您可以更加方便快捷地解决知识付费和运营赞助的难题。同时&#xff0c;它基于高性能的ThinkPHP 6.1.2 Layui PearAdmin架构&#xff0c;提供实时监控和管…

【JavaWeb】Day47.Mybatis基础操作——删除

Mybatis基础操作 需求 准备数据库表 emp 创建一个新的springboot工程&#xff0c;选择引入对应的起步依赖&#xff08;mybatis、mysql驱动、lombok&#xff09; application.properties中引入数据库连接信息 创建对应的实体类 Emp&#xff08;实体类属性采用驼峰命名&#xf…

反转二叉树(力扣226)

解题思路&#xff1a;用队列进行前序遍历的同时把节点的左节点和右节点交换 具体代码如下&#xff1a; class Solution { public:TreeNode* invertTree(TreeNode* root) {if (root NULL) return root;swap(root->left, root->right); // 中invertTree(root->left)…

2022年团体程序设计天梯赛-总决赛 L1 -L2

目录 L1-1 今天我要赢 L1-2 种钻石 L1-3 谁能进图书馆 L1-4 拯救外星人 L1-5 试试手气 L1-6 斯德哥尔摩火车上的题 L1-7 机工士姆斯塔迪奥 L1-8 静静的推荐 L2-1 插松枝 L2-2 老板的作息表 L2-3 龙龙送外卖 L2-4 大众情人 L1-1 今天我要赢 分数 5 2018 年我们曾经出…

时间同步服务器的搭建

1.配置server主机&#xff1a; ①server主机的主机名称为 ntp_server.example.com reboot重启 ②server主机的IP为&#xff1a; 172.25.254.100 ③server主机的时间为1984-11-11 11&#xff1a;11&#xff1a;11 先关闭时间同步服务 设置时间为1984-11-11 11&#xff1a;11&…

vue3父组件使用子组件方法

问题 关于父组件调用子组件方法是比较常见的情况&#xff0c;vue2中使用比较简单&#xff0c;那么vue3 中如何使用呢&#xff1f; 想要的效果&#xff1a; vue2 中调用子组件方法 先看下vue2中如何调用的&#xff0c;代码如下&#xff1a; // child.vue <template>&…

Linux系统的引导过程与服务控制

目录 一、Linux操作系统引导过程 二、Linux系统服务控制 系统初始化进程 三、运行级别切换 *运行级别及切换 Linux系统的运行级别 四、优化开机自动加载服务 五、修复MBR扇区故障 一、Linux操作系统引导过程 主要步骤 开机自检&#xff1a; 检测硬件设备&#…

Matlab软件使用教学

1. Matlab简介 Matlab&#xff08;Matrix Laboratory的缩写&#xff09;是一种由MathWorks公司开发的数值计算和可视化编程环境。它广泛应用于工程、科学研究、数学和教育等领域&#xff0c;因其强大的计算能力和丰富的工具箱而受到青睐。 2. 安装与启动 安装&#xff1a;从M…

Delphi Xe 10.3 钉钉SDK开发——审批流接口(获取表单ProcessCode)

开发钉钉审批流时&#xff0c;需要用到钉钉表单的Processcode&#xff0c;有两种方法 &#xff1a; 一、手动获取&#xff1a; 管理员后台——审批——找到对应的表单&#xff1a;如图&#xff1a; ProcessCode后面就是了&#xff01; 二、接口获取&#xff1a;今天的重点&a…