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,也可以
用这种完全二叉树的方式存储,但是大大浪费了存储空间,基本不用