最近几天学习了二叉树这一广泛使用的数据结构,并用C语言实现了根据前序扩展序列创建二叉树,前序遍历、中序遍历、后序遍历的递归遍历和非递归遍历,层序遍历以及打印二叉树的叶子结点,求二叉树的高度,根据前序序列、中序序列建立二叉树和根据中序序列、后序序列建立二叉树等功能。程序中用到了堆栈和队列的基本操作,复习了线性表的知识。
#include
#include
#define MaxSize 100
typedef struct BiTNode{
int data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
typedef struct stack{
BiTree elem[MaxSize];
int top;
} *Stack;
typedef struct queue
{
BiTree elem[MaxSize];
int front;
int rear;
} *Queue;
//堆栈函数
Stack CreateStack();
void CreateStack(Stack s);
int IsEmpty(Stack s);
int IsFull(Stack s);
void Push(Stack s, BiTree item);
BiTree Pop(Stack s);
//队列函数
Queue CreateQueue();
void AddQueue(Queue q, BiTree T);
BiTree DeletaQueue(Queue q);
int QueueLength(Queue q);
//二叉树函数
void CreateBiTree(BiTree *T);
void ProOrderTraversal(BiTree T);
void InOrderTraversal(BiTree T);
void PostOrderTraversal(BiTree T);
void LevelOrderTraversal(BiTree T);
void ProOrderTraversalNoRecursive(BiTree T);
void InOrderTraversalNoRecursive(BiTree T);
void PostOrderTraversalNoRecursive(BiTree T);
//二叉树应用函数
void PrintLeaves(BiTree T);
int GetTreeHeight(BiTree T);
BiTree RebuildTreeProIn(char *pro, char *in, int len);
BiTree RebuildTreeInPost(char *in, char *post, int len);
堆栈的栈顶指针指向最上边的一个元素。(会影响压栈退栈操作和栈顶指针加减法操作的先后顺序)
//创建堆栈
Stack CreateStack()
{
Stack s;
s = (Stack)malloc(sizeof(stack));
if (!s)
{
printf("初始化堆栈失败");
exit(-1);
}
s->top = -1;//初始化栈顶指针
return s;
}
//判断堆栈是否为空
int IsEmpty(Stack s)
{
return (s->top == -1) ? 1 : 0;
}
//判断堆栈是否为满
int IsFull(Stack s)
{
return (s->top == MaxSize - 1) ? 1 : 0;
}
//压栈操作
void Push(Stack s, BiTree item)
{
if (IsFull(s))
printf("堆栈满,无法插入元素");
else
s->elem[++(s->top)] = item;
}
//退栈操作
BiTree Pop(Stack s)
{
if (IsEmpty(s))
{
printf("堆栈空,无法弹出元素");
exit(-1);
}
else
return s->elem[(s->top)--];
}
队列的front指针指向第一个元素,rear指针指向最后一个元素的后边一个位置。(会影响进队退队操作和指针加法操作的先后顺序)
循环队列最多能存储MaxSize-1个元素,操作front指针和rear指针时记得对MaxSize取余。
//创建队列
Queue CreateQueue()
{
Queue q;
q = (Queue)malloc(sizeof(queue));
if (!q)
{
printf("初始化队列失败");
exit(-1);
}
q->front = 0;
q->rear = 0;
}
//入队操作(队列操作记得对MaxSize取余)
void AddQueue(Queue q, BiTree T)
{
if ((q->rear + 1) % MaxSize == q->front)
{
printf("队列满,无法插入元素");
exit(-1);
}
q->elem[q->rear] = T;
q->rear = (q->rear + 1) % MaxSize;
}
//出队操作
BiTree DeletaQueue(Queue q)
{
BiTree elem;
if (q->front == q->rear)
{
printf("队列满,无法插入元素");
exit(-1);
}
elem = q->elem[q->front];
q->front = (q->front + 1) % MaxSize;
return elem;
}
//队列长度
int QueueLength(Queue q)
{
return (q->rear - q->front + MaxSize) % MaxSize;
}
扩展序列指的是将二叉树中每个结点的空指针引出一个虚结点,其值为一特定值,比如“#”。
中序扩展序列和后序扩展序列不能唯一确定一个二叉树,即有不同的二叉树会对应同一个中序扩展序列或后序扩展序列。详情参考:点击打开链接
该程序中使用了指向指针的指针T作为函数参数,是因为函数中涉及到了对根节点指针修改,因此传递根节点指针的指针。
//根据前序扩展序列创建二叉树(递归实现)
void CreateBiTree(BiTree *T)//注意:T为指向结构体的指针的指针 为什么这么用呢?
{
char data, tmp;
scanf_s("%c", &data);
tmp = getchar();//过滤掉回车键,否则scanf_c会把回车键读进去,导致输出错误!
if (data == '#')
*T = NULL;
else
{
*T = (BiTree)malloc(sizeof(BiTNode));
if (!(*T)) exit(-1);
(*T)->data = data;
printf("输入%c的左子节点:", data);//这句便于调试、找错和验证结果的正确性!
CreateBiTree(&(*T)->lchild);
printf("输入%c的右子节点:", data);//这句便于调试、找错和验证结果的正确性!
CreateBiTree(&(*T)->rchild);
}
}
接下来是仅通过指针完成二叉树创建的程序,函数由于没有涉及指针的指针,较上一段程序更容易理解。
//根据前序扩展序列创建二叉树 特点是没有使用指针的指针,容易理解。
BiTree CreateBiTree()
{
BiTree T;
char data, tmp;
scanf_s("%c", &data);
tmp = getchar();//过滤回车键
if (data == '#')
T = NULL;
else
{
T = (BiTree)malloc(sizeof(BiTNode));
if(!T) exit(-1);
T->data = data;
printf("输入%c的左子节点:", data);
T->lchild = CreateBiTree();
printf("输入%c的右子节点:", data);
T->rchild = CreateBiTree();
}
return T;
}
递归实现是从二叉树的定义出发,比较容易理解。要注意的是终止递归的条件,即树为空。
//前序遍历二叉树(递归)
void ProOrderTraversal(BiTree T)
{
if (T)
{
printf("%c", T->data);
ProOrderTraversal(T->lchild);
ProOrderTraversal(T->rchild);
}
}
//中序遍历二叉树(递归)
void InOrderTraversal(BiTree T)
{
if (T)
{
InOrderTraversal(T->lchild);
printf("%c", T->data);
InOrderTraversal(T->rchild);
}
}
//后序遍历二叉树(递归)
void PostOrderTraversal(BiTree T)
{
if (T)
{
PostOrderTraversal(T->lchild);
PostOrderTraversal(T->rchild);
printf("%c", T->data);
}
}
非递归实现依赖于堆栈保存遍历到的节点。
二叉树中每个节点即是父母节点的左儿子,又是下一级的根节点。非递归遍历的核心是要先遍历完左子树并压栈,再按该方法依次对各层右子树进行遍历。前序遍历和中序遍历的实现十分相似,只需改变打印结点数据指令的位置即可。因为需要考虑右节点是否存在和有没有被访问过,后序遍历的非递归实现难度更大。
//前序遍历二叉树(非递归)
void ProOrderTraversalNoRecursive(BiTree T)
{
BiTree r = T;
Stack s = CreateStack();
while (r || !IsEmpty(s))
{
while (r)//输出父母节点、遍历左子树
{
printf("%c", r->data);
Push(s, r);
r = r->lchild;
}
if (!IsEmpty(s))
{
r = Pop(s);
r = r->rchild;
}
}
}
//中序遍历二叉树(非递归)
void InOrderTraversalNoRecursive(BiTree T)
{
BiTree r = T;
Stack s = CreateStack();
/*Stack s = NULL;
CreateStack(s);
printf("%d\n", s->top);*/
while (r || !IsEmpty(s))
{
while (r)//遍历左子树
{
Push(s, r);
r = r->lchild;
}
if (!IsEmpty(s))
{
r = Pop(s);
printf("%c", r->data);
r = r->rchild;
}
}
}
//后序遍历二叉树(非递归)
void PostOrderTraversalNoRecursive(BiTree T)
{
BiTree pre, r = T;
Stack s = CreateStack();
pre = NULL;
while (r || !IsEmpty(s))
{
while (r)
{
Push(s, r);
r = r->lchild;
}
if (!IsEmpty(s))
{
r = Pop(s);
if (r->rchild != NULL && r->rchild != pre)//右节点存在且没有被访问过
{
Push(s, r);
r = r->rchild;
}
else//右节点已经被访问过,处理相对根节点(父母节点)
{
printf("%c", r->data);
pre = r;
r = NULL;
}
}
}
}
层序遍历依赖于队列保存节点,且只能用非递归方法遍历。
void LevelOrderTraversal(BiTree T)
{
BiTree r = T;
Queue q;
if (!r) return;
q = CreateQueue();
AddQueue(q, r);
while (QueueLength(q))
{
r = DeletaQueue(q);
printf("%c", r->data);
if (r->lchild) AddQueue(q, r->lchild);
if (r->rchild) AddQueue(q, r->rchild);
}
}
这两个功能在递归遍历二叉树的基础上稍加修改即可实现。注意求二叉树高度只能在后序递归遍历的基础上稍加修改,因为求高度需要先求出左子树和右子树的高度。
//打印二叉树的叶子结点
void PrintLeaves(BiTree T)
{
if (T)
{
PrintLeaves(T->lchild);
PrintLeaves(T->rchild);
if (!T->lchild && !T->rchild)
printf("%c", T->data);
}
}
//求二叉树的高度
int GetTreeHeight(BiTree T)
{
int HL, HR, MaxH;
if (T)
{
HL = GetTreeHeight(T->lchild);
HR = GetTreeHeight(T->rchild);
MaxH = (HL > HR) ? HL : HR;
return MaxH + 1;
}
return 0;
}
编程思想是用递归的方法根据先序或后序序列逐级确定二叉树的根节点,从而确定二叉树。先序序列和后序序列不能唯一确定一个二叉树。
//根据前序序列和中序序列建立二叉树
BiTree RebuildTreeProIn(char *pro, char *in, int len)
{
BiTree t;
int i = 0;//头结点的下标
if (len <= 0)
t = NULL;
else
{
while (i < len && in[i] != *pro)//在中序序列中寻找根节点
i++;
if (i >= len)
{
printf("中序序列没有找到根节点,前序序列和中序序列有误,无法建立二叉树!");
exit(-1);
}
else
{
t = (BiTree)malloc(sizeof(BiTNode));
t->data = *pro;
t->lchild = RebuildTreeProIn(pro + 1, in, i);
t->rchild = RebuildTreeProIn(pro + i + 1, in + i + 1, len - 1 - i);//加起来的长度是len-1
}
}
return t;
}
//根据中序序列和后序序列建立二叉树
BiTree RebuildTreeInPost(char *in, char *post, int len)
{
BiTree t;
int i = 0;//头结点的下标
if (len <= 0)
t = NULL;
else
{
while (i < len && in[i] != post[len - 1])//在中序序列中寻找根节点
i++;
if (i >= len)
{
printf("中序序列没有找到根节点,中序序列和后序序列有误,无法建立二叉树!");
exit(-1);
}
else
{
t = (BiTree)malloc(sizeof(BiTNode));
t->data = in[i];
t->lchild = RebuildTreeInPost(in, post, i);
t->rchild = RebuildTreeInPost(in + i + 1, post + i, len - i - 1);//加起来的长度是len-1
}
}
return t;
}
//测试序列:AB#D##C##
//测试序列:ABD##FE###CG#H##I##
void main()
{
BiTree T;
char *pro = "ABCDEF", *in1 = "CBAEDF", *in2 = "ABCDEFG", *post = "BDCAFGE";
printf("请输入数据:");
CreateBiTree(&T);
printf("层序遍历结果为:");
LevelOrderTraversal(T);
printf("\n");
printf("前序遍历结果为:");
ProOrderTraversal(T);
printf("\n");
printf("非递归前序遍历结果为:");
ProOrderTraversalNoRecursive(T);
printf("\n");
printf("中序遍历结果为:");
InOrderTraversal(T);
printf("\n");
printf("非递归中序遍历结果为:");
InOrderTraversalNoRecursive(T);
printf("\n");
printf("后序遍历结果为:");
PostOrderTraversal(T);
printf("\n");
printf("非递归后序遍历结果为:");
PostOrderTraversalNoRecursive(T);
printf("\n");
printf("叶子结点为:");
PrintLeaves(T);
printf("\n");
printf("二叉树的高度为%d", GetTreeHeight(T));
printf("\n");
printf("**************************");
printf("后序遍历结果为:");
PostOrderTraversal(RebuildTreeProIn(pro, in1, 6));//正确输出结果为CBEFDA
printf("\n");
printf("前序遍历结果为:");
ProOrderTraversal(RebuildTreeInPost(in2, post, 7));//正确输出结果为EACBDGF
printf("\n");
}