#include
/*
* 二叉树按顺序存储
* 我们应该明白一点。二叉树的顺序存储是按照完全二叉树进行存储的!!
* 对完全二叉树按从上到下,从左到右的顺序依次编码1,2...n:它是有以下关系的:
* 当i>1时,结点的双亲编号为i/2(向下取整),即当i为偶数时,其双亲的编号为i/2,它是双亲的左孩子;它是奇数时,其双亲的编号为(i-1)/2,它是双亲的右孩子
* 当2i<=n时,结点i的左孩子编号为2i,否则无左孩子;
* 当2i+1<=n时,结点i的右孩子编号为2i+1,否则无右孩子;
* 结点i所在的层次(深度)为log2i+1
*
* 解题思路:
* (1)若i>j,则结点i所在层次大于等于结点j,那么若结点i的双亲结点=j,那么便是其最近公共祖先结点,否则令i=i/2,即以结点i的双亲结点为起点,继续查找
* (2) 若j>i,则结点i所在层次大于等于结点j,那么若结点j的双亲结点=i,那么便是其最近公共祖先结点,否则令j=i/2,即以结点j的双亲结点为起点,继续查找
*/
int common_ancestor(int* T,int i,int j){
if(i==j)
return i;
else if(i>j)
common_ancestor(T,i/2,j);//这里除以2就是它的双亲节点(上面提到的完全二叉树顺序存储关系)
else
common_ancestor(T,i,j/2);
}
int main(){
int T[]={0,1,2,3,0,4,0,5,0,0,6,0};
printf("%d",common_ancestor(T,2,10));
}
非递归解法:
int common_ancestor2(int* T,int i,int j){
if(T[i]!='#'&&T[j]!='#'){
while(i!=j){
if(i>j)
i=i/2;
else
j=j/2;
}
}
}
层次遍历
#include
/*
* 我们首先想到的层次序列都是自上而下,从左到右。欸,刚好相反,那么相反我们想到的是什么?没错,就是栈
* 首先就是利用原有的层次遍历算法,然后再出队的同时把各结点压入栈中。在所有结点都入栈之后,我们再从栈顶开始依次访问。
* 具体实现步骤:
* (1)首先把根结点入队列
* (2)把一个元素出队,接着把该结点入栈
* (3)如果该结点的左孩子不为空,那么把左孩子入队列; 如果该结点的右孩子不为空,那么把右孩子入队列;
* (4)若队列不为空。跳转到(2)继续执行
* (5)队列为空,跳出循环。对栈进行一次遍历。
*/
void InverLevel(BiTree bt){
Stack s,Queue Q;//创建栈和队列
BitNode* p;
if(bt!=NULL){
InitStack(s);//初始化栈
InitQueue(Q);//初始化队列
EnQueue(Q,bt);//根节点入队
while(!QueueEmpty(Q)){
DeQueue(Q,p);//出队列
Push(s,p);//并且入栈
if(p->lchild)
EnQueue(Q,p->lchild);//如果该结点的左孩子不为空,那么把左孩子入队列
if(p->rchild)
EnQueue(Q,p->rchild);//如果该结点的右孩子不为空,那么把右孩子入队列;
}
//跳出循环后,对栈进行一次遍历
while(!StackEmpty(s)){
Pop(s,p);
visit(p->data);
}
}
}
#include
/*
思想:
(1)孩子兄弟表示法,第一个指针指向第一个孩子,第二个指针指向下一个兄弟结点,
那么很显然,若结点的孩子指针为空,那它必是叶结点。
(2)总的叶子结点个数就是孩子子树上的叶子数和兄弟子树上的叶子数
步骤:
(1)如果树为空,返回0
(2)如果结点的孩子指针为空,返回叶结点(个数1)和其兄弟子树中的叶结点数
否则,返回孩子子树上的叶子数和兄弟子树上的叶子数
*/
typedef struct node{
ElemType data;
struct node *fch, *nsib;
}*CSTree;
int Leaves(CSTree t){
if(t==NULL) //如果树为空,返回0
return 0;
if(t->fch==NULL) //如果结点的孩子指针为空,返回叶结点(个数1)和其兄弟子树中的叶结点数
return 1 + leaves(t->nsib);
else //否则,返回孩子子树上的叶子数和兄弟子树上的叶子数
return leaves(t->fch) + leaves(t->nsib);
}
#include
/*
* 思路:
首先我们需要根据先序序列确定根结点A,然后通过A在中序序列中划分出二叉树的左右子树。
基本思路就是这样,仔细一想,是不是和快速排序很相似(先确定枢轴,然后通过枢轴划分。
再划分好的区域继续划分,思路基本上是一样的,所以我们这个算法采用的也是递归算法)
这个算法最后是自己动手画两个图,一个先序序列,一个后序序列,然后通过根结点去确定左子树和右子树的长度表达式
步骤 :
(1)根据先序序列确定树的根结点
(2)通过根结点在中序序列中划分出二叉树的左右子树。然后根据左右子树结点在先序序列中的次序确定根结点,
再继续跳回第(1)步,然后不断划分
如此重复上述步骤,直到每棵子树仅有一个结点为止。
*/
//A为先序序列,B为中序序列
BiTree PreInCreat(ElemType A[],ElemType B[],int l1,int r1,int l2,int r2){
//l1,r1为先序序列的第一和最后一个结点下标
//l2,r2为中序序列的第一和最后一个结点下标
BiTNode *root=(BiTNode *)malloc(sizeof(BiTNode));
root->data=A[l1]; //通过先序序列确定的根结点
for(int i=l2,B[i]!=root->data;i++) //确定中序序列的根结点
;
int lLen=i-l2; //左子树长度
int rLen=r2-i; //右子树长度
if(lLen)
root->lchild=PreInCreat(A,B,l1+1,l1+lLen,l2,i-1); //最后的i-1写成l2+len-1也一样
else
root->lchild=NULL;
if(rLen)
root->rchild=PreInCreat(A,B,r1-rLen+1,r1,r2-rLen+1,r2);
else
root->rchild=NULL;
return root;
}
递归
#include
/*
这道题相对来说还是比较简单的,就是先判断根结点是否是双分支结点,然后再判断其左右子树。直到所有子树都遍历完。
步骤:
递归出口:
如果树为空,返回0
递归体:
如果该结点左右子树不为空,递归其左右子树并 + 1
其他情况(该结点为叶子结点或单分支结点),递归其左右子树。
*/
int DsonNodes(BiTree T){
if(T==NULL)
return 0;
else if(T->lchild!=NULL&&T->rchild!=NULL)
return DsonNodes(T->lchild) + DsonNodes(T->rchild) + 1; //是双分支结点,+1
else
return DsonNodes(T->lchild) + DsonNodes(T->rchild);
}
递归
#include
/*
步骤:
(1)先把结点b的左孩子的左右子树进行交换,
(2)再对结点b的右孩子的左右子树进行交换
(3)最后交换结点b的左右孩子
当结点为空时递归结束!!
思想:
乍一看是不是很眼熟,没错,其实就是递归版的后序遍历!!再后序遍历的基础上增加了交换结点而已。
*/
void swap(BiTree T){
if(T){
swap(T->lchild);//递归地交换左子树
swap(T->rchild);//递归地交换右子树
//下面就是交换左右孩子
BiTree temp=T->lchild;
T->lchild=T->rchild;
T->rchild=temp;
}
}
递归
#include
/*
思想:
在先序遍历的基础上增加一个全局变量i,当i==k时返回结点即可
步骤:
(1)设置一个全局变量i记录已访问过的结点的序号,其初值根结点在先序序列的序号,即1
(2)如果是空树,返回特殊字符'#'
(3)当i==k时,表明找到目标结点,返回结点的值
(4)若i!=k,i++,并递归地在该结点的左子树中查找,若找到则返回,否则递归地在右子树中查找
*/
int i=1; //置一个全局变量i记录已访问过的结点的序号
ElemType PreNode(BiTree T,int k){
if(T==NULL)
return '#';
if(i==k)
return T->data;
i++; //下一个结点
ElemType ch=PreNode(T->lchild,k); //递归地在该结点的左子树中查找
if(ch!='#')
return ch; //在左子树中,则返回该结点
ch=PreNode(T->rchild,k); //递归地在右子树中查找
return ch;
}
#include
/*
思想:
对于一般二叉树,单纯通过先序序列是无法确定后序序列的。
但是对于满二叉树来说,
(1)其任意一个结点的左右子树的结点数相等。
(2)同时,其先序序列的第一个结点为后序序列的最后一个结点!!
步骤:
递归体:
(1)转换左子树,参数为先序序列的左子树,及其第一个结点,最后一个结点,后序序列的左子树,及其第一个结点,最后一个结点
(2)转换右子树,参数为先序序列的右子树,及其第一个结点,最后一个结点,后序序列的右子树,及其第一个结点,最后一个结点
*/
void PreToPost(ElemType pre[],int l1,int r1,ElemType post[],int l2,int r2){
int half;
if(l1<=r1){
post[r2]=pre[l1];
half=(r1-l1)/2;
PreToPost(pre, l1+1, l1+half, post, l2, l2+half-1);//转换左子树
PreToPost(pre, l1+half+1, r1, post, l1+half, r2-1);//转换右子树
}
}
中序遍历
#include
/*
思想:
题目说是将二叉树的叶结点按从左到右的顺序连成一个单链表,
那么就需要遍历单链表,通常我们所用的先序、中序、后序遍历对于叶结点的访问顺序都是从左到右,
所以我们只需要任选一种遍历算法,再此基础上对于遍历到的结点进行判断,如果是叶结点那么将其链入链表即可。
步骤:
(1)设置全局变量的头结点head和前驱结点pre(初始为空)。
(2)进行递归地中序遍历。
(3)对于遍历到的叶结点,第一个叶结点(其pre=NULL)需要另外处理:第一个叶结点由指针head指向
其余叶结点,则pre->rchild指向当前叶结点。
对于最后一个结点,其rchild指针为空
*/
LinkedList head,pre=NULL; //设置全局变量的头结点head和前驱结点pre(初始为空)。
LinkedList InOrder(BiTree bt){
if(bt){
InOrder(bt->lchild); //中序遍历左子树
if(bt->lchild==NULL&&bt->rchild==NULL){
if(pre==NULL){ //处理第一个结点
head=bt;
pre=bt;
}else{
pre->rchild=bt; //前驱结点指针pre的右指针指向当前结点
pre=bt;
}
}
InOrder(bt->rchild); //中序遍历右子树
pre->rchild=NULL; //设置链表尾
}
return head;
}
(1)递归
这道题递归相对比较简单
#include
/*
思想:
基于递归地先序序列遍历,在此基础上多了一个判断叶结点以及多了一个深度的参数。
并且用一个静态的变量记录wpl
步骤:
(1)若该结点是叶结点,则变量wpl加上该结点的深度与权值之积。
(2)若该结点是非叶结点,
则左子树不为空时,对左子树调用递归算法;
右子树不为空时,对右子树调用递归算法;
深度参数均为当前层次的深度+1;
(3)最后返回计算出的wpl即可
*/
typedef struct BiTNode{
int weight;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
int WPL(BiTree root){
return wpl_PreOrder(root,0);
}
int wpl_PreOrder(BiTreee root,int deep){
static wpl=0;
if(root->lchild==NULL&&root->rchild==NULL)
wpl+=root->weight*deep;
if(root->lchild!=NULL){
wpl_PreOrder(root->lchild,deep+1);
}
if(root->rchild!=NULL){
wpl_PreOrder(root->rchild,deep+1);
}
return wpl;
}
(2)层次遍历
层次遍历相对递归来说繁杂了点,但是如果层次遍历比较熟悉的话,也不是很难。其深度的判断和前面第3题的思路是一样的,就当做练习做做还是不错的。
#include
/*
思想:层次遍历的基础上,判断出队的结点是否是叶结点,若是叶结点,变量wpl加上该结点的深度与权值之积。
(可以参考第3题,就是多了个判断叶结点,然后计算wpl而已!!)
步骤:
(1)队列出队,当出队的结点为叶结点时,累计wpl。
(2)若为非叶结点,则把该结点的左、右子树加入队列
(3)接着把出队的结点和last指针比较,若两者相同则层次level+1。
队列为空时遍历结束,返回wpl
*/
#define MaxSize 100
typedef struct BiTNode{
int weight;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
int wpl_levelOrder(BiTree root){
BiTree Q[MaxSize]; //声明队列,front为头指针,rear为尾指针
int front=rear=-1;
int last=0; //指向当前层次最右结点,初始为根节点
int level=0; //记录当前结点的层次
int wpl=0; //初始化wpl
BiTree p; //暂存出队的结点
Q[++rear]=root; //根结点入队
while(frontlchild==NULL&&p->rchild==NULL)
wpl+=p->weight*level;
if(p->lchild)
Q[++rear]=p->lchild;
if(p->rchild)
Q[++rear]=p->rchild;
if(front==last){
level++;
last=rear; //last指向下一层最右结点
}
}
return wpl;
}
typedef struct node{
char data[10];
struct node *left,*right;
}BiTree;
#include
/*
思想:
表达式树的中序序列加上必要的括号即为中缀表达式。
表达式树中分支结点所对应的子表达式的计算次序,由该分支结点所处的位置决定。
所以,需要在生成遍历序列的同时,在适当的位置添加括号。
显然,表达式的最外层(对应根结点)和操作数(对应叶结点)时不需要添加括号的。所以函数还需要多加一个深度的参数
记住一点:分支结点对应表达式,叶结点对应操作数
步骤:
(1)如果是空树,返回
(2)若为叶结点,输出操作数,不加括号
(3)其他情况,
若深度大于1(根结点不需要加括号),加一层左括号
递归左子树
输出操作符
递归右子树
若深度大于1(根结点不需要加括号),加一层右括号
*/
void BtreeToE(BiTree *root){
BtreeToExp(root,0);
}
void BtreeToExp(BiTree *root,int deep){
if(root==NULL) //如果是空树,返回
return;
else if(root->left==NULL&&root->right==NULL) //若为叶结点,输出操作数,不加括号
printf("%s",root->data);
else{
if(deep>1) //若深度大于1(根结点不需要加括号),加一层左括号
printf("%s","(");
BtreeToExp(root->left,deep+1); //递归左子树
printf("%s",root->data;) //输出操作符
BtreeToExp(root->right,deep+1); //递归右子树
if(deep>1) //若深度大于1(根结点不需要加括号),加一层右括号
printf("%s",")");
}
}
#include
/*
思想:
利用二叉排序树的性质,左孩子结点 < 根结点 < 右孩子结点
所以最小的关键字应该是最左下结点,最大的关键字应该是最右下结点
*/
KeyType MinKey(BiTree T){
while(T->lchild)
T=T->lchild;
return T->data;
}
KeyType MaxKey(BiTree T){
while(T->rchild)
T=T->rchild;
return T->data;
}
#include
/*
思想:
利用二叉排序树的性质,左孩子结点 < 根结点 < 右孩子结点
所以为了从大到小输出,先遍历右子树,再访问根结点,最后遍历左子树
*/
void OutPut(BiTree T,KeyType k){
if(T==NULL)
return;
if(T->rchild)
OutPut(T->rchild,k);
if(T->data>=k)
printf("%d",T->data);
if(T->lchild)
OutPut(T->lchild,k);
}