第二章 二叉树
顺序存储结构的二叉树,设计一个算法求,编号为i和j的两个结点的最近公共祖先结点的值
1、二叉树顺序顺序存储
2、最近公共祖先结点,
1、双亲编号问题,i的祖先结点为i/2,j的祖先结点为j/2
2、取多少次
3、将较大的不断取一半,比较i和j的大小,不断将大的取一半
二叉树:
1、顺序存储:
适用范围:完全二叉树、满二叉树
2、链式存储
对于普通的二叉树如何使用顺序存储?
在存储的过程中将逻辑关系表达出来,将普通二叉树转换为完全二叉树,用数组按照层次一层一层保留,在存储过程中下标从1开始。
算法思想:不断的将i、j较大的取一半,比较i、j的大小,直到i=j,找到公共祖先。
int com_Ancestor(SqTree T,int i,j)
{
if(T[i ] ! = NULL && T[j] != NULL)//如果第i个结点不为空
{ while(i != j)//如果i不等于j则说明没有找到
{
if(i > j)//如果i大于j则i取一半,i往前走
{
i = i/2;
}
else//否则j大,取一半
{
j = j/2;
}
}
}
return T[i};
}
二叉树的遍历(递归),链式存储
typedef struct BiTNode
{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTNode
每个结点都访问一次,且只访问一次,时间复杂度为O(n),在最坏情况下,二叉树是有n个结点且深度为n的单只树
void preorder(BiTree T)
{
if(T != NULL)
{
visit(T);
preorder(T->lchild);
preorder(T->rchild);
}
}
void Inorder(BiTree T)
{
if(T != NULL)
{
Inorder(T->lchild);
visit(T);
Inorder(T->rchild);
}
}
void Postorder(BiTree T)
{
if(T != NULL)
{
Postorder(T->lchild);
Postorder(T->rchild);
visit(T);
}
}
二叉树的中序遍历(非递归)
中序遍历:左根右
1、沿着根的左孩子依次入栈,直到左子树为空
2、栈顶元素出栈,并访问,若出栈的结点右孩子为空则继续出栈
3、否则执行1
口诀:入栈向左一直走,出栈访问右子树
//中序遍历非递归
void Inorder(BiTree T)
{
InitStack(s);
BiTree p = T;
while(p != NULL || !IsEmpty(s))//当两个都不为空的时候才执行以下操作,p结点不为空且栈不为空
{
if(p != NULL)
{
push(s,p);
p = p->lchild;//一路往左走
}
else
{
pop(s,p);//栈顶元素出栈并访问
visit(p);
p = p->rchild;
}
}
}
二叉树的先序遍历(非递归)
前序遍历:根左右
//后序遍历非递归
void Inorder(BiTree T)
{
InitStack(s);
BiTree p = T;
while(p != NULL || !IsEmpty(s))//当两个都不为空的时候才执行以下操作,p结点不为空且栈不为空
{
if(p != NULL)
{
push(s,p);
visit(p);
p = p->lchild;//一路往左走
}
else
{
pop(s,p);//栈顶元素出栈并访问
p = p->rchild;
}
}
}
二叉树的后序遍历(非递归)
后续遍历:左右根
算法思想:
1、沿着根的左孩子依次入栈,直到左孩子为空
2、读栈顶元素,判断,
若右孩子不为空且没有被访问过,将右子树执行1
3、若右子树为空则或被访问过,栈顶元素出栈
口诀:入栈向左一直走,判定(右子树),出栈访问标记重置
void Postorder(BiTree T)
{
InitStack(s);
BiNode *p = T;
BiTNode r = NULL;//标记最近访问过的结点
if(p != NULL)//如果结点不为空则一直往左走,否则取栈顶元素
{
push(s,p);
p = p->lchild;//一直处理左子树
}
else
{
GetTop(s,p);//读栈顶元素
if(p->rchild && p->rchild != r)//如果右孩子不为空且没有被访问过,则处理右子树,否则将结点出栈并且标记被访问过
{
p = p->rchild;//先处理右子树
push(s,p);
p = p->lchild;
}
else
{
pop(s,p);//若被访问过或为空则直接出栈,并且访问
visit(p->data);
r = p;//标记被访问过
p = NULL;
}
}
}
二叉树的层次遍历
如果有左子树,则入队
如果有右子树,则入队
算法思想:将根结点入队,出队,访问出队结点,若它有左子树,则将左子树入队,如果它有右子树,则将右子树入队,反复进行直到为空。
入队出队访问,有左入左,有右入右
void leveorder(BiTree T)
{
InitQueue(Q);
BiTree p;
EnQueue(Q,T);//首先将根结点入队
while(!IsEmpty(Q))//如果队列不为空
{
DeQueue(Q,p);//出队根结点
visit(p);//访问
if(p->lchild != NULL)//如果左子树不为空则入队
{
EnQueue(Q,p->lchild);//若有右子树入队
}
if(p->rchild != NULL)//如果右子树不为空则入队
{
EnQueue(Q,p->rlchild);
}
}
}
二叉树自上而下,从右到左的层次遍历算法
1、队列出队元素入栈
算法思想:层次遍历利用原本层次遍历算法出队的同时入栈,再次出栈。
void leverorder(BiTree T)
{
InitQueue(Q);
InitStack(S);
BiTree p;
if(T != NULL)//如果二叉树不为空
{
EnQueue(Q,T);
while(!IsEmpty(Q))
{
DeQueue(Q,p);
//visit(p);
push(S,p);//不进行访问入栈
if(p->lchild != NULL)
{
EnQueue(Q,p->lchild);
}
if(p->rchild != NULL)
{
EnQueue(Q,p->rchild);
}
}
}
while(!IsEmpty(S))
{
pop(S,p);//出栈
visitd(p);
}
}
求二叉树的高度递归和非递归算法
算法思想:递归的求左子树高度,右子树高度取较大的+1
int Btdepth(BiTree T)
{
if(T == NULL)
{
return 0;
}
ldep = Btdepth(T->lchild);//求左高度
rdep = Btdepth(T->rchild);//求右高度
if(ldep > rdep)//如果左子树大则左+1
{
return ldep+1;
}
else
{
return rdep+1;
}
//return (ldep > rdep:ldep?rdep)+1;
}
算法思想:last指向最右结点,当指向最右结点的时候level加1
如何让last始终在最后一个结点呢?
int Btdepth(BiTree T)
{
if(T == NULL)
{
return 0;
}
int front = -1,rear = -1;
int last = 0;
int level = 0;
BiTree Q[MaxSize];
Q[++rear] = T;
BiTree p;
while(front < rear)//队列不空
{
p = Q[++front];//出队
if(p->lchild != NULL)
{
Q[++rear] = p->lchild;//入队
}
if(p->rchild != NULL)
{
Q[++rear] = p->rchild;
}
if(front == last)//相当于rear每走两步,front走一步,front==last时,last等于rear,层数+1
{
level++;
last = rear;//last指向最右结点
}
}
return level+1
}
交换二叉树左右子树
算法思想:递归的交换左右子树,先交换根结点的左右子树,然后交换根的
void swap(BiTree T)
{
BiNode *p = T;
int temp=;
if(p != NULL)
{
swap(p->lchild);//递归的处理左子树
swap(p->rchild);//递归的处理右子树
temp = p->lchild;
p->lchild = p->rchild;
p->rchild = temp;
}
}
求二叉树双分支(度为2)结点的个数
算法思想:
int DoubleNode(BiTree T)
{
BiNode *p = T;
if( p == NULL)//如果树为空
return 0;
else if(p->lchild != NULL && p->rchild != NULL)//是双分支结点
return (DoubleNode(p->lchild) + DoubleNode(p->rchild)) + 1;//本身结点加1
else
return DoubleNode(p->lchild) + DoubleNode(p->rchild;//如果不是双分支,则等于左子树+右子树
}
链表双分支左右结点都不为空,
求二叉树中度为1的结点个数
int Node(BiTree T)
{
BiNode *p = T;
if(p == NULL)
return 0;
//else if(p->lchild != NULL || p->rchild != NULL)
else if((p->lchild == NULL && p->rchild != NULL) || (p->lchild != NULL && p->rchild ==NULL))
return (Node(p->lchild) + Node(p->rchild)) + 1;//+1所因为本身也属于
else
return Node(p->lchild) + Node(p->rchild;
}
求二叉树中度为0的结点个数
int Node(BiTree T)
{
BiNode *p = T;
if(p == NULL)
return 0;
else if(p->lchild == NULL && p->rchild == NULL)
return (Node(p->lchild) + Node(p->rchild)) + 1;//+1所因为本身也属于
else
return Node(p->lchild) + Node(p->rchild;
}
求二叉树中叶子结点的个数
算法思想:递归法
int CountNode(BiTree T)
{
BiNode *p = T;
if( p == NULL)//递归边界
return 0;
if((p->lchild == NULL) && (p->rchild == NULL))//左子树为空且右子树为空
return 1;
n1 = CountNode(p->lchild);//递归式
n2 = CountNode(p->rchild);
return n1+n2;
}
算法思想:遍历法,如果是叶子结点则保留+1,不是叶子结点继续遍历
int CountNode(BiTree T)
{
BiTNode *p = T;
if(p != NULL)
{
if((p->lchild == NULL) && (p->rchild == NULL))
++n;
CountNode(p->lchild);
CountNode(p->rchild);
}
return n;
}
求二叉树中所有结点的个数
int CountNoude(BiTree T)
{
BiTNode *p = T;
int n1 = 0,n2 = 0;
if( p == NULL)
return 0;
else
{
n1 = CountNode(p->lchild);//递归求左子树
n2 = CountNode(p->rchild);//递归求右子树
}
return n1+n2+1;//左+右+根
}
算法思想:在先序遍历访问时visited,计数+1
int count(BiTree T)
{
int n;
BiTNode *p = T;
if(T != NULL)
{
n++;
count(p->lchild);
count(p->rchild);
}
}
求先序遍历中第k的结点的值
算法思想:先序遍历二叉树,求第k的结点的值
void preorder(BiTree T,int k)
{
int n;
BiTree *p = T;
if( p != NULL)
{
//visit(p);
++n;
if(n == k)//判断是第k个结点
{
printf("%d",p->data);
}
preorder(p->lchild);
preorder(p->rchild);
}
}
寻找data域中等于key的结点是否存在,若存在将q指向它,否则q为空
算法思想:遍历过程中,判断结点是都等于key,如果等于则q指向它,否则为空
void preorder(BiTree *T,int key,BiTNode *q)
{
BiTNode *p = T;
if( p != NULL)
{
if(p->data ==key)
{
q = p;
}
//visit(p);
preorder(p->lchild);
preorder(p->rchild);
}
}
将二叉树的叶子利用结点的右孩子指针(说明是二叉树链表结点),从左向右链接成一个单链表(head指向第一个,tail指向最后一个)
算法思想:递归的处理左子树和右子树,找到叶子结点,连城单链表,第一个结点的操作和剩余操作不一样
vold preorder(BiTree *p,BiTNode *head,BiTNode *taill)
{
if(p != NULL)
{
if( p->lchild == NULL && p->rchild == NULL)
{
if(head == NULL)//如果head说明为第一个叶子结点
{
head = p;//head指向p
tail = p;//tail指向p
}
else//如果head不为空只需要移动taill即可,因为head始终指向第一个结点
{
tail->rchild = p;//将tail的rchild指向p
tail = p;//将tail指向p,因为taill始终指向最后一个结点
}
}
}
//visit(p);//if部分相当于访问结点
preorder(p->lchild,head,taill);
preorder(p->rchild,head,taill);
}
求指定结点s的二叉排序树的层次
算法思想:根据二叉排序树的性质,左边的比根结点小,右边的比根结点大,查找结点,查找到就+1。
如果我们查找一个结点,值k大于根结点T则去左边找,如果k比当前根结点T大,则去右边找,因为根结点的右边都是大于T的值。
void BST_Search(BSTree *T,int k)//二叉排序树的查找
{
if(! T)
return ;
while (T != NULL)
{
if(T->data == K)
{
printf("%d",T->data);
break;
}
else if(T->data > K)
{
T = T->lchild;//如果结点的值大于k,则说明k比结点小去左子树找,因为左边的值都小于T。
}
else
{
T = T->rchild;//如果结点的值小于k,则取左子树找
}
}
}
int level(BiTree T)
{
BiTNode *p = T;
int n = 0;
if(T != NULL)
{
while(p->data != s->data)
{
if(p->data < s->data)//比s大去右边找,比s小去左边找
{
p = p->rchild;
}
else
{
p = p->lchild;
}
++n;//找到结点+1
}
}
return n;
}
求二叉树的WPL
WPL=叶子结点*深度
算法思想:递归,将所有叶子结点的WPL值累加
1、找叶子结点
2、
int WPL_preorder(BiTree root,int deep)
{
BiTNode *p = root;
int WPL = 0;
if(root != NULL)
{
//visited(P);
if((root->lchild == NULL) &&(root->rchild))//如果是叶子结点则计算WPL
{
WPL += deep*root->weight;//叶子结点的权重*深度
}
if(root->lchild != NULL)//如果左子树不空则求左子树
WPL_preorder(root->lchild,deep+1)
if(root->rchild != NULL)//如果右子树不空则求右子树
WPL_preorder(root->rchild,deep+1)
//preorder(p->lchild);
//preorder(p->rchild);
}
return WPL;
}
int WPL(BiTree root)
{
return WPL_preorder(root,0);//deep从0开始递归遍历求WPL
}
将给定表达式转换为等价的中缀表达式,通过括号反映计算次序
算法思想:
括号如何加 ?
1、根不加括号
2、叶子结点不加括号
3、其他结点加括号
中序访问左子树之前加
中序访问右子树之后加
void Inorder(BiTree *root,int deep)
{
if(root == NULL)//如果是空则 直接返回
{
return ;
}
else if(root->lchild == NULL && root->rchild == NULL)//如果是叶子结点,输出操作数,不加括号
{
printf();//不需要加括号
}
else
{
if(deep > 1)//若有子表达式,则加一层括号,中序访问之前加
printf("(");
Inorder(root->lchild,deep+1);
printf("%s",root->data);
Inorder(root->right,deep+1);
if(deep > 1)//若有子表达式,则加一层括号,中序访问之后加
printf(")")
}
}
void fun(BiTree *root)
{
Inorder(root,1);//根的高度为1
}
求二叉树中值为x的层号
int fun(BiTNode *p,int x)
{
int h = 1;
if(p != NULL)
{
if(p->data == x)
printf(h)
++h;//当第一次访问结点h+1
fun(p->lchild,x);
fun(p->rchild,x);
--h;//第三次访问结点h-1
}
return h;
}
增加一个指向双亲结点的parent,并指向指针赋值,并输出所有结点到根结点的路径
手动模拟
1、为什么增加parent指针
2、单个结点到根结点路径
3、遍历整棵树
//结构体
typedef struct BiTNode
{
char data;
struct BiTNode *parent,lchild,rchild;
}BITNode;
//增加parten指针,指向p结点的父结点
void fun(BiTNode *p,BiTNode *q)
{
if(p != NULL)//增加一个parent指针
{
p->parent = q;
q = p;
fun(p->lchild,q);
fun(p->rchild,q);
}
}
//单个结点输出路径
void print(BiTNode *p)
{
while(p != NULL)
{
printf();
p = p->parent;//通过partent指针实现输出结点
}
}
//输出所有结点的路径
void allprint(BiTree *T)
{
if(p != NULL)
{
print(p);
allprint(T->lchild);
allprint(T->rchild);
}
}
输出根结点到叶子结点的路径
1、遍历过程中碰到叶子结点开始处理(打印出来)
2、对特殊结点进行处理
3、借助一个栈
int path(BiTNode *p)
{
int i = 0,top = 0;
Elemtype stack[MAXSIZE];
if(p != NULL)//p不为空则入栈
{
stack[++top] = p->data;//入栈
}
if(p->lchild == NULL && p->rchild == NULL)//叶子结点则输出栈中元素
{
for(i = 0;i < top;i++)//输出栈中元素
{
printf(stack[i]);
}
}
path(p->lchild);//遍历
path(p->rchild);
--top;//当入栈时++top,退栈--top
}