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

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 ₍ᐢ…ᐢ₎♡

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

对于awd

最近我们老师直接说要我准备awd&#xff0c;大概率要我上场我就顺便整理一下awd的资料&#xff08;准备写很多所以建议大家收藏一下&#xff09; 攻防指北 先来一个思维导图 Awd竞赛 AWD(Attack With Defense&#xff0c;攻防兼备)是一个非常有意思的模式&#xff0c;你需要…

Nginx配置整合:基本概念、命令、反向代理、负载均衡、动静分离、高可用

一、基本概念 1.什么是Nginx Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;也是一个IMAP/POP3/SMTP代理server。其特点是占有内存少。并发能力强&#xff0c;其并发能力确实在同类型的网页server中表现较好。 http服务器 Web服务器是指驻留于因特网上某种类型计算机的程…

IT技术岗的面试技巧分享

我们在找工作时,需要结合自己的现状,针对意向企业做好充分准备。作为程序员,你有哪些面试IT技术岗的技巧?你可以从一下几个方向谈谈你的想法和观点。 方向一:分享你面试IT公司的小技巧 1、事先和邀约人了解公司的基本情况,比如公司的行业,规模,研发人员占比等 2、事先和…

cmder 使用简介

文章目录 1. cmder 简介2. 下载地址3. 安装4. 配置环境变量5. 添加 cmder 到右键菜单6. 解决中文乱码问题 1. cmder 简介 cmder 是一个增强型命令行工具&#xff0c;不仅可以使用 windows 下的所有命令&#xff0c;更爽的是可以使用 linux的命令, shell 命令。 2. 下载地址 …

【C#】MVC页面常见的重定向方式和场景

本篇文章主要简单讲讲&#xff0c;C# MVC 页面常见跳转或者重定向的方式和场景。 在实际项目开发中&#xff0c;在一些特定场景肯定会用到重定向&#xff0c;比如&#xff1a;不同角色跳转到不同视图地址 目录 一、种常见重定向方式1.1、RedirectToAction1.2、RedirectToRoute1…

【算法 -- LeetCode】(025) K 个一组翻转链表

1、题目 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点…

记录一次抓取WiFi驱动日志以及sniffer日志

起因 路由器桥接一个WiFi&#xff0c;然后设备连接这个路由器的WiFi&#xff0c;发现网络不可用&#xff0c;而手机或者电脑连接就没问题&#xff0c;与供应商沟通问题&#xff0c;需要抓取日志&#xff0c;记录一下 抓取WLAN DRIVER WLAN FW3日志 进入开发者模式打开启动WL…

MQTT 与 Kafka|物联网消息与流数据集成实践

MQTT 如何与 Kafka 一起使用&#xff1f; MQTT (Message Queuing Telemetry Transport) 是一种轻量级的消息传输协议&#xff0c;专为受限网络环境下的设备通信而设计。Apache Kafka 是一个分布式流处理平台&#xff0c;旨在处理大规模的实时数据流。 Kafka 和 MQTT 是实现物…

数据结构和算法——快速排序(算法概述、选主元、子集划分、小规模数据的处理、算法实现)

目录 算法概述 图示 伪代码 选主元 子集划分 小规模数据的处理 算法实现 算法概述 图示 快速排序和归并排序有一些相似&#xff0c;都是用到了分而治之的思想&#xff1a; 伪代码 通过初步的认识&#xff0c;我们能够知道快速排序算法最好的情况应该是&#xff1a; 每…

keil5编辑器主题配色美化使用(附六款暗黑主题)

一、通过配置文件修改主题 1、在软件安装目下备份以下三个文件&#xff0c;更换主题只需要替换global.prop arm.propglobal.propglobal.prop.def 2、替换配置文件 将已经准备好的配色文件复制到\UV4下替换 https://download.csdn.net/download/qq_43445867/88064961 Theme1…

【湍流介质的三维传播模拟器】全衍射3-D传播模拟器,用于在具有随机和背景结构的介质中传播无线电和光传播(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码实现 &#x1f4a5;1 概述 全衍射3-D传播模拟器是一种用于模拟在具有随机和背景结构的介质中传播无线电和光的工具。它可以帮助研究人员和工程师理解和预测无线电波和光波…

【数据可视化】基于Python和Echarts的中国经济发展与人口变化可视化大屏

1.题目要求 本次课程设计要求使用Python和ECharts实现数据可视化大屏。要求每个人的数据集不同&#xff0c;用ECharts制作Dashboard&#xff08;总共至少4图&#xff09;&#xff0c;要求输入查询项&#xff08;地点和时间&#xff09;可查询数据&#xff0c;查询的数据的地理…

IDEA+SpringBoot +ssm+ Mybatis+easyui+Mysql求职招聘管理系统网站

IDEASpringBoot ssm MybatiseasyuiMysql求职招聘管理系统网站 一、系统介绍1.环境配置 二、系统展示1. 登录2.注册3.首页4.公司5.关于我们6.我的简历7.我投递的简历8.修改密码9. 管理员登录10.我的信息11.用户信息12.职位类别13. 职位列表14. 公司列表15. 日志列表 三、部分代码…

【高阶数据结构】跳表

文章目录 一、什么是跳表二、跳表的效率如何保证&#xff1f;三、skiplist的实现四、skiplist跟平衡搜索树和哈希表的对比 一、什么是跳表 skiplist本质上也是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff0c;跟平衡搜索树和哈希表的价值是 一样的&#xff0c;可…

2321. 拼接数组的最大分数;768. 最多能完成排序的块 II;2192. 有向无环图中一个节点的所有祖先

2321. 拼接数组的最大分数 核心思想&#xff1a;数学思维。假设nums1的和为a0a1a2a3...an-1 sum(nums1),nums2的和为b0b1b2b3...bn-1 sum(nums2),交换al...ar与bl..br&#xff0c;现在nums1的和要最大&#xff0c;则s sum(nums1) (br-ar)(br-1-ar-1)...(bl-al),所以你要使…

MATLAB遗传算法求解带容量约束的物流配送选址问题实例

MATLAB遗传算法求解带容量约束的物流配送选址问题实例 作者&#xff1a;麦哥爱西芹 MATLAB遗传算法求解带容量约束物流配送中心选址问题代码实例 遗传算法编程问题实例&#xff1a; 在经度范围为(116, 118)&#xff0c;纬度范围为(38, 40)的矩形区域内&#xff0c;散布着37个需…

物联网大数据传输安全难题与解决方案

随着物联网时代的到来&#xff0c;大数据传输变得更加频繁和庞大&#xff0c;同时也给传输安全带来了更高的风险和挑战。本文将探讨物联网时代的大数据传输安全问题&#xff0c;并介绍镭速传输如何有效地解决这些问题。 首先&#xff0c;物联网时代的大数据传输面临的一个主要问…

LeetCode[148]排序链表

难度&#xff1a;Medium 题目&#xff1a; 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&…

nosql作业

nosql作业 文章目录 作业一&#xff1a;string list hash结构中&#xff0c;每个至少完成5个命令&#xff0c;包含插入 修改 删除 查询&#xff0c;list 和hash还需要增加遍历的操作命令1、 string类型数据的命令操作&#xff1a;2、 list类型数据的命令操作&#xff1a;3、 ha…

Oracle 普通视图 (Oracle Standard Views)

视图&#xff08;views&#xff09;是一种基于表的"逻辑抽象"对象&#xff0c;由于它是从表衍生出来的&#xff0c;因此和表有许多相同点&#xff0c;我们可以和对待表一样对其进行查询/更新操作。但视图本身并不存储数据&#xff0c;也不分配存储空间。 本文只讨论普…