知识结构
1.树与二叉树
1.1树的定义与性质
树型结构与线性表都是一种数据结构,是由若干个结点和若干条边组成的数据结构。
涉及的概念有 :结点(node)、根结点(root)、叶子结点(leaf)、边、子结点、子树。
常用性质:
1.无结点时为空树;
2.层次从根结点开始算;
3.结点的子树数量是结点的度;
4.n个结点的树,定有n-1条边,反之亦成立;
5.叶子结点度为1,只有一个结点时,根结点同时也是叶子结点;
6.结点深度从上到下,结点高度从下到上;
7.森林是若干树的集合。
1.2二叉树的递归定义
递归定义:1.要么二叉树是空树;2.要么二叉树由左子树、右子树组成,且左子树、右子树都是二叉树。
满二叉树:每层节点都达到了该层节点能达到的最大结点数。
完全二叉树:与满二叉树结点对应,只可能在最后一层少。
1.3二叉树的存储结构与基本操作
1.3.1 一般二叉树使用链表定义。(二叉链表)
struct node {
typename data;//数据域
node *lchild;//左子树根结点
node *rchild;//右子树根结点
};
二叉树建树前根结点不存在,地址一般设为node *root = NULL;
如需要新建结点(如往二叉树插入结点时),可用该插入结点函数:
//生成一个新结点,V为结点权值
node* newNode ( int v ) {
node* Node = new node;//申请一个Node型变量的地址空间
Node->data = v;
Node->lchild = Node->rchild = NULL;
return Node; //返回新建结点地址
}
主要操作还是创销、增删、改查。
1.3.2二叉树结点的查找、修改
给定数据域,在二叉树中找到所有数据域为给定数据域的结点,并将他们的数据域修改为给定数据域。
先判断当前结点是否是所需查找结点,如果是,对其进行修改操作;如果不是,则分别往该结点左孩子和右孩子递归,直到当前结点为NULL为止。
//二叉树结点的查找、修改
void Search ( node* root, int x, int newdata ){
//传入三个参数树、待查树、需改数
if ( root == NULL )
return 0;
if ( root->data == x ){
root->data = newdata;
}
Search ( root->lchild, x, newdata );
Search ( root->rchild, x, newdata );
}
1.3.3二叉树结点的插入
由于二叉树形态众多,对于某个不知其特点的二叉树,难以给出具体插入方法。
//二叉树的插入
void Insert ( node* &root, int x ){ //注意加引用
if ( root == NULL ){ //空树,查找失败,即插入位置
root = newNode(x);
return;
}
if ( 由二叉树性质,x应该插在左子树){
Insert ( root->lchild, x ); //往左子树搜索(递归)
}else{
Insert ( root->rchild ,x );//往右子树搜索(递归)
}
}
1.3.4二叉树的创建
二叉树的创建其实就是二叉树结点的插入,一般题目给出数据,可将之存入一个数组,再将数组元素一个一个插进二叉树,最终返回根结点指针root。也可边输入数据边插入结点。
//二叉树的创建
node* Creat ( int data[], int n ){ //数组传入数据,n为元素个数
node* root = NULL; //新建空结点 root
for ( int i=0; i<n; i++ ){
Insert ( root, data[i] );
}
return root; //返回根节点
}
注意root = NULL; 与 *root = NULL; 的区别
结点不存在与结点存在但没有内容。
1.3.5完全二叉树的存储结构
完全二叉树可用大小为2的k次方的数组来存(K为完全二叉树最大宽度),1号位放根结点,每个结点的左右孩子编号都可以计算得出。
数组中元素存放的顺序,为该树层序遍历序列。
若结点左孩子编号的2倍大于结点总个数n,则该结点为叶子结点。
若结点编号x大于总结点个数n,则该节点为空。
2.二叉树的遍历(四种)
先序遍历(DFS)、中序遍历(DFS)、后序遍历(DFS)、层序遍历(BFS)
2.1先序遍历(根左右)
//先序遍历
void preorder ( node* root ) {
if ( root == NULL ) {
return; //到达空树,递归边界
}
printf( "%d\n", root->data );//访问根结点,如将其输出
preorder ( root->lchild );
preorder ( root->rchild );
}
2.2中序遍历(左根右)
//中序遍历
void inOrder ( node* root ){
if ( root == NULL ){
return; //到达递归边界,空树
}
inOrder ( root->lchild ); //先访问左子树
printf ( "%d\n", root->data );//访问根节点,打印
inOrder ( root->rchild );
}
2.3后序遍历(左右根)
//后根遍历
void postorder ( node* root ){
if ( root == NULL ){
return;
}
postorder ( root->lchild );
postorser ( root->rchild );
printf ( "%d\n", root->data );
}
2.4层序遍历
2.4.1如何实现层序遍历
- 将根结点加入到队列q。
- 取出队首结点,访问它。
- 如果该结点有左孩子,将左孩子入队。
- 如果该结点有右孩子,将右孩子入队。
- 返回2,直到队列为空。
//层序遍历
void LayerOrder ( node* root ){
queue < node* > q; //队列中存放地址
q.push ( root ); //根结点地址入队
while ( !q.empty () ){
node* now = q.front ();
q.pop();
printf ( "%d\n ", now->data );
if ( now->lchild != NULL ) q.push (now->lchild);
if ( now->rchild != NULL ) q.push (now->rchild);
}
}
2.4.2如何计算结点层次
当要求计算结点的层次时,在定义二叉树结点时要加入记录层次的变量layer。
struct node {
typename data;
int layer;
node* lchild;
nose* rchild;
}
当根结点入队前,将根结点layer赋为1(或0)
在左(now->lchild)右(now->rchild)孩子入队前,将他们的层号都记为当前now层号加1.
void LayerOrder ( node* root ){
queue < node* > q;
root->layer = 1; //队列中存放地址
q.push ( root ); //根结点地址入队
while ( !q.empty () ){
node* now = q.front ();
q.pop();
printf ( "%d\n ", now->data );
if ( now->lchild != NULL ) {
now->lchild->layer = now->layer+1;
q.push (now->lchild);
}
if ( now->rchild != NULL ){
q.push (now->rchild);
now->rchild->layer = now->layer;
}
}
}
2.4.3给定先、中序列,如何重建二叉树
先序序列:根、左子树、右子树
中序序列:左子树、根、右子树
//先序序列[preL, preR], 中序序列[inL, inR]
node* create ( int preL, int preR, int inL, int inR ){
if ( preL > preR ){
return NULL; //左大于右,序列不存在
}
node* root = new node;
root->data = pre[preL];//先序第一个结点为根结点
int k;
for ( k=inL; k<=inR; k++ ){//在中序序列中找到根结点
if ( in[k] == root->data ){
break;
}
}
int numLeft = k-inL;//左子树结点个数
root->lchild = creat ( preL+1, preL+numLeft, inL, k-1 );//递归处理左子树
r00t->rchild = creat ( preL+numLeft+1, preR, k+1, inR );//递归处理右子树
return root;
}