【数据结构】树

参考:1.b站https://www.bilibili.com/video/BV1Mt4y197kC?t=814&p=119
2.柳神代码https://liuchuo.blog.csdn.net/article/details/52505179

3.1119. Pre- and Post-order Traversals (30)-PAT甲级真题(前序后序转中序)_eqopp的博客-CSDN博客

树的构建方法

树的构建方法有一下几种:

1.用二维数组来存储树(类似于图)(多用于树,不是二叉树?)

   多用于这种输入模式

    

20 9 24    //节点数  非叶节点个数    要查找的权重
10 2 4 3 5 10 2 18 9 7 2 2 1 3 12 1 8 6 2 2        //输入所有节点的权重
00 4 01 02 03 04    //节点    节点的孩子个数    其他节点
02 1 05
04 2 06 07
03 3 11 12 13
06 1 09
07 2 08 10
16 1 15
13 3 14 16 17
17 2 18 19

   例:1053 Path of Equal Weight

   缺点:开的数据大了容易内存超限

2.用邻接表存储树(二维vector数组)(多用于树,不是二叉树)

    多用于这种输入模式

    

9 1.80 1.00
1 5 4 4 -1 4 5 3 6        
/*下标代表这个节点的下标,数值代表这个节点的父亲节点的下标,为-1代表根节点*/

    例:1090 Highest Price in Supply Chain、1106 Lowest Price in Supply Chain

3.用结构体数组存储(多用于只有左右孩子的二叉树)

struct Node{
	int l,r,index,id,level;    //分别为左孩子下标,右孩子下标,下标,节点的值,节点的层数
}a[15];    //有几个节点数组就开多大

    多用于这种输入模式

8        //节点个数
1 -      //第i个节点的左右孩子,‘-’代表没有
- -
0 -
2 7
- -
- -
5 -
4 6

    例:1102 Invert a Binary Tree

     注意:1.这种输入方式的根节点寻找可以借助一个vector数组,唯一没有出现的那个节点就是根节点

                  2.输入数据应该用string接受,因为输入可能是两位数

4.用结构体链表存储(多用于构建BST树)

BST树:二叉搜索树,所有节点的左子树小于该节点,所有节点的右子树大于该节点

struct Node{
	int v;        //节点的值
	struct Node *left;    //节点左孩子的指针
	struct Node *right;    //节点右孩子的指针
};

多用于这种输入模式

9
25 30 42 16 20 20 35 -5 28    //告诉你节点的信息,让你构建BST树

构建BST树的函数

List Build(List root,int v){        //递归函数
	if(root==NULL){        //如果节点是空的,代表改数据已经找到了自己的位置
		root=new Node();
		root->left=NULL;
		root->right=NULL;
		root->v=v;
	}
	else if(root->v>=v){        //不是空本来节点的值大于等于给的值时的递归调用左子树
		root->left=Build(root->left,v);    
        /*一定是左子树等于递归函数,不是return递归函数*/
	}
	else
		root->right=Build(root->right,v);    //小于时调用左子树
	return root;        //返回根节点
}

build函数注意事项

1.递归时是让左右子树等于递归的返回值,不是return这个值

2.最后一定记得返回根节点

例:1115 Counting Nodes in a BST

树的遍历

一、先序遍历

void dfs(root){
    cout<<root.v;
    dfs(root->left);
    dfs(root->right);
}

二、中序遍历

void dfs(root){
    dfs(root->left);
    cout<<root.v;
    dfs(root->right);
}

三、后序遍历

void dfs(root){
    dfs(root->left);
    dfs(root->right);
    cout<<root.v;
}

四、层序遍历

层序遍历的两种方法

第一种:只用下标表示

void dfs(root,int index){    //引入一个index函数,初始值为零
    /*假设这棵树是一颗平衡二叉树,
    那么一个节点的左孩子节点为父节点index*2+1
    右孩子节点为父节点的index*2+2*/
    root->index=index;
    dfs(root->left,index*2+1);
    dfs(root->right,index*2+2);
}
//输出
int main(){
    //将结构体数组按index的值从大到小排序,按index的值从小到大输出
}

优点:

对于结构体数组构建的树,这种方法代码量较少,适用于树高度不高的树(小于20层)

缺点:

当树高度大于20层,或者只有单边的二叉树,资源利用率极小,每一层的index是2^n-1层数过高index的值会超限

第二种:用层数level表示

void dfs(int root,int level){
	maxlevel=max(maxlevel,level);
	//a[root].index=index;
	if(a[root].l!=-1)
		dfs(a[root].l,level+1);
	a[root]={a[root].r,a[root].l,b[cnt++]};
	if(a[root].r!=-1)
		dfs(a[root].r,level+1);
}
/*DFS函数中求出这棵搜索树的最大层数*/
vector<Node> v[maxlevel+1]        
/*建立结构体树的二维数组,其中行代表树的深度level列代表这行有几个数据*/
int main(){
    v[0].push_back(root);        //把根节点放到第一层
    for(int i=0;i<=maxlevel;i++){    
        /*一定是i<=maxlevel,当等于maxlevel时是叶子结点,不是叶子结点的下一层节点*/
        for(int j=0;j<v[i].size();j++){
			cout<<a[v[i][j]].id;        //输出这个节点的值
			if(a[v[i][j]].l!=-1)	v[i+1].push_back(a[v[i][j]].l);    
			if(a[v[i][j]].r!=-1)	v[i+1].push_back(a[v[i][j]].r);	
            /*这个节点如果有左孩子或者右孩子,将左右孩子放入下一层的动态数组中*/
		}
    }
}

二维动态数组的行是层数,列是这一行有几个数据

开始之前一定记得把根节点放在第0层,否则for循环无法开始

遍历相互之间的转化

1.由先序序列和中序序列转化为后序序列

#include<bits/stdc++.h>
using namespace std;
int in[8]={3,1,2,6,7,5,8};
int pro[8]={6,1,3,2,5,7,8};
void post(int root,int start,int end){
    /*root是先序序列根节点的下标,
    start这层递归树先序序列开始的下标,
    end是这层递归树先序序列结束的下标*/
    if(start>end)    //循环结束的判断条件
        return ;
    int i=start;
    while(pro[root]!=in[i]&&i<end)    i++;    //在中序序列中寻找先序的根节点
    post(root+1,start,i-1);        
    /*递归左子树,根节点等于原先子树加一
    start不变,end编程i-1*/
    post(root+(i-start)+1,i+1,end);
    /*递归右子树,i-start相当于原先的树减去左子树,
    还剩原来的根结点和右子树,所以应该再加1*/
    printf("%d",pre[root]);    //因为是后序序列,应该在递归结束后再输出
}
int main(){
    post(0,0,7);
}

递归思想

2.由中序序列和后序序列转化为先序序列

#include<bits/stdc++.h>
using namespace std;
int in[8]={3,1,2,6,7,5,8};
int post[8]={3,2,1,7,8,5,6};
void pro(int root,int start,int end){
    if(start>end){
        return ;
    }
    int i=start;
    while(pro[root]!=in[i];i<end)    i++;
    cout<<post[root];
    pro(root-(end-i)-1,start,i-1);
    pro(root-1,i+1,end)
}
int main(){
    pro(8-1,0,7);
}

与上同理

3.由先序序列和后序序列转化为中序序列(结果可能不唯一)

void getIn(int preLeft, int preRight, int postLeft, int postRight) {
    if(preLeft == preRight) {        //递归结束
        in.push_back(pre[preLeft]);
        return;
    }
    if (pre[preLeft] == post[postRight]) {
        int i = preLeft + 1;        //先序序列除了根的第一个节点
        while (i <= preRight && pre[i] != post[postRight-1]) i++;        
        /*找pre中等于post除了根的最后一个节点*/
        if (i - preLeft > 1)
            getIn(preLeft + 1, i - 1, postLeft, postLeft + (i - preLeft - 1) - 1);    
            //递归左子树
        else
            unique = false;        //当左右子树只有一个时,说明情况不唯一
        in.push_back(post[postRight]);
        getIn(i, preRight, postLeft + (i - preLeft - 1), postRight - 1);    //递归右子树
    }
}

4.中序遍历转层序遍历(完全BST?)

例题:

1064 Complete Binary Search Tree 

int level[1005];
int in[1005];
int t=0;
void inOrder(int root){
    if(root>=n)    return ;    //大于等于,因为下标为n就没有值了
    inOrder(root*2+1);
    level[root]=in[t++];
    inOrder(root*2+2);
}
int mian(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>in[i];     
    }
    inOrder(0);
}

由遍历建树

模板(后序遍历)

int in[8]={12,11,20,17,1,15,8,5};
int post[8]={12,20,17,11,15,8,5,1};
List dfs(List Root,int root,int start,int end){
	if(start>end)
		return Root;
	if(Root==NULL){
		Root=new Node();
		Root->val=post[root];
		Root->left=NULL;
        Root->right=NULL;
	}
	int i=start;
	while(in[i]!=post[root]&&i<end)	i++;
	Root->left=dfs(Root->left,root-(end-i)-1,start,i-1);
	Root->right=dfs(Root->right,root-1,i+1,end);
	return Root;
}
int main(){
    List Root;
    Root=dfs(Root,n-1,0,n-1);
}

AVL树模板

模板

#include<bits/stdc++.h>
using namespace std;
typedef struct Node *List;
struct Node{
	int val;
	List left;
	List right;
};
List root=NULL;
int getHeight(List root){            //获得高度
	if(root==NULL)	return 0;
	int l=getHeight(root->left);
	int r=getHeight(root->right);
	return max(l,r)+1;                //一定记得加1
}
List rightRotation(List root){        //当左边高于右边是右单旋
	List temp=root->left;
	root->left=temp->right;
	temp->right=root;
	return temp;
}
List leftRotation(List root){        //当右边高于左边是左单旋
	List temp=root->right;
	root->right=temp->left;
	temp->left=root;
	return temp;
}
List rightLeftRotation(List root){        //右左双旋
	root->right=rightRotation(root->right);
	return leftRotation(root);
}
List leftRightRotation(List root){        //左右双旋
	root->left=leftRotation(root->left);
	return rightRotation(root);
}
List insert(List root,int val){
	if(root==NULL){
		root=new Node();
		root->val=val;
		root->left=NULL;
		root->right=NULL;
	}
	else if(val<root->val){
		root->left=insert(root->left,val);
		int l=getHeight(root->left);            //插入完成后判断树的高度
		int r=getHeight(root->right);            
		if(l-r>=2){
			if(val<root->left->val){
				root=rightRotation(root);            //左边高于右边是右单旋
			}
			else{
				root=leftRightRotation(root);        //左边高于右边是左右双旋
			}
		}
	}
	else{
		root->right=insert(root->right,val);        
		int l=getHeight(root->left);
		int r=getHeight(root->right);
		if(r-l>=2){
			if(val>root->right->val){
				root=leftRotation(root);            //右边高于左边是左单旋
			}
			else{
				root=rightLeftRotation(root);        //右边高于左边是右左双旋
			}
		}
	}
	return root;                                    //一定记得return!!!!!!
}
void dfs(List root){                //深搜验证
	if(root==NULL)
		return ;
	cout<<root->val<<" ";
	dfs(root->left);
	dfs(root->right);
}
int main(){
	int n;
	cin>>n;
	int data;
	for(int i=0;i<n;i++){
		cin>>data;
		root=insert(root,data);
	}
	dfs(root);
} 

其他注意事项

1.翻转二叉树就是每一个节点的左右孩子相互交换,递归时先递归左子树在递归右子树是原先的数,先递归右子树再递归左子树就是原本树的翻转二叉树

2.由层序遍历遍历二叉树判断是不是完全二叉树,只要有一个节点为空,后面必须没有节点才能满足完全二叉树的条件,否则则不是完全二叉树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值