栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【C++】超详细!数据结构——树(一、树和二叉树Ⅰ)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C++】超详细!数据结构——树(一、树和二叉树Ⅰ)

合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。

下续:
二、树和二叉树Ⅱ


树与二叉树Ⅰ
  • 1 树基本概念
      • 1.1 术语介绍
  • 2 二叉树
      • 2.1 二叉树的递归定义
      • 2.2 简单而特殊的二叉树
        • 2.2.1 满二叉树
        • 2.2.2 完全二叉树
      • 2.3 二叉树的存储结构与基本操作
        • 2.3.1 二叉树的存储与创建
        • 2.3.2 二叉树的查找与修改
        • 2.3.3 二叉树插入结点与创建
        • 2.3.4 完全二叉树的建立方式
      • 2.4 二叉树的遍历
        • 2.4.1 先序遍历
        • 2.4.2 中序遍历
        • 2.4.3 后序遍历
        • 2.4.4 层序遍历
        • 2.4.4 中序遍历和后序遍历→求先序遍历


1 树基本概念 1.1 术语介绍

1、每个元素叫节点node.
2、最顶上的节点叫树根root.
3、大树的一部分可以看作一棵小树,叫子树(一般用T来表示).
4、连着一个结点的上一层结点叫这个点的前驱,同样地,连着它的下(后)面的点叫后继;在一棵树中,除树根之外的任一一个节点只有一个前驱,但是可以有很不多不同的后记,而树根没有前驱.
5、一个结点后继的个数被称为它的度degree,而度数为0的点叫做树叶leaf,它们处在最后一层,是光的.
6、一棵树上根节点的层次level或layer为0(有时也会是1,看题目或实际情况需要),所有结点的层次的最大值为这棵树的深度depth,也叫高度height.
7、一个结点的子树的根节点是这个结点的孩子结点,而这个结点是它的孩子结点的父亲结点,与这个结点层次相同的是它的兄弟结点;比这个结点层次高的是它的子孙结点,反之,比他层次低的是它的祖先节点.(层次高,地位低;层次低,地位高)
8、一棵子树上的任意两点都是直接或间接相邻的,也就是说,从一个结点到另一个结点都有一条路径,这个路径的长度便是两点层次之差,也就是这条路径上点的个数-1.
9、许多不相交的树组在一起便形成了森林forest,而没有结点的树叫空树.

e.g.如上图,不难发现,根结点为A(树根),A的度数为2,它的后继是B和C,点B,D和E为树叶;A与E在同一棵子树上,它们之间的路径为(A, C, E),A层次为0,E层次为2,路径上有3个点,显然,路径长度为2.


2 二叉树

多叉树存储与基本操作与二叉树类似

2.1 二叉树的递归定义

递归定义详细内容如下:
#1、 二叉树没有根节点,成为一棵空树.
#2、 二叉树若不是空树,则它由树根、左子树、右子树构成 ,并且左子树与右子树也是二叉树.
重复1、2两步,直到所有分支都到了1.
如下图,A、B、C、D、E都是二叉树
叫它递归定义是因为它在用自身定义自身,就像在一个函数里调用这个函数一样;类似地,它也有一个递归边界和递归式,那么定义中,#1就是递归边界,#2是递归式.
说得再通俗一点:对于一个点,它有父亲结点,也有祖父结点,甚至于曾祖父结点;那么,按照递归定义,祖父节点就是父亲节点的父亲结点,曾祖父结点就是父亲结点的父亲结点的父亲结点;同理,它的儿子结点就是它的孙子结点的父亲结点,依此类推.

2.2 简单而特殊的二叉树

2.2.1 满二叉树

术语定义:每一层的结点个数都达到了当层能达到的最大结点数.
白话定义:满满当当,不增加层数的情况下不能再加点的树.
图E就是一棵满二叉树,但其它几个图不是.

2.2.2 完全二叉树

定义:不看最后一层,这个二叉树是满二叉树;最后一层中,结点从左至右并且连续一字排开,但不要求排满;也就是说,最后一层最左边要有一个结点,最后一层的结点是连续不断的.
如图D、E是完全二叉树,但其它几个图不是.

2.3 二叉树的存储结构与基本操作 2.3.1 二叉树的存储与创建

这里,我们用链表来存储二叉树,先上代码:

struct node {
	typename data;    //数值域,typename为任意类型
	node* lchild;     //指向左子树树根的指针
	node* rchild;     //指向右子树树根的指针
};
node* root = NULL;    //最开始是一个空树

上述结构体中,在node中又定义了node,这就是递归的体现.
上面的代码仅仅是存储的容器的类型,下面,我们就要新建一个容器,并向容器里填充树.
让我们先建立一个初始的根节点:

//生成一个新结点,v为结点权值
node* nowRoot(typename v) {
	node* Node = new node;      //申请一个node类型的地址空间,创建一棵树
	Node->data = v;          //结点权值为v
	Node->lchild = Node->rchild = NULL;     //初始状态下没有左右孩子
	return Node;              //返回新结点地址
}

这样,我们就完成了一个树根的创建,也就完成了一个树的创建.

2.3.2 二叉树的查找与修改

这是新建结点的前提
先上代码:

void change(node* root, typename x, typename newdata) {
	if(root == NULL)         //递归边界,到空树就回溯
		return;
	
	if(root->data == x) {     //找到了,改掉
		root->data = newdata;
	}
	
	//递归式
	search(root->lchild, x, newdata);     //搜左孩子
	search(root->rchild, x, newdata);     //搜右孩子
}
2.3.3 二叉树插入结点与创建

先是插入结点:

//insert函数将在二叉树中插入一个数据域为X的新结点
//注意根结点指针root要使用引用,否则插入不会成功
void insert(node* &root, int x) {
	//递归边界,如是碰到了树叶(死胡同),就插入
	if(root == NULL) {
		root = nowNode(x);
		return;
	}
	
	//递归式,不停左拐右拐,确定要插入值的位置
	if(x应走左子树) {
		insert(root->lchild, x);
	} else {
		insert(root->rchild, x);
	}
}

最后,将前面新建树根增合起来,调用insert插入多个结点,就完成了一棵二叉树的创建,代码如下:

node* Create(int data[], int n) {
	node* root = NULL;            //新建空跟结点root
	for(int i = 0; i < n; i++) {
		insert(root, data[i]);       //将data[0]~data[n-1]插入二叉树中,data数组为需要建树的数组
	}
	return root;      //返回根节点
}


如上图,我们就建立了一棵二叉树,建立的方式如右图所示

2.3.4 完全二叉树的建立方式

对于一棵完全二叉树,我们发现若一个结点编号为 x x x,则它的左子树的根结点为 2 x 2x 2x,它的右子树的根结点为 2 x + 1 2x+1 2x+1,如此一来,我们就可以用一维数组建图,并且这棵树的存放顺序就是该完全二叉树的层序遍历序列.

2.4 二叉树的遍历 2.4.1 先序遍历

树根→左子树→右子树
遍历左右子树时,再按照如上方法遍历,直到遍历到空树为止,这也就形成了递归遍历.
其实,遍历也是一个递归函数,二叉树的递归边界永远是 r o o t = = N U L L root==NULL root==NULL,递归式递归式如上箭头式所示,那么这个函数便水到渠成了:

void preorder(node* root) {
	if(root == NULL) {  //永远的递归边界
		return;
	}
	
	printf("%dn", root->data);
	
	preorder(root->lchild);   //递归式
	preorder(root->rchild);
}

举个例子:
对于如下一棵二叉树⬇

我们的程序按如图所示的顺序运行,绿圈表示现在看的树,红圈表示需要输出的树根.
所以,这棵树的前序遍历为ABCD.
性质:对于先序遍历,序列第一个一定是根结点

2.4.2 中序遍历

左子树→树根→右子树
遍历左右子树时,再按照如上方法遍历,直到遍历到空树为止,这也就形成了递归遍历.
其它不再赘述,代码如下:

void inorder(node* root) {
	if(root == NULL) {
		return;
	}
	
	preorder(root->lchild);           //第6行
	printf("%dn", root->data);       //第7行
	preorder(root->rchild);           //第8行
}

中序遍历和上面的先序遍历相比,只是代码第6、7、8行颠倒一下而已,应该无须解释了,只要懂了上面的思想,这个也顺理成章了.
性质:对于中序遍历,只要知道了根结点,就可以区分左右子树.

2.4.3 后序遍历

左子树→右子树→树根
和上面两个一样:

//后序遍历
void inorder(node* root) {
	if(root == NULL) {
		return;
	}
	
	preorder(root->lchild);
	preorder(root->rchild);
	printf("%dn", root->data);
}

性质:序列最后一个一定是根节点.

2.4.4 层序遍历

按层次由低到高一层一层向下遍历.

由于这种遍历是先看完这一层再看下一层的,不是一条路走到黑再画一条路,所以我们用 B F S BFS BFS来完成.
接下来是详细的步骤⬇

① 将根节点 r o o t root root压入队列 q q q.
② 访问队首结点,弹掉它.
③ 若该结点存在左子树,则将其左子树的根结点压入 q q q.
④ 若该结点存在右子树,则将其右子树的根节点压入 q q q.
⑤ 返回操作②,知道 q q q为空.

有了思想,代码也终于浮出水面:

//层序遍历
void LayerOrder(node* root) {
	queue q;
	q.push(root);
	
	while(!q.empty) {
		node* now = q.top();   //抓到队首元素
		q.pop();
		
		printf("%dn", now->data);    //找孩子
		if(now->lchild != NULL)
			q.push(now->lchild);
		if(now->rchild != NULL)
			q.push(now->rchild);
	}
}

需要注意的是:代码中必须是 n o d e ∗ node* node∗而不能用 n o d e node node,因为 n o d e node node仅仅传递了一个副本

2.4.4 中序遍历和后序遍历→求先序遍历

L u o g u Luogu Luogu P 1030 P1030 P1030 求先序排列

思路:利用后序遍历和中序遍历的性质,先利用后序遍历找出树根,再到中序遍历中用树根把序列裂成左子树和右子树.

A C AC AC代码:

#include 
using namespace std;

void beford(string in, string after) {
	if(in.size() > 0) {  //如果没到空树,递归边界
		char ch = after[after.size()-1];
		cout << ch;   //抓到根并输出
		int k = in.find(ch);    //裂数组
		beford(in.substr(0, k), after.substr(0, k));    //递归左子树
		beford(in.substr(k+1), after.substr(k, in.size()-1-k));    //递归右子树
	} else {
		return;
	}
}

signed main() {
	string in, after;
	cin >> in >> after;
	beford(in, after);
	return 0;
}

3 m s / 808.00 K B 3ms/808.00KB 3ms/808.00KB.

特别要注意一下下标的问题,细节要慢慢扣.

那么已知先序、中序求后序也就异曲同工了.
L u o g u Luogu Luogu P 1827 P1827 P1827 美国血统
A C AC AC Code:

#include 
using namespace std;

void aftord(string pre, string in) {
	if(pre.size() > 0) {
		char ch = pre[0];
		int k = in.find(ch);
		
		aftord(pre.substr(1, k), in.substr(0, k));
		aftord(pre.substr(k+1), in.substr(k+1));
		cout << ch;
	} else {
		return;
	}
}

signed main() {
	string pre, in;
	cin >> in >> pre;
	
	aftord(pre, in);
	
	return 0;
}

本文参考《信息学奥赛:一本通 C++版》、《算法笔记》;属个人知识版权,切勿以此作商务只用.


c r e a t e : 2022 / 7 / 28 create :2022/7/28 create:2022/7/28
u p d a t e : 2022 / 7 / 29 update:2022/7/29 update:2022/7/29


赠人拇指,手留余香,记得点个免费的赞再走 Q w Q QwQ QwQ.

C S D N @ z h e n g z i a o CSDN@zhengziao CSDN@zhengziao

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1029253.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号