目录
前言
在数据结构中,树是重要的一个章节,它里面含有着很多的思想,我们在很多的地方也会见到它。尤其是二叉树的知识,让我们有很多的用武之地,在这里,我们就了解一下二叉树的创建还有他的前中后序三种遍历方法,后面我还会写一篇关于他的层序遍历,还有一些计算出度入度的方法。
一、树是什么?
树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
二、二叉树是什么?
二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个节点最多只能有两棵子树,且有左右之分 [1] 。
二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个节点 [1] 。(百度定义)
就类型这种的就是二叉树,通俗的来说二叉树就是树只有左孩子,右孩子。
三、创建二叉树
Tree *Create_Tree()
{
//先申明一个空指针
Tree *root=NULL;
int data;
scanf("%d", &data);//通过输入的ch是否为特殊符号来判断该节点是否有孩子节点
//这里可以用任何的特殊字符,根据自己来定义
if(data == -1){ //不存在孩子节点
root=NULL;
}else{
//当这个数据需要存储的时候要,我们就需要离开开辟空间
root = (Tree *)malloc(sizeof(Tree));
//如果开辟失败,就返回
if(NULL == root){
printf("创建失败\n");
return NULL;
}
//将数据存储到根节点里面
root->data = data;
root->l_child = Create_Tree();//存在左孩子节点,递归调用本函数,使得左孩子节点先被赋值
root->r_child = Create_Tree();//存在右孩子节点,递归调用本函数,使得右孩子节点后被赋值
}
//最后将该节点返回
return root;
}
四、遍历方法
递归方法都是很简单的,所以我们在后面的介绍中,对递归的直接简单的介绍,大量精力再非递归上面。
1.先序遍历
1.递归方法
void first(Tree* tree)
{
//如果树为空的话,就返回
if(tree==NULL){
return ;
}
//打印该节点的数据
printf("%d\n",tree->data);
//递归调用,打印左孩子的
first(tree->l_child);
//递归调用,打印右孩子的
first(tree->r_child);
}
2.非递归
void Nrefirst(Tree* tree)
{
//先申明一个栈类型的指针,为了后面的操作
Tree *stack[MAX],*node;
int top=0;//初始化栈
//如果树为空的话,就返回
if(tree==NULL){
printf("这个树为空\n");
return ;
}else{
top++;//对索引进行操作,栈第一个位置不存储数据
stack[top]=tree;//将树存到栈里面
//如果栈为空,就退出循环
while(top>0){
//取出栈顶元素
node=stack[top];
top--;//对索引进行操作
printf("%d\n",node->data);//打印该节点的数据
//将该节点的右孩子先存到栈里面,再把左孩子存到里面,因为栈是先进后出的
if(node->r_child!=NULL){
top++;
stack[top]=node->r_child;
}
if(node->l_child!=NULL){
top++;
stack[top]=node->l_child;
}
}
}
}
2.中序遍历
1.递归
代码如下(示例):
void Middle(Tree* tree)
{
if(tree==NULL){
return ;
}
Middle(tree->l_child);
printf("%d\n",tree->data);
Middle(tree->r_child);
}
2.非递归
void Nremiddle(Tree* tree)
{
//先申明一个栈类型的指针,为了后面的操作
Tree *stack[MAX],*node;
//初始化栈为空
int top=0;
//如果树为空的话,就返回
if(tree==NULL){
printf("这个树为空\n");
return;
}
//用另外的指针来存树
node=tree;
//树为空,或者栈为空的时候,我们就返回
while(node!=NULL||top>0){
//将树的所有节点都先存到栈里面
while(node!=NULL){
top++;//我们栈的第一个空间不存储数据
stack[top]=node;//将数据存储到栈里面
node=node->l_child;//一直换到左孩子指针为空
}
//取出栈顶元素
node=stack[top];
top--;//对索引进行操作
printf("%d\n",node->data);//打印该数据
node=node->r_child;//对有孩子进行存储
}
}
3.后序遍历
1.递归
void last(Tree* tree)
{
if(tree==NULL){
return;
}
last(tree->l_child);
last(tree->r_child);
printf("%d\n",tree->data);
}
2.非递归
void Nrelast(Tree* tree)
{
//初始化栈,定义一个虚拟指针r,利用node把树存进去
Tree *stack[MAX],*node=tree,*r=NULL;
int top=0;//初始化栈为空
//如果树为空的话,就返回
if(tree==NULL){
printf("这个树为空\n");
return;
}
//树为空,或者栈为空的时候,我们就退出循环
while(node||top>0){
//先把左边的节点全部存到栈里面
while(node){
stack[top]=node;
top++;//对索引操作
node=node->l_child;
}
//因为之前的加一,所以这里我们需要减1
node=stack[top-1];
//此节点没有右孩子或者右孩子已经访问过
if(NULL==node->r_child||node->r_child==r){
printf("%d\n",node->data);
top--;
r=node;//记录最近访问过的节点
node=NULL;//节点访问过的节点
}else{
node=node->r_child;//右孩子存在则指向右孩子,重复上面操作
}
}
}
总结
二叉树是一个重要的数据结构,所以我们要认真了解,在整个过程中,我们要人认识到,栈的作用,所以我们对栈的索引,要仔细的引用,思考到每一步的操作。接下来就分享所有的代码。
#include<stdio.h>
#include<stdlib.h>
#define MAX 10
typedef struct node{
int data;
struct node *l_child;
struct node *r_child;
}Tree;
void Middle(Tree* tree);//中序遍历
void Nremiddle(Tree* tree);//非递归 非递归中序遍历
void first(Tree* tree);//先序遍历
void Nrefirst(Tree* tree);//非递归 前序遍历
void last(Tree* tree);//后序遍历
void Nrelast(Tree* tree);//非递归 后序遍历
Tree *Create_Tree();//创建二叉树
int main()
{
Tree*tree;
tree=Create_Tree();
printf("中序遍历\n");
Middle(tree);
printf("先序遍历\n");
first(tree);
printf("后序遍历\n");
last(tree);
printf("非递归中序遍历\n");
Nremiddle(tree);
printf("非递归先序遍历\n");
Nrefirst(tree);
printf("非递归后序遍历\n");
Nrelast(tree);
}
void Middle(Tree* tree)
{
if(tree==NULL){
return ;
}
Middle(tree->l_child);
printf("%d\n",tree->data);
Middle(tree->r_child);
}
void first(Tree* tree)
{
//如果树为空的话,就返回
if(tree==NULL){
return ;
}
//打印该节点的数据
printf("%d\n",tree->data);
//递归调用,打印左孩子的
first(tree->l_child);
//递归调用,打印右孩子的
first(tree->r_child);
}
void last(Tree* tree)
{
if(tree==NULL){
return;
}
last(tree->l_child);
last(tree->r_child);
printf("%d\n",tree->data);
}
Tree *Create_Tree()
{
//先申明一个空指针
Tree *root=NULL;
int data;
scanf("%d", &data);//通过输入的ch是否为特殊符号来判断该节点是否有孩子节点
//这里可以用任何的特殊字符,根据自己来定义
if(data == -1){ //不存在孩子节点
root=NULL;
}else{
//当这个数据需要存储的时候要,我们就需要离开开辟空间
root = (Tree *)malloc(sizeof(Tree));
//如果开辟失败,就返回
if(NULL == root){
printf("创建失败\n");
return NULL;
}
//将数据存储到根节点里面
root->data = data;
root->l_child = Create_Tree();//存在左孩子节点,递归调用本函数,使得左孩子节点先被赋值
root->r_child = Create_Tree();//存在右孩子节点,递归调用本函数,使得右孩子节点后被赋值
}
//最后将该节点返回
return root;
}
void Nremiddle(Tree* tree)
{
//先申明一个栈类型的指针,为了后面的操作
Tree *stack[MAX],*node;
//初始化栈为空
int top=0;
//如果树为空的话,就返回
if(tree==NULL){
printf("这个树为空\n");
return;
}
//用另外的指针来存树
node=tree;
//树为空,或者栈为空的时候,我们就返回
while(node!=NULL||top>0){
//将树的所有节点都先存到栈里面
while(node!=NULL){
top++;//我们栈的第一个空间不存储数据
stack[top]=node;//将数据存储到栈里面
node=node->l_child;//一直换到左孩子指针为空
}
//取出栈顶元素
node=stack[top];
top--;//对索引进行操作
printf("%d\n",node->data);//打印该数据
node=node->r_child;//对有孩子进行存储
}
}
void Nrefirst(Tree* tree)
{
//先申明一个栈类型的指针,为了后面的操作
Tree *stack[MAX],*node;
int top=0;//初始化栈
//如果树为空的话,就返回
if(tree==NULL){
printf("这个树为空\n");
return ;
}else{
top++;//对索引进行操作,栈第一个位置不存储数据
stack[top]=tree;//将树存到栈里面
//如果栈为空,就退出循环
while(top>0){
//取出栈顶元素
node=stack[top];
top--;//对索引进行操作
printf("%d\n",node->data);//打印该节点的数据
//将该节点的右孩子先存到栈里面,再把左孩子存到里面,因为栈是先进后出的
if(node->r_child!=NULL){
top++;
stack[top]=node->r_child;
}
if(node->l_child!=NULL){
top++;
stack[top]=node->l_child;
}
}
}
}
void Nrelast(Tree* tree)
{
//初始化栈,定义一个虚拟指针r,利用node把树存进去
Tree *stack[MAX],*node=tree,*r=NULL;
int top=0;//初始化栈为空
//如果树为空的话,就返回
if(tree==NULL){
printf("这个树为空\n");
return;
}
//树为空,或者栈为空的时候,我们就退出循环
while(node||top>0){
//先把左边的节点全部存到栈里面
while(node){
stack[top]=node;
top++;//对索引操作
node=node->l_child;
}
//因为之前的加一,所以这里我们需要减1
node=stack[top-1];
//此节点没有右孩子或者右孩子已经访问过
if(NULL==node->r_child||node->r_child==r){
printf("%d\n",node->data);
top--;
r=node;//记录最近访问过的节点
node=NULL;//节点访问过的节点
}else{
node=node->r_child;//右孩子存在则指向右孩子,重复上面操作
}
}
}