storage.h
#ifndef STORAGE_H_INCLUDED
#define STORAGE_H_INCLUDED
//typedef oldName newName;
//二叉树的顺序存储结构
#define MaxSize1 50
typedef int SqBitree[MaxSize1]; //SqBitree bt; 从数组下表1开始存储结点,否则不匹配二叉树数学性质
//二叉树的链式存储结构(二叉链表)-介个用的多
typedef struct BitNode
{
int data;
struct BitNode *lchild, *rchild;
}BitNode, *Bitree; //BitNode *p;-通常表明一个结点, Bitree T;-通常表明一个树
//*Bitree 是结构体指针!
//https://blog.csdn.net/zhaojie911272507/article/details/106261997/
//栈的顺序存储结构
typedef struct
{
BitNode *data[MaxSize1]; //创建一个内存单位为BitNode大小的数组,共计有(MaxSize1)个
int top;
}SqStack; //SqStack S; 定义一个结构体变量
//链队列的存储结构-这才好用
typedef struct LinkNode
{
BitNode *data;
struct LinkNode *next;
}LinkNode;//定义一个结点
typedef struct
{
LinkNode *lifront, *lirear;
}LinkQuene;//定义队头队尾的指针变量
// testTree.cpp
void createBTree(BitNode *&p);
#endif // STORAGE_H_INCLUDED
order.h
#ifndef ORDER_H_INCLUDED
#define ORDER_H_INCLUDED
// order.cpp
void InitStack(SqStack &S); //创
bool StackEmpty(SqStack S); //判空
void PushStack(SqStack &S, Bitree e); //入栈(增)
void PopStack(SqStack &s, Bitree &x ); //出栈(删)
void GetTop(SqStack S, Bitree &y); //读取栈顶元素
void InitLiQuene(LinkQuene &Q); //创
void EnLiQuene(LinkQuene &Q, Bitree y);
int DeLiQuene(LinkQuene &Q, Bitree &z);
int IsLiQuEmpty(LinkQuene Q);
void visit(Bitree T); //访问函数--实际上定义为打印结点信息
void Preorder(Bitree T); //先序遍历
void Preorder1(Bitree T); //非递归的先序遍历
void Inorder(Bitree T);
void Inorder1(Bitree T);
void Postorder(Bitree T);
void Postorder1(Bitree T);
void Levelorder(Bitree T); //层序遍历+
// order.cpp 遍历的应用
void CreateBitree1(Bitree &T); //利用先序遍历创建二叉树
void CopyBitree(Bitree T, Bitree &newT); //复制一个二叉树
int DepthBitree1(Bitree T); //计算二叉树的深度
int NodeCount1(Bitree T); //统计二叉树的结点
int NodeCount_0(Bitree T); //统计度为0的结点
int NodeCount_1(Bitree T); //统计度为1的结点
int NodeCount_2(Bitree T); //统计度为2的结点
#endif // ORDER_H_INCLUDED
threadbitree.h
#ifndef THREADBITREE_H_INCLUDED
#define THREADBITREE_H_INCLUDED
/*
二叉链表-含n个结点的二叉树其有n+1个空指针。[叶结点空指针2个,度为1的结点空指针1个。n=n0+n1+n2=分支总数(2n2+n1)+1,所以n0=n2+1,然后换算即可)
n+1个空指针可以被利用,在指向其左右孩子的基础上,指向其线性序列(通过遍历而得)前驱或后继
同时需引入对应标志来标识是前驱后继还是左右孩子。
线索二叉树由此而来
通过遍历将二叉链表改为二叉线索树的方式成为二叉树的线索化
*/
//线索二叉树存储结构
typedef struct ThreadNode
{
int data;
struct ThreadNode *lchild, *rchild; //指向左右孩子(线索=0)或前驱后继(线索=1)的指针变量
int ltag, rtag; //左右线索标志
}ThreadNode, *Threadtree;
void Inthread(Threadtree &p, Threadtree &pre);
void createInthread(Threadtree &T);
ThreadNode *FirstNode(ThreadNode *p);
ThreadNode *LastNode(ThreadNode *p);
ThreadNode *NextNode(ThreadNode *p);
ThreadNode *PriorNode(ThreadNode *p);
#endif //THREADBITREE_H_INCLUDED
testTree.cpp
//原文链接:https://blog.csdn.net/weixin_45482422/article/details/108163699
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "storage.h"
/*
二叉树的先序遍历非递归算法
目标遍历的二叉树:
1
/ \
2 4
/ \
3 5
待输出结果为1,2,3,5,4
1.首先得用上面定义的结构体把这颗树表示出来
2.表示出这颗树后在调用二叉树的先序遍历非递归算法
*/
void createBTree(Bitree &p)
{
/*
生成这颗树,这里就是直接按照二叉树的样子来逐一生成
p做为根结点(root)
*/
p = (Bitree)malloc(sizeof(Bitree));
p->data = 1;
BitNode *n2, *n3, *n4, *n5 ;//分别对应存储元素2,4,3,5的4个结点
n2 = (BitNode*)malloc(sizeof(BitNode));
n3 = (BitNode*)malloc(sizeof(BitNode));
n4 = (BitNode*)malloc(sizeof(BitNode));
n5 = (BitNode*)malloc(sizeof(BitNode));
n2->data = 2;
n3->data = 4;
n4->data = 3;
n5->data = 5;
p->lchild = n2;
p->rchild = n3;
n2->lchild = n4;
n2->rchild = n5;
n3->lchild = n3->rchild = NULL;
n4->lchild = n4->rchild = NULL;
n5->lchild = n5->rchild = NULL;
}
order.cpp
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "storage.h"
//实际上 访问一个结点p时,栈中结点恰好是p结点所有祖先。从栈底到栈顶结点再加上p结点,刚好构成从根节点到p结点的一条路径
//栈的操作
void InitStack(SqStack &S)
{
S.top = -1; //top指针始终指向栈顶(且已存放数据)位置。top=0则表示指向栈顶下一个(且未存放数据)位置
}
bool StackEmpty(SqStack S) //栈判空
{
if(S.top == -1) //关注栈顶指针
return true;
else
return false;
}
void PushStack(SqStack &S, Bitree e) //入栈
{
if(S.top >=-1 && S.top < MaxSize1 -1 ) //栈顶指针合法值=[-1,MaxSize-1].所以如这样的存储结构申请下来,一个栈占用(MaxSize+1)个int空间。入栈前若栈满,只会溢出
S.data[++S.top] = e; //先自增栈顶指针后入栈,确保指针指向栈顶元素
}
void PopStack(SqStack &S, Bitree &x) //出栈
{
if(S.top >=-1 && S.top <= MaxSize1 -1 )
x = S.data[S.top--]; //先出栈再指针-1
}
void GetTop(SqStack S, Bitree &y)
{
if(S.top >=-1 && S.top <= MaxSize1 -1 )
y = S.data[S.top];
}
//队列的操作
void InitLiQuene(LinkQuene &Q)
{
Q.lifront=Q.lirear=(LinkNode*)malloc(sizeof(LinkNode)); //生成一个结点,front 和 rear均指向该结点
Q.lifront->next = NULL; //此时front 和 rear 指向同一个结点
}
void EnLiQuene(LinkQuene &Q, Bitree y)
{
LinkNode *x;
x = (LinkNode*)malloc(sizeof(LinkNode));
x->data = y;
//x->next = NULL; //王道书上有这么一句,可以写
Q.lirear->next = x;
Q.lirear = x;
}
int DeLiQuene(LinkQuene &Q, Bitree &z)
{
if(Q.lifront == Q.lirear)
return 0; //说明队列为空
LinkNode *p;
p = Q.lifront->next; //该队列有头结点!!!
z = p->data;
Q.lifront->next = p->next;
if(Q.lirear == p)
Q.lirear = Q.lifront; //原队列中只有一个结点,删除后都指向头结点,就是空列
free(p);
}
int IsLiQuEmpty(LinkQuene Q)
{
if(Q.lifront == Q.lirear)
return 1; //队头队尾均指向一个结点头结点,说明队列空
else
return 0;
}
void visit(Bitree T)
{
printf("%2d", T->data);
}
//先序遍历-根左右-递归
void Preorder(Bitree T)
{
if(T!=NULL)
{
visit(T);
Preorder(T->lchild);
Preorder(T->rchild);
}
}
//先序遍历-根左右-非递归(用栈的方式体现)
void Preorder1(Bitree T)
{
SqStack S;
InitStack(S);
Bitree p = T; //书上是这么写的,相对严谨一点
//注意此处的循环判断。犯错在于栈判空未取非,以至于左孩子为空的时候就会退出循环
while(p || !StackEmpty(S))
{
if(p)
{
visit(p);
PushStack(S, p);
p=p->lchild;
}
else
{
PopStack(S, p);
p=p->rchild;
}
}
}
void Inorder(Bitree T)
{
if(T!=NULL)
{
Inorder(T->lchild);
visit(T);
Inorder(T->rchild);
}
}
void Inorder1(Bitree T)
{
SqStack S; //局部变量
InitStack(S);
Bitree p = T; //为后续函数调用少写1个*,此处声明tree指针变量。同时也是从根节点开始遍历的,所以也能够理解。
while( p || !StackEmpty(S))
{
if(p)
{
PushStack(S, p);
p = p->lchild;
}
else
{
PopStack(S, p);
visit(p);
p = p->rchild;
}
}
}
void Postorder(Bitree T)
{
if(T!=NULL)
{
Postorder(T->lchild);
Postorder(T->rchild);
visit(T);
}
}
void Postorder1(Bitree T)
{
SqStack S;
InitStack(S);
Bitree p = T;
Bitree r = NULL;
while( p || !StackEmpty(S))
{
if(p)
{
PushStack(S, p);
p = p->lchild;
}
else
{
GetTop(S, p); //不能直接出栈,否则回退时,找不到该结点。因为该结点已经出栈了
if( p->rchild && p->rchild != r )
//访问会回退,因此只有设置另一指针变量r才能确保在进行下一次访问时,所访问结点不是上一次的。
{
p = p->rchild;
}
else
{
PopStack(S,p);
visit(p);
r = p;
p = NULL; //没有这个 输出将会一直是左下角第一个节点。为什么?
/*
左下角结点入栈后因为没有右孩子出栈,访问。然后重新开始while循环,
此时左下角结点再一次入栈。然后循环往复
*/
}
}
}
}
void Levelorder(Bitree T)
{
//队列先进先出,一个结点最多有左右两个孩子
LinkQuene Q;
InitLiQuene(Q);
EnLiQuene(Q, T);
Bitree p;
while(!IsLiQuEmpty(Q))
{
DeLiQuene(Q, p);
visit(p);
if(p->lchild != NULL) //判断条件不能写!p->lchild。!IsLiQuEmpty(Q)是因为函数会根据结果返回0或1的状态
EnLiQuene(Q, p->lchild);
if(p->rchild != NULL)
EnLiQuene(Q, p->rchild);
}
}
//
orderuse.cpp
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "storage.h"
//“遍历”是二叉树各种操作的基础
/* 遍历的应用 */
/*
1.表达式 先、中、后序遍分别对应前缀、中缀、后缀表达式
2.先序遍历建立二叉链表
3.复制二叉树
4.计算二叉树的深度
5.统计二叉树中结点数量
5.1统计二叉树中度为0的结点数
5.2统计二叉树中度为1的结点数
5.3统计二叉树中度为2的结点数
*/
/*
2.先序遍历建立二叉链表
算法思想:
肯定得是传递一个指向根节点的指针变量T来!
1)输入数字
2)指针变量T获取空间
3)数据域赋值
4)递归:递归生成左子树==传递当前结点的左指针进入函数(意思是使用存储当前结点的左指针变量)
5)递归:递归生成右子树
如何结束?肯定是输入特定字符就结束
1.1)加一个判断条件,当输入数字不为9999时,即可视作可输入数字,否则视为空。
*/
void CreateBitree1(Bitree &T)
{
int da;
printf("输入字符,9999建立空结点");
scanf("%d", &da);
if(da != 9999)
{
T = (Bitree)malloc(sizeof(BitNode)); //创建一个空间大小为BitNode型的空间,赋予地址给Bitree型结构体指针
T->data = da;
CreateBitree1(T->lchild);
CreateBitree1(T->rchild);
}
else
T = NULL; //请注意这个是必须的
}
/*
3.复制二叉树
算法思想:几乎和之前一致。
生成新结点、赋值、递归
如果节点是空的怎么办!空的必须赋予NULL!
*/
void CopyBitree(Bitree T, Bitree &newT)
{
if(T != NULL)
{
newT = (Bitree)malloc(sizeof(BitNode));
newT->data = T->data;
CopyBitree(T->lchild, newT->lchild);
CopyBitree(T->rchild, newT->rchild);
}
else
newT = NULL; //书上还写了一个 return;
}
/*
4.计算二叉树的深度
算法思想:
递归。
个人理解:再复杂的二叉树 最后都视作一个根结点,一个左孩子和一个右孩子的三角组合。
指向他们的方式是利用递归进行
这是用后序遍历的基础上的运算,是否可以用前序遍历的方式计算呢?
*/
int DepthBitree1(Bitree T)
{
int m = 0;
int n = 0;
if(T == NULL)
return 0;
else
{
m = DepthBitree1(T->lchild);
n = DepthBitree1(T->rchild);
}
if(m>n)
return (m+1);
else
return (n+1);
}
/*
5.统计二叉树中结点的个数
*/
int NodeCount1(Bitree T)
{
if(T == NULL)
return 0;
else
return NodeCount1(T->lchild)+NodeCount1(T->rchild)+1;
}
//https://blog.csdn.net/qq_36645322/article/details/102655196/
//这是王道书中认为比较重要的部分
//5.1.统计度为0的结点数
int NodeCount_0(Bitree T)
{
int i = 0;
if(T == NULL)
return 0;
if(T->lchild == NULL && T->rchild == NULL)
{
i = 1; //因为是叶结点,在此之后不会再有结点了。在此处就只累计1个
}
else
i = NodeCount_0(T->lchild) + NodeCount_0(T->rchild); //非叶结点,则使用递归查询左右子树
return i;
}
//5.2.统计度为1的结点数
int NodeCount_1(Bitree T)
{
int i = 0;
if(T == NULL)
return 0;
if((T->lchild == NULL && T->rchild != NULL) && (T->rchild == NULL && T->lchild != NULL))
i = 1 + NodeCount_1(T->lchild) +NodeCount_1(T->rchild);
else
i = NodeCount_1(T->lchild) +NodeCount_1(T->rchild);
return i;
}
//5.3.统计度为2的结点数
int NodeCount_2(Bitree T)
{
int i = 0;
if(T == NULL)
return 0;
if(T->lchild != NULL && T->rchild != NULL)
i = 1 + NodeCount_2(T->lchild) +NodeCount_2(T->rchild);
else
i = NodeCount_2(T->lchild) +NodeCount_2(T->rchild);
return i;
}
threadbitree.cpp
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "storage.h"
#include "order.h"
#include "threadbitree.h"
/*
中序线索二叉树构造
目的:利用二叉链表空余位置寻找前驱及后继
1.必有2指针,一前一后
2.每一次左右访问必有 是否有左右孩子的判断条件
3.类比于中序遍历
*/
void Inthread(Threadtree &p, Threadtree &pre)
{
if(p!=NULL)
{
Inthread(p->lchild, pre);
if(p->lchild == NULL)
{
p->lchild = pre; //后序指针(pre)是前序指针(p)的前驱
p->ltag = 1;
}
if(p->rchild == NULL && pre != NULL) //没有右孩子,就是有后继
{
pre->rchild = p; //先序指针是后序指针的后继
pre->ltag = 1;
}
pre = p;
Inthread(p->rchild, pre);
}
}
//不带头结点的中序线索二叉树的构造
void createInthread(Threadtree &T)
{
Threadtree pre = NULL;
if(T!=NULL)
{
Inthread(T,pre);
pre->rchild = NULL;
pre->ltag = 1;
//若额外附设头结点,其头结点被第一个结点的lchild指针(定义pre时,就指向头结点)和最后一个结点的rchild指针所指向。
}
}
//带头结点的中序线索二叉树的构造
void createInthread1(Threadtree &T)
{
Threadtree head;
head = (Threadtree)malloc(sizeof(ThreadNode*));
//头结点左指针,树空时指向自己,树非空指向根结点(视为左孩子)
//头结点右指针,初始化时指向自己,是线索
//后序指针pre指向头结点
head->ltag = 0;
head->rtag = 1;
head->rchild = head;
if(T == NULL)
head->lchild = head;
else
{
head->lchild = T;
Threadtree pre = head;
Inthread(T,pre);
pre->rchild = head;
pre->ltag = 1;
head->rchild = pre;
}
}
//求中序线索二叉树中 中序遍历序列的第一个结点
//第一个结点,左孩子一定为空
ThreadNode *FirstNode(ThreadNode *p)
{
while(p->ltag == 0)
p = p->lchild; //遍历已生成的线索二叉树,一直找左孩子直到其没有左孩子位置。那么这个就是中序遍历序列的第一个节点
return p;
}
//求中序遍历序列的最后一个结点
//最后一个结点,右孩子一定为空
ThreadNode *LastNode(ThreadNode *p)
{
while(p->rtag == 1)
p = p->rchild;
return p;
}
//求结点p在中序序列下的后继
ThreadNode *NextNode(ThreadNode *p)
{
if(p->rtag == 1)
return p->rchild; //如果其有后继标志,则直接返回后继结点
else
return FirstNode(p->rchild); //如果其有右孩子标志,则直接返回其右子树的第一个结点
}
//求结点p在中序序列下的前驱
ThreadNode *PriorNode(ThreadNode *p)
{
if(p->ltag == 1)
return p->lchild; //如果其有前驱标志,则直接返回前驱结点
else
return FirstNode(p->lchild); //如果其有左孩子标志,则直接返回其左子树的第一个结点
}
main.cpp
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "storage.h"
#include "order.h" //声明与遍历二叉树相关的函数
#include "threadbitree.h"
using namespace std;
int main()
{
Bitree T;
createBTree(T);
int i;
i = NodeCount1(T);
printf("---%d",i);
return 0;
}