当前位置: 首页 > news >正文

用数组实现树的存储遍历【复习笔记】

1. 树的基本概念

1.1 树的定义和术语

树是由 n(n≥0)个有限节点组成的一个具有层次关系的集合。当 n=0 时,称为空树。在一棵非空树中,有且仅有一个特定的节点称为根节点;其余节点可分为 m 个互不相交的有限集 T1、T2、…、Tm,其中每个集合本身又是一棵树,并且称为根结点的子树

因此,树是递归定义的

节点的度:一个节点拥有的子树个数称为该节点的度。度为 0 的节点称为叶子节点或终端节点;度不为 0 的节点称为分支节点。

树的度:树中节点的最大度数称为树的度。

路径:树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,路径长度为序列中 边的个数

节点的层次:从根节点开始定义,根节点为第 1 层,根的子节点为第 2 层,以此类推,若某节点在第 i 层,则其孩子节点在第 i + 1 层。

树的高度或深度:树中节点的最大层次数称为树的高度或深度。

1.2 树的种类

有序树:结点的子树按照从左往右的顺序排列,不能更改

无序树:结点的子树没有顺序,可以更改

有根树:根节点已知

无根树:根节点未知,都可以是根节点

1.3 树的存储

树存储时,要保存值域,也要保存结点和结点的关系。存储方式有多种:双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法......

1.3.1 孩子表示法

将每个结点的孩子信息存储下来(无根树中,父子关系不明,都存储)

假设树存储树时有n条信息,第一条为n个结点,接下来n-1行,每行两个树x,y表示x和y间有一条边

1. 使用 vector 数组

​
#include<iostream>
#include<vector>
using namespace std;

int n;
const int N = 1e5 + 10;
//创建N个vector数组,edges[i]中保存i号结点的孩子信息
vector<int> edges[N];

int main()
{
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int x, y; cin >> x >> y;
		//x和y间有一条边
		edges[x].push_back(y);
		edges[y].push_back(x);
	}
	return 0;
}

​

2. 链式前向星(使用数组实现的链表)

链式前向星的本质是用数组来模拟链表

#include<iostream>
using namespace std;

int n;
const int N = 1e5 + 10;
//一条边的两个数据存储两遍,所有范围扩大2倍
int e[2 * N], ne[2 * N];
//h[N]表示每个父亲结点的集合
int h[N];
int id;

void put(int x, int y)
{
	//先将y存储进数组
	id++;
	e[id] = y;
	//将y进行头插进入父亲结点后
	ne[id] = h[x];
	//头标记移动
	h[x] = id;
}

int main()
{
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int x, y; cin >> x >> y;
		put(x, y); put(y, x);
	}
	return 0;
}

2. 树的遍历

树的遍历方式有两种,分别为深度优先遍历宽度优先遍历

2.1 深度优先遍历(DFS)

从根结点出发,依次遍历每一颗子树;遍历子树时,重复第一步。

由此可以看出,深度优先遍历是一种递归形式

案例:

题目描述:给一棵树,共 n 个结点,编号 1~n

输入描述:第一行一个整数 n ,表示 n 个结点

                  接下来 n-1 行,每行两个整数 x,y,表示 x 和 y 间有一条边

1. 在 vector 存储下实现

​
#include<iostream>
using namespace std;
#include<vector>

const int N = 1e5 + 10;
int n;
//定义布尔数组进行标记
bool st[N];
vector<int> edges[N];

void dfs(int x)
{
	//先打印该父结点
	cout << x << " ";
	//标记为打印过了
	st[x] = true;
    //依次遍历子结点
	for (auto u : edges[x])
	{
		//如果没打印过,再次深度优先遍历
		if (!st[x]) dfs(u);
	}
}

int main()
{
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int x, y; cin >> x >> y;
		edges[x].push_back(y);
		edges[y].push_back(x);
	}

	//进行深度优先遍历
	 dfs(1);
    return 0;
}

​

2. 在链式前向星存储下实现

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int e[2 * N], ne[2 * N];
int h[N];
bool st[N];
int n;
int id;

void put(int x, int y)
{
	id++;
	e[id] = y;

	ne[id] = h[x];
	h[x] = id;
}

void dfs(int x)
{
	cout << x << " ";
	st[x] = true;
	//遍历每一个孩子
	for (int i = h[x]; i; i = ne[i])
	{
		//递归
		int v = e[i];
		if (!st[v]) dfs(v);
	}

	
}

int main()
{
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int x, y; cin >> x >> y;
		put(x, y); put(y, x);
	}

	//DFS
	dfs(1);
	return 0;
}

dfs要将树的边扫描两边,边数量为 n-1 ,所以时间复杂度为O(N)

最坏情况,N个结点的树高也为N,此时深度为N,所以空间复杂度为O(N)

2.2 宽度优先遍历(BFS)

每次访问同一层的结点,同一层访问完再访问下一层

可以用队列实现:先初始化一个队列,根节点入队,同时标记;当队列不为空,依次拿出头元素访问,将队头元素的所有孩子入队,标记;重复该过程

案例:

题目描述:给一棵树,共 n 个结点,编号 1~n

输入描述:第一行一个整数 n ,表示 n 个结点

                  接下来 n-1 行,每行两个整数 x,y,表示 x 和 y 间有一条边

1.  用 vector 实现储存

#include<queue>
#include<iostream>
#include<vector>
using namespace std;
const int N = 1e5 + 10;
bool st[N];
vector<int> edges[N];
int n;

void bfs()
{
	queue<int> q;
	q.push(1);
	st[1] = true;
	while (q.size())
	{
		//父亲出队
		auto u = q.front(); q.pop();
		cout << u << " ";
		//孩子入队
		for (auto v : edges[u])
		{
			if (!st[v])
			{
				q.push(v);
				st[v] = true;
			}
		}
	}
}

int main()
{
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int x, y; cin >> x >> y;
		edges[x].push_back(y);
		edges[y].push_back(x);
	}

	bfs();
	return 0;
}

2. 链式前向星储存

#include<iostream>
using namespace std;
#include<queue>
const int N = 1e5 + 10;
int e[2 * N], ne[2 * N];
int h[N];
int n;
int id;
bool st[N];

void put(int x, int y)
{
	id++;
	e[id] = y;

	ne[id] = h[x];
	h[x] = id;
}

void bfs()
{
	queue<int> q;
	q.push(1);
	st[1] = true;

	while (q.size())
	{
		auto u = q.front(); q.pop();
		cout << u << " ";
		//遍历孩子
		for (int i = h[u]; i; i = ne[i])
		{
			int v = e[i];
			if (!st[v])
			{
				q.push(v);
				st[v] = true;
			}
		}
	}
}

int main()
{
	cin >> n;
	for (int i = 1; i < n; i++)
	{
		int x, y; cin >> x >> y;
		put(x, y); put(y, x);
	}

	bfs();
	return 0;
}

所有结点入队一次,出队一次,时间复杂度O(N)

最坏情况下,所有非根结点同一层,队列最多 n - 1 个元素,空间复杂度O(N)

相关文章:

  • 3DUNet-Pytorch-master环境配置(3dunet)
  • (IDE接入DeepSeek)简单了解DeepSeek接入辅助开发与本地部署建议
  • 【前端基础】Day 2 CSS层叠样式表
  • 说一下 SpringMVC的运行流程?
  • 代码随想录算法【Day54】
  • 系统架构设计:软件测试需要掌握的常用方法
  • pytorch阶段性总结1
  • 前端模拟请求池-浏览器同时发起大量请求
  • mysql 拼接多行合并为一行
  • 入门网络安全工程师要学习哪些内容【2025年寒假最新学习计划】
  • 机试刷题_HJ106 字符逆序【python】
  • 【实战 ES】实战 Elasticsearch:快速上手与深度实践-1.1.2典型应用场景:日志分析、实时搜索、推荐系统
  • Redis|事务
  • 网络七层模型—OSI参考模型详解
  • Fiddler在Windows下抓包Https
  • HGAME2025 Week1
  • 尚硅谷爬虫note13
  • 一张表解释01背包问题
  • js:根据后端返回的数组取出每一个数组的keyword字段然后拼接成一个逗号分隔的字符串
  • 总结前端常用数据结构 之 栈篇【JavaScript 】
  • 伊美第四轮核问题谈判开始
  • 第一集丨《亲爱的仇敌》和《姜颂》,都有耐人寻味的“她”
  • “犍陀罗艺术与亚洲文明”在浙大对外展出
  • 重庆荣昌区委区政府再设“答谢宴”,邀请800余名志愿者机关食堂用餐
  • 巴基斯坦关闭全部领空
  • 《中国人民银行业务领域数据安全管理办法》发布,6月30日起施行