数据结构之BinaryTree(二叉树)的实现

news2024/10/6 4:12:01

BinaryTree要实现的方法

总结

  1. remove不在BinNode里,而是BinTree里

  2. 递归的两种写法
    从上往下:同一对象的递归(参数多一个,判空用一句话),子对象的递归(参数void,判空用两句话)(因为分叉,所以递归是真递归)
    从下往上:不分叉,为了效率,可以用循环

// 我的最初写法(递归更新一条路径上的全部节点high值)
template <typename T>
void BinNode<T>::updateHigh(void)
{
	int oldHigh = high;
	high = std::max(getHigh(left), getHigh(right)) + 1;
	if (parent != NULL && oldHigh != high) parent->updateHigh();
}

// 调用
rt->updateHigh();
// 改良版(循环更新一条路径上的全部节点high值)
template <typename T>
void BinTree<T>::updateHighAbove(BinNode<T> * rt)
{
	while (rt)
	{
		if (!rt->updateHigh()) break; //这是书里说的优化,但书中并未实现
		rt = rt->parent;
	}
}
template <typename T>
bool BinNode<T>::updateHigh(void) //返回是否更新了树高
{
	int oldHigh = high;
	high = std::max(getHigh(left), getHigh(right)) + 1;
	return oldHigh != high;
}
// 调用
updateHighAbove(rt);
  1. 整棵树与根节点的区别:整棵树的类型是BinTree 而根节点的类型是BinNode。二者都有成员变量high和size改了根节点的size,没改整棵树的size

  2. 发现high也有上一条的问题。书上BinTree没有设置high,BinNode没有size,那就删掉,省的单独更新

  3. 为什么remove写两个,因为切断来自parent的指针更新高度不用层层进行,还是为了提高效率。remove_core负责递归,remove在最外层负责切断来自parent的指针更新高度

  4. remove如果rt是根节点那么不进行切断来自parent的指针更新高度,析构函数要用

  5. 静态成员函数可以不依附于具体对象来调用。为了调用时简洁。静态成员函数就很像是面向过程

code BinNode

# pragma once
# include <algorithm>
# define getHigh(x) x ? x->high : -1

// 仿函数:打印功能
template <typename T>
struct print {
	void operator ()(T data)
	{
		std::cout << data << std::endl;
	}
};

template <typename T>
struct BinNode {

	int high;
	T data;

	BinNode<T> * left;
	BinNode<T> * right;
	BinNode<T> * parent;
	
	int Size(BinNode<T> * x)
	{
		if (x)
		{
			return 1 + Size(x->left) + Size(x->right);
		}
		return 0;
	}

	BinNode(T const & d, BinNode<T> * p) : data(d), parent(p), left(NULL), right(NULL), size(1), high(0) {}

	BinNode<T> * insertAsLeft(T const & val)
	{
		left = new BinNode<T>(val, this);
		return left;
	}

	BinNode<T> * insertAsRight(T const & val)
	{
		right = new BinNode<T>(val, this);
		return right;
	}

	bool updateHigh(void)
	{
		int oldHigh = high;
		high = std::max(getHigh(left), getHigh(right)) + 1;
		return oldHigh != high;
	}

	template <typename T2>
	void travPre(T2 visit)
	{
		visit(data);
		if (left) left->travPre(visit);
		if (right) right->travPre(visit);
	}

	template <typename T2>
	void travIn(T2 visit)
	{
		if (left) left->travIn(visit);
		visit(data);
		if (right) right->travIn(visit);
	}

	template <typename T2>
	void travPost(T2 visit)
	{
		if (left) left->travPost(visit);
		if (right) right->travPost(visit);
		visit(data);
	}
};

code BinTree

# pragma once
# include "BinNode.h"

template <typename T>
class BinTree
{
protected:
public:
	int size;
	BinNode<T> * root;

public:
	BinTree():size(0), root(NULL){}
	~BinTree() { if (root) remove(root); }

	//***********************************************************只读*********************************************************

	int Size(void) const { return size; }
	bool empty(void) const { return size == 0; }
	BinNode<T> * Root(void) const { return root; }

	//***********************************************************可写*********************************************************

	// 节点插入
	BinNode<T> * insertAsRoot(T const & e)
	{
		size = 1;
		root = new BinNode<T>(e, NULL);
		return root;
	}

	BinNode<T> * insertAsLeft(T const & e, BinNode<T> * rt)
	{
		++size;
		rt->insertAsLeft(e);
		updateHighAbove(rt);
		return rt->left;
	}

	
	BinNode<T> * insertAsRight(T const & e, BinNode<T> * rt)
	{
		++size;
		rt->insertAsRight(e);
		updateHighAbove(rt);
		return rt->right;
	}

	// 子树接入,返回接入位置rt
	BinNode<T> * attachAsLeft(BinTree<T> * newSubtree, BinNode<T> * rt)
	{
		size += newSubtree->size;
		rt->left = newSubtree->root;
		updateHighAbove(rt);

		// 清空newSubtree原来的东西
		newSubtree->size = 0;
		newSubtree->root = NULL;
		return rt;
	}

	BinNode<T> * attachAsRight(BinNode<T> * newSubtree, BinNode<T> * rt)
	{
		size += newSubtree->size;
		rt->right = newSubtree->root;
		updateHighAbove(rt);

		// 清空newSubtree原来的东西
		newSubtree->size = 0;
		newSubtree->root = NULL;
		return rt;
	}

	int remove(BinNode<T> * rt)
	{
		// 切断来自parent的指针
		fromParentTo(rt) = NULL;

		// 更新高度
		updateHighAbove(rt->parent);

		int ans = remove_core(BinNode<T> * rt);

		size -= ans
		return ans;
	}

	int remove_core(BinNode<T> * rt)
	{
		if (!rt) return 0; // 递归出口
		int ans = remove(rt->left) + remove(rt->right) + 1;
		delete rt;
		return ans;
	}

	BinTree<T> * secede(BinNode<T> * rt) // 先不考虑 如果rt是树根
	{
		// 切断来自parent的指针
		fromParentTo(rt) = NULL;

		size -= BinNode<T>::Size(rt);

		BinTree<T> * newTree = new BinTree<T>();
		newTree->root = rt;
		rt->parent = NULL;

		updateHighAbove(rt->parent);
		
		return newTree;
	}

	void updateHighAbove(BinNode<T> * rt)
	{
		while (rt)
		{
			if (!rt->updateHigh()) break;
			rt = rt->parent;
		}
	}

	//***********************************************************遍历*********************************************************
	template <typename T2>
	void travPre(T2 visit)
	{
		if (root) root->travPre(visit);
	}

	template <typename T2>
	void travIn(T2 visit)
	{
		if (root) root->travIn(visit);
	}

	template <typename T2>
	void travPost(T2 visit)
	{
		if (root) root->travPost(visit);
	}

private:
	BinNode<T> * & fromParentTo(BinNode<T> * x)
	{
		if (x == x->parent->left) return x->parent->left;
		else return x->parent->right;
	}
};

测试程序(树的结构如图)

在这里插入图片描述

# include <iostream>
# include "BinTree.h"

int main(void)
{
	BinTree<char> T1;
	BinNode<char> * pA = T1.insertAsRoot('A');
	BinNode<char> * pB = T1.insertAsLeft('B', pA);
	BinNode<char> * pG = T1.insertAsLeft('G', pB);
	BinNode<char> * pC = T1.insertAsRight('C', pA);
	BinNode<char> * pE = T1.insertAsRight('E', pC);
	BinNode<char> * pH = T1.insertAsRight('H', pG);
	BinNode<char> * pD = T1.insertAsLeft('D', pC);
	BinNode<char> * pF = T1.insertAsLeft('F', pD);

	print<int> p;
	std::cout << T1.Size() << '\n';
	// 高度测试
	std::cout << "根高度:";  std::cout << T1.Root()->high; std::cout << "\n";
	std::cout << "C高度:";  std::cout << pC->high; std::cout << "\n";
	// 遍历测试
	std::cout << "后序遍历:"; T1.travPost(p); std::cout << "\n";
	std::cout << "中序遍历:"; T1.travIn(p); std::cout << "\n";
	// 移除测试
	std::cout << "删掉:" << T1.remove(pD) << "个节点" << '\n';
	std::cout << "C高度:";  std::cout << pC->high; std::cout << "\n";
	std::cout << "中序遍历:"; T1.travIn(p); std::cout << "\n";
	std::cout << T1.Size() << '\n';
	// 切断测试
	BinTree<char> * pT2 = T1.secede(pC);
	std::cout << "中序遍历T1:"; T1.travIn(p); std::cout << "\n";
	std::cout << "中序遍历T2:"; pT2->travIn(p); std::cout << "\n";
	std::cout << T1.Size() << '\n';
	std::cout << pT2->Size() << '\n';

	return 0;
}

输出

8
根高度:3
C高度:2
后序遍历:H G B F D E C A
中序遍历:G H B A F D C E
删掉:2个节点
C高度:1
中序遍历:G H B A C E
6
中序遍历T1:G H B A
中序遍历T2:C E
4
2

迭代遍历

上面的代码是递归的遍历。现在再写迭代的前序、中序、后序遍历。

尾递归

前序遍历的递归代码,左右子树都属于尾递归。中序遍历的递归代码,右子树属于尾递归。而后序遍历完全不属于尾递归。所以后序遍历比前、中序遍历复杂得多。要理解这句话,还要先了解尾递归转成迭代的方法,而这也是编译器在编译之前常做的优化手段。

以汉诺塔为例,理解递归转迭代

BinTree 的三个遍历接口不变,只需修改BinNode 的遍历函数。

travPre迭代1:直接转化(但不可推广到中、后序)

仅说思路:根节点进栈。当栈非空:出栈访问,然后右子树入栈,左子树入栈。

这个完全模拟了尾递归时编译器的操作。至于为什么先右后左,因为这样出栈时才能先左后右。

(汉诺塔那篇文章如果你把结果打印出来,发现迭代的结果跟递归结果是反过来的,如果将入栈的两句话交换位置,那么输出结果一模一样)

travPre迭代2:新思路(可推广)

我们通过仔细观察travPre,得到前序遍历的迭代算法。

	template <typename T2>
	void travPre(T2 visit)
	{
		// 出栈,访问
		visit(data);
		
		// 左侧链一路访问到底
		if (left) left->travPre(visit);
		
		// 每访问一个左孩子,将对应右孩子进栈备用
		if (right) right->travPre(visit);
	}

迭代2的算法

	template <typename T2>
	void travPre2(T2 visit)
	{
		// 出栈顶。右孩子进栈,访问左孩子。转到左孩子,重复上一步。直到没有左孩子,出栈顶。
		BinNode<T> * current;
		Stack<BinNode<T>*> s;
		s.push(this);
		while (!s.empty())
		{
			current = s.pop();		
			while (current)
			{
				visit(current->data);
				if (current->right) s.push(current->right);
				current = current->left;
			}
		}
	}

travIn迭代

按图索骥,继续先观察递归算法

	void travIn(T2 visit)
	{
		// 沿左侧链一路向左进栈
		if (left) left->travIn(visit);
		
		// 没有左子树时,出栈访问
		visit(data);
		
		// 访问完马上转到右儿子,进一个栈
		if (right) right->travIn(visit);
	}

travIn迭代算法

	template <typename T2>
	void travIn2(T2 visit)
	{
		// 左孩子进栈,没有左孩子时,出栈顶访问,一个右孩子进栈,右孩子的左孩子到底进栈
		BinNode<T> * current = this;
		Stack<BinNode<T>*> s;
		while (current)
		{
			s.push(current);
			current = current->left;
		}
		while (!s.empty())
		{
			current = s.pop();
			visit(current->data);
			current = current->right;

			while (current)
			{
				s.push(current);
				current = current->left;
			}
		}
	}

travPost迭代

比较复杂。邓老师将其描述为,藤绕树。先找藤的头。

善后工作

为了调用更加友好,BinTree 的三个遍历接口加一个tag参数,缺省为0,宏定义ITER(意为迭代)为1

	template <typename T2>
	void travPre(T2 visit, int tag = 0)
	{
		if (root)
		{
			if (tag & ITER) root->travPre2(visit);
			else root->travPre(visit);
		}
	}

	template <typename T2>
	void travIn(T2 visit, int tag = 0)
	{
		if (root)
		{
			if (tag & ITER) root->travIn2(visit);
			else root->travIn(visit);
		}
	}

	template <typename T2>
	void travPost(T2 visit, int tag = 0)
	{
		if (root)
		{
			if (tag & ITER) root->travPost2(visit);
			else root->travPost(visit);
		}
	}

theeeend ₍ᐢ…ᐢ₎♡

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

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

相关文章

算法分析和设计简答题

算法分析和设计简答题 1.1分治法的算法思想&#xff08;重点&#xff09; 1.2动态规划的算法思想&#xff08;重点&#xff09; 1.3贪心算法的算法思想 1.4回溯算法的算法思想 1.5分支限界法的算法思想 1.6时间复杂度的定义(最好/一般/坏)&#xff0c;有什么意思 1.7渐进记号…

【Git】分支合并冲突产生与解决

文章学习自&#xff1a;麦兜搞IT&#xff0c;如有侵权&#xff0c;告知删除 文章目录 前言1 Fast Forword 合并1.1 核心原理1.2 举个栗子1.3 经验之谈 2 three way merge2.1 核心原理2.2 举个栗子&#xff08;不带冲突&#xff09;2.3 带冲突的three way merge 3 变基rebase3.…

Windows上配置Python环境变量

Python配置环境变量 &#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; Python下载官网&#xff1…

GB28181设备接入端如何播放语音广播数据?

技术背景 语音广播功能是GB28181设备接入端非常重要的功能属性&#xff0c;语音广播让终端和平台之间&#xff0c;有了实时双向互动&#xff0c;可以满足执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、…

Docker介绍以及实战教程

Docker简介 Docker为什么出现 从事软件开发的朋友&#xff0c;可能经常会碰到以下场景&#xff1a;运维&#xff1a;你这程序有Bug啊&#xff0c;怎么跑不起来啊&#xff01;开发&#xff1a;我机子上能跑啊&#xff0c;你会不会用啊究其原因还是开发环境与生产环境不同造成的…

基于linux下的高并发服务器开发(第二章)- 2.18 内存映射(2)

1.如果对mmap的返回值(ptr)做操作(ptr), munmap是否能够成功? void * ptr mmap(...);ptr; 可以对其进行操作munmap(ptr, len); // 错误,要保存地址 2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样? 错误&#xff0c;返回MAP_FAILEDopen()函数中的…

cpolar+calibre搭建自己的kindle书库

cpolarcalibre搭建自己的kindle书库 在我们身边众多的便携电子设备中&#xff0c;Kindle无疑是最为矛盾的设备之一&#xff0c;很多人在买它时都想读书破万卷&#xff0c;可是到最后Kindle的归宿都是盖泡面。尽管如此&#xff0c;当亚马逊不讲武德&#xff0c;打算将Kindle真正…

FPGA中RAM的结构理解

FPGA中RAM的结构理解 看代码的过程中对RAM的结构不是很理解&#xff0c;搞脑子一片浆糊&#xff0c;反复推算&#xff0c;好不容易理清了思路&#xff0c;记录下来&#xff0c;防止忘记。开辟的RAM总容量为128bytes&#xff0c;数据的位宽为32位&#xff08;即一个单元有32bit…

CodeForces:Madoka and Underground Competitions

经过观察&#xff0c;发现只要延小区域 右上-左下 的对角线填满X即可&#xff0c;那么就是可以总结为满足(i j) % k (r c) % k #include <bits/stdc.h> using namespace std; int t; void solve(){int n, k, r, c;cin >> n >> k >> r >> c…

团队如何选择合适的Git分支策略?

现代软件开发过程中要实现高效的团队协作&#xff0c;需要使用代码分支管理工具实现代码的共享、追溯、回滚及维护等功能。目前流行的代码管理工具&#xff0c;包括CVS&#xff0c;SVN&#xff0c;Git&#xff0c;Mercurial等。 相比CVS和SVN的集中管理&#xff0c;Git具有非常…

【stable diffusion】保姆级入门课程03-Stable diffusion(SD)图生图-涂鸦(绘图)的用法

目录 0.本章素材 1.涂鸦有什么用 2.涂鸦的使用场景是什么 3.操作面板 4.提示词与涂鸦 5.涂鸦与重绘幅度 6.涂鸦的其他用法(自由创作) 7.课后训练 0.本章素材 Realistic Vision V3.0模型(真实系模型)百度网盘链接&#xff1a;https://pan.baidu.com/s/1HkSKW2t4L6wMg…

openlayers系列:加载arcgis和geoserver在线离线切片

https://www.freesion.com/article/1751396517/ 1.背景 有个项目需要使用openlayer加载各种服务上发布的数据&#xff0c;坐标系也不同&#xff0c;我们都知道openalyer默认可以加载EPAG:3857,要加载4490的坐标系的数据需要重新定义一下&#xff0c;之后再加载。一想起要重新…

[SQL系列] 从头开始学PostgreSQL 事务 锁 子查询

[SQL系列] 从头开始学PostgreSQL 索引 修改 视图_Edward.W的博客-CSDN博客https://blog.csdn.net/u013379032/article/details/131818865 事务 事务是一系列逻辑相关的数据库操作&#xff0c;可以作为一个整体进行操作或者回滚。事务通常会包含一个序列的读或者写操作&#xf…

No4: Python脚本的交互式运用

No4: Python脚本的交互式运用 1、 先安装了Python和环境变量设置 2、编写Pthon脚本 3、在脚本所在目录位置下&#xff0c;Python 脚本&#xff1b;

Nginx系列之 一 负载均衡

目录 一、Nginx概述 1.1 负载均衡概述 1.2 负载均衡的作用 1.3 四/七层负载均衡 1.3.1 网络模型简介 1.3.2 四层和七层负载均衡对比 1.3.3 Nginx七层负载均衡实现 1.4 Nginx负载均衡配置 1.5 Nginx负载均衡状态 1.6 Nginx负载均衡策略 二、负载均衡实战 2.1 测试服…

论文笔记--Won’t Get Fooled Again: Answering Questions with False Premises

论文笔记--Won’t Get Fooled Again: Answering Questions with False Premises 1. 文章简介2. 文章概括3 文章重点技术3.1 大模型面对FPQs的表现3.2 False QAs数据集3.3 训练和评估 4. 文章亮点5. 原文传送门 1. 文章简介 标题&#xff1a;Won’t Get Fooled Again: Answerin…

LLMs之LLaMA2:LLaMA2的简介(技术细节)、安装、使用方法(开源-免费用于研究和商业用途)之详细攻略

LLMs之LLaMA2&#xff1a;LLaMA2的简介(技术细节)、安装、使用方法(开源-免费用于研究和商业用途)之详细攻略 导读&#xff1a;2023年7月18日&#xff0c;Meta重磅发布Llama 2&#xff01;这是一组预训练和微调的大型语言模型&#xff08;LLM&#xff09;&#xff0c;规模从70亿…

(三)springboot实战——web新特性之函数式实现

前言 本节内容我们主要介绍一下web访问的另一种形式&#xff0c;通过函数式web实现一个restful风格的http请求案例。函数式web是spring5.2之后的一个新特性&#xff0c;可以通过函数去定义web请求的处理流程&#xff0c;使得代码更为简洁&#xff0c;耦合性也降低了。 正文 …

在vue3中配置ByteMD掘金同款markdown编辑器

最近因为想要一个富文本编辑器集合到项目中&#xff0c;在查找网上很多资料后&#xff0c;选择了ByteMD 编辑器&#xff0c;ByteMD 编辑器是字节跳动的掘金团队所开源的一个编辑器组件&#xff0c;还挺好用的&#xff0c;那如果要在vue3项目中配置ByteMD编辑器要如何配置呢&…