算法笔记 9.1 树与二叉树

9.1.1 树的定义与性质

记住以下性质:其中1和5经常被用来出边界数据
    1.树可以没有结点,这种情况下把树称为空树
    2.树的层次(layer)从根结点开始算起,即根结点为第一层,根结点子树的根结点为第二层,以此类推
    3.把结点的子树课数称为结点的度(degree)(即:树结点的度指的是该节点孩子的个数.没有出度入度之分),
      而树中结点的最大的度称为树的度(也称为树的宽度),例如上图中3棵树的度分别为2、3、5
    4.由于一条边连接两个结点,且树中不存在环,因此对有n个结点的树,边数一定是n-1。且满足连通、
      边数等于顶点数减1的结构一定是一棵树。

    5.叶子结点被定义为度为0的结点,因此当树中只有一个结点(即只有根结点)时,根结点也算作叶子结点
    6.结点的深度(depth)是指从根结点(深度为1)开始自顶向下逐层累加至该结点时的深度值;结点的高度(height)
      是指从最底层叶子结点(高度为1)开始自底向上逐层累加至该结点时的高度值。树的深度是指树中结点的最大
      深度,树的高度是树中结点的最大高度。对树而言,深度和高度是相等的。上图中3棵树的深度和高度分别是
      4、4、2
    7.多棵树组合在一起称为森林(forest),即森林是若干棵树的集合、

一般树了解接口,下面的二叉树才是重点,要掌握

 

9.1.2 二叉树的递归定义

二叉树递归定义

1.要么二叉树没有根结点,是一棵空树
2.要么二叉树由根结点、左子树、右子树组成,且左子树和右子树都是二叉树

二叉树度为2的树的区别。对树来说,树结点的子树不区分左右顺序,因此度为2的树(结点最大度为2)仅仅说明树中每个结点的子结点个数不超过2。二叉树每个结点的子结点个数不超过2,但是左右子树严格区分的

 

特殊二叉树:

1.满二叉树:每一层的结点个数都达到了当层能达到的最大结点数。如图9-2E
2.完全二叉树:除了最下面一层之外,其余层的结点个数都达到了当层能达到
  的最大结点数,且最下面一层只从左至右连续存在若干结点,而这些连续结
  点右边的结点全部不存在。例如图9-2的树DE

 

层次:同一层,也即是备份

孩子结点、父亲结点、兄弟结点、祖先结点子孙结点
其中:存在一条从结点X到Y的从上到下的路径,那么称结点X是Y的祖先结点,结点Y是结点X的子孙结点
    右定义可知3点:
       1.祖先、子孙是直系父辈之间的关系   即:孙子、儿子、父亲、爷爷、爷爷的父亲。。。
       2.自己也是自己的祖先结点和父亲结点(自己当然能走到自己了)
       3.父亲也是祖先结点

 

9.1.3 二叉树的存储结构与基本操作

1.二叉树的存储结构

二叉树存储:一般用链表来定义二叉树,这种形式的链表又称为二叉链表

struct node{
	int data;//数据域  类型此处默认设为int
	node* lchild;//指向左子树根结点的指针
	node* rchild;//指向右子树根结点的指针	
};

 

定义新结点

//新建结点(插入结点时用)
//生成一个新结点,v为结点的权值
node* newNode(int v){
	node* Node=new node;//申请一个node型变量的地址空间
	Node->data=v;
	Node->lchild=Node->rchild=NULL;//初始状态下没有左右孩子
	return Node;
}

 

   9.2二叉树遍历再写demo

 

2.二叉树结点的查找、修改

//二叉树查找、修改
//在二叉树root中查找所有数据域为x的结点,并将他们的数据域修改为newdata
void search(node* root,int x,int newdata){
	if(root==NULL) return;//空树,死胡同,递归边界
	if(root->data==x) root->data=newdata;//找到数据域为x的结点,把他修改为newdata
	search(root->lchild,x,newdata);//往左子树搜索x(递归式)
	search(root->rchild,x,newdata);//往左子树搜索x(递归式)
}

 

3.二叉树结点的插入

具体代码由二叉树本身的特点决定

查找失败的地方(死胡同,NULL)处,就是结点需要插入的地方

//insert将在二叉树中插入一个数据域为x的新结点
//注意根结点指针root要使用引用,否则插入会不成功
void insert(node* &root,int x){
	if(root==NULL){//空树,说明查找失败,也即插入位置(递归边界)
		root=newNode(x);//修改的不是root指向的内容,而是root这个指针型变量本身,而不加引用对指针型变量本身来说
						//是值传(备份一个相同地址),修改作用不到原来变量(即最初root一步步指向的那个node)
		return;
	}
	if(由二叉树性质,x应该在左子树){
		insert(root->lchild,x);//往左子树搜索(递归式)
	}else{
		insert(root->rchild,x);//往右子树搜素(递归式)
	}
}

如果函数中需要新建结点,即对二叉树的结构做出修改,就需要加引用;如果只是修改当前已有结点的内容,或仅仅是遍历树,就不用加引用。

新建结点时,务必令新结点的左右指针域为NULL,表示这个结点暂时没有左右子树

 

4.二叉树的创建

本质结点插入过程,而结点的数据域存储在数组中

//二叉树的创建
node* Create(int data[],int n){
	node* root=NULL;//新建空根结点root
	for(int i=0;i<n;i++){
		insert(root,data[i]);//本文件内insert的定义为先根插入
	}
	return root;
}

 

小结:其中二叉树的创建和遍历是9.2的成果  看完9.2再看此demo

#include<iostream>
#include<queue>
using namespace std;
struct node{
	int data;//数据域  类型此处默认设为int
	node* lchild;//指向左子树根结点的指针
	node* rchild;//指向右子树根结点的指针	
};

//新建结点(插入结点时用)
//生成一个新结点,v为结点的权值
node* newNode(int v){
	node* Node=new node;//申请一个node型变量的地址空间
	Node->data=v;
	Node->lchild=Node->rchild=NULL;//初始状态下没有左右孩子
	return Node;
}

//二叉树查找、修改
//在二叉树root中查找所有数据域为x的结点,并将他们的数据域修改为newdata
void search(node* root,int x,int newdata){
	if(root==NULL) return;//空树,死胡同,递归边界
	if(root->data==x) root->data=newdata;//找到数据域为x的结点,把他修改为newdata
	search(root->lchild,x,newdata);//往左子树搜索x(递归式)
	search(root->rchild,x,newdata);//往左子树搜索x(递归式)
}

//insert将在二叉树中插入一个数据域为x的新结点
//注意根结点指针root要使用引用,否则插入会不成功
void insert(node* &root,int x){
	if(root==NULL){//空树,说明查找失败,也即插入位置(递归边界)
		root=newNode(x);//修改的不是root指向的内容,而是root这个指针型变量本身,而不加引用对指针型变量本身来说
						//是值传(备份一个相同地址),修改作用不到原来变量(即最初root一步步指向的那个node)
		return;
	}
	if(/*由二叉树性质,x应该在左子树*/root->lchild==NULL){
		insert(root->lchild,x);//往左子树搜索(递归式)
	}else{
		insert(root->rchild,x);//往右子树搜素(递归式)
	}
}

//先序插入遍历
void Create(node* &root){
    int c;
    cin>>c;
    if(c == -1)//-1表示结点为空
        root = NULL;  //null表示为空枝
    else{
        root = new node;
        root->data = c;
        Create(root->lchild);
        Create(root->rchild);
    }
}

//先根遍历
void preorder(node* root){
	if(root==NULL) return;//到达空树,递归边界
	//访问根结点root,如输出数据域
	cout<<root->data<<" ";
	//访问左子树
	preorder(root->lchild);
	//访问右子树
	preorder(root->rchild);
}

//层序遍历
void LayerOrder(node* root){
	queue<node*> q;
	q.push(root);
	while(!q.empty()){
		node* top=q.front();
		q.pop();
		cout<<top->data<<" ";
		if(top->lchild!=NULL) q.push(top->lchild);
		if(top->rchild!=NULL) q.push(top->rchild);
	}
}

int main(){
	freopen("input0.txt","r",stdin);

	//由于在二叉树建树前根结点不存在,因此其地址一般设为NULL
	node* root=NULL;
	
	//test
	//创建二叉树  先根插入
	Create(root);
	cout<<"先序遍历:";
	preorder(root);
	cout<<endl;
	cout<<"层序遍历:";
	LayerOrder(root);
	cout<<endl;

	//修改 5改成555
	search(root,5,55);
	search(root,6,66);
	cout<<"修改后:"<<endl;
	cout<<"先序遍历:";
	preorder(root);
	cout<<endl;
	cout<<"层序遍历:";
	LayerOrder(root);
	cout<<endl;

	return 0;
}

input0.txt:  单独的任何单个遍历序列都是无法确定一颗二叉树的,因此必须有特殊的结束符,此处暂且一-1作为结束符

1 2 4 8 -1 -1 9 -1 -1 5 10 -1 -1 -1 3 6 -1 -1 7 -1 -1

运行结果:

 

 

扫尾:完全二叉树的存储结构

很明显,若题目确定二叉树为一棵完全二叉树,可以直接用一个线性数组来存储(前几天蓝桥的试题 G: 完全二叉树的权值)。这样存储不用写链表,简单方便还有不少好的性质

存储:从左到右顺序进行编号(从1开始
          树的深度为k,则数组大小2^k(0~2^k-1)

性质:
    1.任何一个编号为x的结点,左孩子是2*x,右孩子是2*x+1
    2.数组中元素存放顺序就是该完全二叉树层序遍历的序列
    3.判断叶子结点:判断左孩子编号2*x大于结点总个数n
    4.判断某个结点为空结点:结点下标>结点总个数n
    5.叶子结点个数为[n/2] (n/2向上取整)  
    6.非叶子结点下标范围[1,n/2] (1~n/2向下取整)


非完全二叉树,即普通二叉树,通过将空结点都补上为NULL,也可以
用这种完全二叉树的方式存储,但是大大浪费了存储空间,基本不用

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值