【数据结构】树

引言

一直没有系统的学习“树”这一数据结构,导致对一些概念,用法模棱两可,所以现在开始系统的学习一下;

知识点总结

树的定义

有若干条节点和若干条边组成的数据结构;
树是一种非线性的数据结构,我们可以类比想像生活中的树,有树枝,树枝分叉处,树叶等;
在这里插入图片描述

为什么要使用树

我们来考虑一个问题,有一段线路(长度为n)出现了故障,我们要判断问题出现在那部分,一般方法就是通过一节节的测试,但是这样的话平均需要测n/2次,当n非常大时,复杂度是不能接受的;因此,需要一个更为巧妙的办法==》二分查找
在这里插入图片描述
这样的话,查找的时间复杂度就直接变成了log2n次,复杂度大大降低了,这种方法可以用”树“来实现;

树的特点

  • 一棵树有n个节点,则可以确定有且仅有n-1条边;

常见术语

  • 节点的度,指节点的子树数;
    在这里插入图片描述
  • 叶节点,度为0的点;
  • 树的度,树中最大的节点的度;

二叉树

树的种类有很多种,其中最常用的就是二叉树;

二叉树的定义

树的度<=2的树称为二叉树;且二叉树的左右子树是严格区分的,不可随意改动;

特殊的二叉树

1.满二叉树
在这里插入图片描述
像这样的除了叶子节点,其余节点的度都为2的二叉树称为满二叉树;
2.完全二叉树
满二叉树是特殊的完全二叉树;
在这里插入图片描述

完全二叉树的性质

  • 根节点的编号为1;
  • 用数组表示完全二叉树的话,节点编号为i的左右节点存在的话,对应的编号为2i,2i+1
  • 二叉树的i层至多有2^(i-1)个节点;

二叉树的创建

struct node{
	int data;
	node* lc;
	node* rc;//<1>
};
node* newNode(int x){
	node* Node=new node;
	Node->lc=Node->rc=NULL;//<2>
	Node->data=x;
	return Node;
}
void insert(node* &root,int x){//<5>
	if(root==NULL){
		root=newNode(x);
		return;
	}
	if(root->lc==NULL)//<3>
	  insert(root->lc,x);
    else
	  insert(root->rc,x);
}
int num[100];
node* create(int data[],int n){
	node* root=NULL;//<4>
	for(int i=0;i<n;i++)
	  insert(root,data[i]);
	return root;
}

上述代码创建出来的二叉树:
在这里插入图片描述

对上面的代码一些我自己纠结了很久的地方做个解释
<1>
node* root与node *root是一样的,都是表示root为node类型的指针;千万不要过分纠结(很难受)<2><4>
都是为了初始化节点,NULL是一个宏定义,表示不指向任何地址的空指针;
<5>
node* &root的写法是为了能够修改二叉树(添加节点),与之相对应的是在遍历操作者就不需要使用&root,因为遍历时不需要对二叉树进行修改;
<3>
目前只会这样创建,太菜了;

二叉树的遍历

**注意:以下先,中,后都是描述根节点的位置的,比如先序遍历的顺序是根节点->左节点->右节;后序遍历的顺序是左->右->根节点

1.先序遍历

void preorder(node* root){
	if(root==NULL)
	    return;
	printf("%d ",root->data);
	preorder(root->lc);
	preorder(root->rc);
}

先序遍历的特点:
先序遍历序列的第一个一定是根节点;

2.中序遍历

void inorder(node* root){
	if(root==NULL)
	    return;
	inorder(root->lc);
	printf("%d ",root->data);
	inorder(root->rc);
}

中序遍历的特点:
只要知道根节点,就能区分左右子树;
1.后序遍历

void postorder(node* root){
	if(root==NULL)
	    return;
	postorder(root->lc);
	postorder(root->rc);
	printf("%d ",root->data);
}

后序遍历的特点:
序列的最后一个是根节点;

总结
只有知道中序遍历序列,才可能唯一确定一棵二叉树,因为先序/后序节点只能确定根节点的位置,不能把左右节点分开;

4.层序遍历

void layerorder(node* root){
	queue<node*>q;
	q.push(root);
	while(!q.empty()){
		node* curr=q.front();
	     	q.pop();
		cout<<curr->data<<" ";
		if(curr->lc!=NULL){
			q.push(curr->lc);	
		}
		if(curr->rc!=NULL){
			q.push(curr->rc);
		}
	}
}

重建二叉树

我们经常会见到这样的问题:给定一个二叉树的中序序列,和先序/后序/层次序列的一种,重建这个二叉树;其中,先序等三种序列的作用是确定树的根节点位置中序遍历序列的作用是区分左右节点
以下面的例题来解释:

问题描述
定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列。这里假设键值都是互不相等的正整数。
输入样例
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
输出样例
4 1 6 3 5 7 2

问题分析
在这里插入图片描述
代码展示

#include<bits/stdc++.h>
using namespace std;
struct node{
	int data;
	node* lc;
	node* rc;//<1>
};
node* newNode(int x){
	node* Node=new node;
	Node->lc=Node->rc=NULL;//<2>
	Node->data=x;
	return Node;
}
int post[100],in[100];
vector<int>ans;
node* JudgeByPostAndIn(int PostL,int PostR,int inL,int inR){
	//当前树的后序序列:[PostL,PostR],中序序列:[inL,inR]

	if(PostL>PostR)//??什么情况会 > 
	   return NULL;
	   node* root=newNode(post[PostR]);//创建节点! 
	 int k=inL;
	 while(in[k]!=post[PostR])k+=1;
	 //找到中序序列中root的位置	
	 int numLeft=k-inL;//左子树个数 
	 root->lc=JudgeByPostAndIn(PostL,PostL+numLeft-1,inL,k-1);
	 root->rc=JudgeByPostAndIn(PostL+numLeft,PostR-1,k+1,inR);  
	 return root;
}
void layorder(node* root){
	queue<node*>q;
	q.push(root);
	while(!q.empty()){
		node* curr=q.front();
		q.pop();
		ans.push_back(curr->data);
		if(curr->lc!=NULL)q.push(curr->lc);
		if(curr->rc!=NULL)q.push(curr->rc);
	}
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&post[i]);
	for(int i=1;i<=n;i++)scanf("%d",&in[i]);
   node* root=JudgeByPostAndIn(1,n,1,n);
   layorder(root);
   for(int i=0;i<ans.size();i++){
   	if(i!=0)printf(" ");
   	printf("%d",ans[i]);
   }
   return 0;
   
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高冷小伙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值