二叉树(Binary Tree)相关算法的实现
写在前面:
二叉树是比较简单的一种数据结构,理解并熟练掌握其相关算法对于复杂数据结构的学习大有裨益
一.二叉树的创建
所谓的创建二叉树,其实就是让计算机去存储这个特殊的数据结构(特殊在哪里?特殊在它是我们自定义的)
首先,计算机内部存储都是线性的,而我们的树形结构是一种层级的,计算机显然无法理解,计算机能够接受的原始数据类型并不能满足我们的需求
所以,只好自定义一种数据结构来表示层级关系
实际上是要定义结构 + 操作,结构是为操作服务的,举个例子,我们要模拟买票的过程,现有的数据结构无法满足我们的需求(不要提数组...),我们需要的操作可能是:
1.获取站在买票队伍最前面的人
2.把买好票的人踢出队伍
3.第一个人买完票后,他后面的所有人都要“自觉”地向前移动
明确了这三个操作,再根据操作来定义结构,最后我们得到了队列(数组/链表 + 对应的函数)
二叉树也是这样,计算机看到的只是结构 + 操作,结构是Node集合(二叉链表),操作是创建、遍历、查找等等函数
结点:
struct bt
{
char data;
struct bt *left;
struct bt *right;
};
结点就是一个桶,两只手(桶里装数据,两只手伸出去抓左右两个孩子)
操作:
//createBT();
//printBT();
//deleteNode(Node node);
//...
-------上面是对二叉树的理解,下面是创建二叉树具体实现-------
二叉树的创建过程其实就是遍历过程(此处指递归方式),我们知道二叉树的任何一种遍历方式都可以把树形结构线性化(简单的说就是一组遍历结果可以唯一的表示一颗二叉树),因此可以根据遍历结果来还原一颗二叉树
先序遍历递归建树的具体思路:
1.读入当前根结点的数据
2.如果是空格,则将当前根置为空,否则申请一个新结点,存入数据
3.用当前根结点的左指针和右指针进行递归调用,创建左右子树
语言描述可能不太好懂,代码如下:
struct bt
{
char data;
struct bt *left;
struct bt *right;
}; void createBT(struct bt ** root)
{
char c;
c=getchar();
if(c == ' ')*root=NULL;//若为空格则置空
else
{
*root=(struct bt *)malloc(sizeof(struct bt));//申请新结点
(*root)->data=c;//存入数据
createBT(&((*root)->left));//建立当前结点的左子树
createBT(&((*root)->right));//建立当前结点的右子树
}
}
例如,如果我们要建立一个二叉树a(b, c),只要输入它的先序遍历结果ab××c××即可(×表示空格),其余两种建树方式于此类似,不再详述,至于非递归的建树方法参见下面的非递归遍历,非常相似
二.遍历
遍历在实现方式上有递归与非递归两种方式,所谓的非递归其实是由递归转化而来的(手动维护一个栈),开销(内存/时间)上可能非递归的更好一些,毕竟操作系统的栈中维护的信息更多,现场的保存与恢复开销都要更大一些
在遍历顺序上有3种方式:
1.先序遍历(根-左-右)
2.中序遍历(左-根-右)
3.后序遍历(左-右-根)
举个例子,二叉树a(b, c(d, e))的三种遍历结果分别是:
1.abcde
2.badce
3.bdeca
-------下面看看后序遍历的递归与非递归实现,其余的与之类似-------
后序遍历递归:
void postOrder(struct bt * root)
{
if(root == NULL)return;
else
{
postOrder(root->left);
postOrder(root->right);
putchar(root->data);
}
}
后序遍历非递归:
void postOrder(struct st* root)
{
struct st* stack[100];//声明结点栈
int top=-1;//栈顶索引
struct bt* p=root;//当前结点(present)
struct bt* q=NULL;//上一次处理的结点
while(p!=NULL||top!=-1)
{
for(;p!=NULL;p=p->left)stack[++top]=p;//遍历左子树
if(top!=-1)
{
p=stack[top];
if(p->right==NULL||p->right==q)//无右孩子,或右孩子已经遍历过
{
putchar(p->data);//输出根结点
q=p;
p=stack[top];
top--;
p=NULL;
}
else p=p->right;//遍历右子树
}
}
}
为了描述地更清晰,上面直接实现了栈的操作,当然,更规范的做法是将栈作为一个独立的数据结构封装起来,在我们的函数中调用栈提供的操作函数来进行相关操作
三.输出叶结点
检索特定结点的一系列操作都是建立在遍历的基础上的,输出叶结点就是一个例子,叶结点满足的条件是左右孩子都为空,我们只要在遍历中添加这样的判断条件就可以了
//此处采用先序遍历
void printLeaves(struct bt* root)
{
if(root == NULL)return;
else
{
if(root->left == NULL&&root->right == NULL)putchar(root->data);
else
{
printLeaves(root->left);
printLeaves(root->right);
}
}
}
于此类似的操作有,输出二叉树中满足一定条件的结点,删除指定结点,在指定位置添加结点(子树)...都是在遍历的基础上做一些额外的操作
四.计算树的深度
计算树深有多种方式,例如:
1.分别计算根下左右子树的高度,二者中的较大的为树深
2.最大递归深度为树深
...
我们采用第一种方式,更清晰一些
int btDepth(struct bt* root)
{
int rd,ld;
if(root==NULL)return 0;//空树深度为0
else
{
ld=1+btDepth(root->left);//递归进层,深度加1
rd=1+btDepth(root->right);//递归进层,深度加1
return ld > rd ? ld : rd;//返回最大值
}
}
五.树形输出
所谓树形输出,即对自然表示的二叉树逆时针旋转90度,其实仍然是在遍历的过程中记录递归层数,以此确定输出结果
//depth表示递归深度,初始值为0
void btOutput(struct bt* root,int depth)
{
int k;
if(root==NULL)return;
else
{
btOutput(root->right,depth+1);//遍历右子树
for(k=0;k<depth;k++)
printf(" ");//递归层数为几,就输出几个空格(缩进几位)
putchar(root->data);printf("\n");
btOutput(root->left,depth+1);//遍历左子树
}
}
//“右-中-左”的遍历顺序被称为“逆中序”遍历,采用这种顺序是为了符合输出规则(逆时针90度)
六.按层缩进输出
按层缩进输出就像代码编辑器中的自动缩进,从根结点开始逐层缩进,只需要对上面的代码稍作改动就可以实现
//k仍然表示递归深度,初始值为0
void indOutput(struct bt* root,int k)
{
int i;
if(root!=NULL)
{
for(i=1;i<=k;i++)
putchar(' ');
putchar(root->data);putchar('\n');
indOutput(root->left,k+1);
indOutput(root->right,k+1);
}
else return;
}
//按层缩进输出与树形输出的唯一区别就是遍历方式不同,前者是先序遍历,后者是逆中序遍历
七.按层顺序输出
按层顺序输出与前面提及的两种输出方式看似相似,实则有着很大不同,至少,我们无法简单地套用任何一种遍历过程来完成这个目标
所以,只能维护一个队列来控制遍历顺序
void layerPrint(struct bt* root)
{
struct bt* queue[100];//声明结点队列
struct bt* p;//当前结点
int amount=0,head,tail,j,k;//队列相关属性(元素总数,对头、队尾索引)
queue[0]=root;
head=0;
tail=1;
amount++;
while(1)
{
j=0;
for(k=0;k<amount;k++)
{
p=queue[head++];//取对头元素
if(p->left!=NULL)
{
queue[tail++]=p->left;//如果有则记录左孩子
j++;
}
if(p->right!=NULL)
{
queue[tail++]=p->right;//如果有则记录右孩子
j++;
}
putchar(p->data);//输出当前结点值
}
amount=j;//更新计数器
if(amount==0)break;
}
}
八.计算从根到指定结点的路径
要记录路径,当然不宜用递归的方式,这里采用后序遍历的非递归实现
为什么选择后序遍历?
因为在这种遍历方式中,某一时刻栈中现有的结点恰恰就是从根结点到当前结点的路径(从栈底到栈顶)。严格地说,此时应该用队列来保存路径,因为栈不支持从栈底到栈顶的出栈操作(这样的小细节就把它忽略好了...)
//参数c为指定结点值
void printPath(struct bt* root,char c)
{
struct st* stack[100];//声明结点栈
int top=-1;//栈顶索引
int i;
struct bt* p=root;//当前结点
struct bt* q=NULL;//上一次处理的结点
while(p!=NULL||top!=-1)
{
for(;p!=NULL;p=p->left)stack[++top]=p;//遍历左子树
if(top!=-1)
{
p=stack[top];//获取栈顶元素
if(p->right==NULL||p->right==q)//如果当前结点没有右孩子或者右孩子刚被访问过
{
if(p->data==c)//如果找到则输出路径
{
for(i=0;i<=top;i++)
{
p=stack[i];
putchar(p->data);
}
printf("\n");
//此处不跳出循环,因为可能存在不唯一的结点值,遍历整个树,找出所有路径
}
q=p;
p=stack[top];
top--;
p=NULL;
}
else p=p->right;//遍历右子树
}
}
}
九.完整源码与截图示例
源码:
#include<stdio.h> struct bt
{
char data;
struct bt *left;
struct bt *right;
}; void createBT(struct bt ** root)
{
char c;
c=getchar();
if(c == ' ')*root=NULL;
else
{
*root=(struct bt *)malloc(sizeof(struct bt));
(*root)->data=c;
createBT(&((*root)->left));
createBT(&((*root)->right));
}
} void preOrder(struct bt * root)
{
if(root == NULL)return;
else
{
putchar(root->data);
preOrder(root->left);
preOrder(root->right);
}
} void inOrder(struct bt * root)
{
if(root == NULL)return;
else
{
inOrder(root->left);
putchar(root->data);
inOrder(root->right);
}
} void printLeaves(struct bt* root)
{
if(root == NULL)return;
else
{
if(root->left == NULL&&root->right == NULL)putchar(root->data);
else
{
printLeaves(root->left);
printLeaves(root->right);
}
}
} int btDepth(struct bt* root)
{
int rd,ld;
if(root==NULL)return 0;
else
{
ld=1+btDepth(root->left);
rd=1+btDepth(root->right);
return ld > rd ? ld : rd;
}
} void btOutput(struct bt* root,int depth)
{
int k;
if(root==NULL)return;
else
{
btOutput(root->right,depth+1);
for(k=0;k<depth;k++)
printf(" ");
putchar(root->data);printf("\n");
btOutput(root->left,depth+1);
}
} void postOrder(struct st* root)
{
struct st* stack[100];
int top=-1;
struct bt* p=root;
struct bt* q=NULL;
while(p!=NULL||top!=-1)
{
for(;p!=NULL;p=p->left)stack[++top]=p;
if(top!=-1)
{
p=stack[top];
if(p->right==NULL||p->right==q)
{
putchar(p->data);
q=p;
p=stack[top];
top--;
p=NULL;
}
else p=p->right;
}
}
} void printPath(struct bt* root,char c)
{
struct st* stack[100];
int top=-1;
int i;
struct bt* p=root;
struct bt* q=NULL;
while(p!=NULL||top!=-1)
{
for(;p!=NULL;p=p->left)stack[++top]=p;
if(top!=-1)
{
p=stack[top];
if(p->right==NULL||p->right==q)
{
if(p->data==c)
{
for(i=0;i<=top;i++)
{
p=stack[i];
putchar(p->data);
}
printf("\n");
}
q=p;
p=stack[top];
top--;
p=NULL;
}
else p=p->right;
}
}
} void layerPrint(struct bt* root)
{
struct bt* queue[100];
struct bt* p;
int amount=0,head,tail,j,k;
queue[0]=root;
head=0;
tail=1;
amount++;
while(1)
{
j=0;
for(k=0;k<amount;k++)
{
p=queue[head++];
if(p->left!=NULL)
{
queue[tail++]=p->left;
j++;
}
if(p->right!=NULL)
{
queue[tail++]=p->right;
j++;
}
putchar(p->data);
}
amount=j;
if(amount==0)break;
}
} void indOutput(struct bt* root,int k)
{
int i;
if(root!=NULL)
{
for(i=1;i<=k;i++)
putchar(' ');
putchar(root->data);putchar('\n');
indOutput(root->left,k+1);
indOutput(root->right,k+1);
}
else return;
} void main()
{
char c;
struct bt * root;
printf("请输入先序遍历结果: ");
createBT(&root);
printf("先序遍历(preOrder)[递归]: \n");
preOrder(root);
printf("\n中序遍历(inOrder)[递归]: \n");
inOrder(root);
printf("\n后序遍历(postOrder)[非递归]: \n");
postOrder(root);
printf("\n叶结点(leaves): \n");
printLeaves(root);
printf("\n深度(depth): \n");
printf("%d\n",btDepth(root));
printf("树形输出(tree output): \n");
btOutput(root,0);
printf("缩进输出(indentation output): \n");
indOutput(root,0);
printf("请输入目标结点(target node): ");
getchar();
c=getchar();
printf("路径(path): \n");
printPath(root,c);
printf("按层输出(layerPrint): \n");
layerPrint(root);
printf("\n");
}
截图示例:
一颗较为复杂的二叉树:
其先序遍历结果为:ABD××EG×××C×FH××I××(×表示空,输入的时候要把×换成空格)
把D改成H再试一次(存在重复元素了,因该有两条到H的路径)
二叉树(Binary Tree)相关算法的实现的更多相关文章
- 算法与数据结构基础 - 二叉树(Binary Tree)
二叉树基础 满足这样性质的树称为二叉树:空树或节点最多有两个子树,称为左子树.右子树, 左右子树节点同样最多有两个子树. 二叉树是递归定义的,因而常用递归/DFS的思想处理二叉树相关问题,例如Leet ...
- [Swift]LeetCode968.监控二叉树 | Binary Tree Cameras
Given a binary tree, we install cameras on the nodes of the tree. Each camera at a node can monitor ...
- 学习笔记——二叉树相关算法的实现(Java语言版)
二叉树遍历概念和算法 遍历(Traverse): 所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问. 从二叉树的递归定义可知,一棵非空的二叉树由根结点及左. ...
- 数据结构-二叉树(Binary Tree)
#include <stdio.h> #include <string.h> #include <stdlib.h> #define LIST_INIT_SIZE ...
- [LeetCode] 889. Construct Binary Tree from Preorder and Postorder Traversal 由先序和后序遍历建立二叉树
Return any binary tree that matches the given preorder and postorder traversals. Values in the trave ...
- [LeetCode] Invert Binary Tree 翻转二叉树
Invert a binary tree. 4 / \ 2 7 / \ / \ 1 3 6 9 to 4 / \ 7 2 / \ / \ 9 6 3 1 Trivia: This problem wa ...
- [LeetCode] Binary Tree Inorder Traversal 二叉树的中序遍历
Given a binary tree, return the inorder traversal of its nodes' values. For example:Given binary tre ...
- [LeetCode] Closest Leaf in a Binary Tree 二叉树中最近的叶结点
Given a binary tree where every node has a unique value, and a target key k, find the value of the n ...
- [LeetCode] Maximum Width of Binary Tree 二叉树的最大宽度
Given a binary tree, write a function to get the maximum width of the given tree. The width of a tre ...
随机推荐
- 第八章 高级搜索树 (xa3)红黑树:插入
- 关于weblogic报UnsatisfiedLinkError Native Library xxx.so already loaded
一.场景 最近写的一个系统,在Tomcat测试完后说要改使用weblogic,于是在服务器上安装了weblogic,捣鼓了半天,一个个问题冒了出来,其中就有个比较麻烦的报错:UnsatisfiedLi ...
- Markdown的简单使用
markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式.(扩展名为.md) markdown语法 # 一级标题 ## 二级标题 ### ...
- linux-centos6-rabbitmq安装及配置
服务器版本centos6.8 一.先安装Erlang 具体安装哪个版本可以对照 http://www.rabbitmq.com/which-erlang.html 如下图: 准备安装RabbitMQ3 ...
- Netty系列(四)TCP拆包和粘包
Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...
- Zookeeper 系列(二)安装配制
Zookeeper 系列(二)安装配制 一.Zookeeper 的搭建方式 Zookeeper 安装方式有三种,单机模式和集群模式以及伪集群模式. 单机模式 :Zookeeper 只运行在一台服务器上 ...
- VS2010 MFC 使用GDI+给图片添加汉字
1.配置GDI+ VS2010自带GDI+,直接使用. (1)首先要添加头文件和库 #pragma comment( lib, "gdiplus.lib" ) #include & ...
- [转载红鱼儿]Delphi XE7 update1进步太大了
写以下的文字是怀着无比兴奋的心情写的,急于同朋友们分享XE7的进步! 1.更新的bug列表并不全 通过bug修正列表及发布的消息,可以看到up1修正了很多bug,正如我所说,有些bug并没有写到发布的 ...
- 2018.10.14 NOIP训练 猜数游戏(决策单调性优化dp)
传送门 一道神奇的dp题. 这题的决策单调性优化跟普通的不同. 首先发现这道题只跟r−lr-lr−l有关. 然后定义状态f[i][j]f[i][j]f[i][j]表示猜范围为[L,L+i−1][L,L ...
- 2018.07.06 BZOJ1208: HNOI2004宠物收养所(非旋treap)
1208: [HNOI2004]宠物收养所 Time Limit: 10 Sec Memory Limit: 162 MB Description 最近,阿Q开了一间宠物收养所.收养所提供两种服务:收 ...