不得不说,第一次接触数据结构,二叉树的这一章让我多少有点觉得吃力,里面的代码实现,琢磨了几天才做出来,今天做一个整体代码总结,希望能帮助到大家。
前导知识
我们知道,一个满二叉树或完全二叉树,我们是完全可以使用顺序存储结构的。
因为我们满二叉树或者完全二叉树的结点个数较为完整,因为完全二叉树接近满二叉树,满二叉树是完全二叉树的特例,所以我们可以使用满二叉树的形式存储。
对于完全二叉树中有些结点无法与满二叉树完全对应,所以我们适当添加一些空结点来对应满二叉树。
但是如果是一个普通二叉树,仍然采用顺序存储结构的话,就会非常浪费空间,当只有右单分支的情况下,我们需要分配2的h次方-1的空结点,所以为了存储空间的节约,我们采用链式存储结构存储一棵普通二叉树。
二叉树的遍历分为递归和非递归遍历。
递归遍历分为先序遍历,后序遍历,中序遍历
创建一棵二叉树
这里建议使用getChar(),因为getChar()可以避免空格和回车字符,scanf必须在占位符前面加上一个空格才能避免把回车当作一个字符
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <stdbool.h>
#define MAXSIZE 100
typedef struct TreeNode{
char data;
struct TreeNode *lchild;
struct TreeNode *rchild;
}TreeNode;
TreeNode* create(TreeNode *treeList){
char value;
printf("请输入一个字符\n");
scanf(" %c",&value);//注意啊,回车也会被当作一个字符
if(value=='#'){
treeList=NULL;
}else{
treeList=(TreeNode *)malloc(sizeof(TreeNode));
treeList->data=value;
//由于每一层都有返回值,
//所以我们必须保存每次递归后的左右节点的值,否则链表就会连接不起来
treeList->lchild=create(treeList->lchild);
treeList->rchild=create(treeList->rchild);
}
return treeList;
}
先序递归遍历
先输出根结点,然后遍历每个结点的左结点,直到左结点为空,我们在循环遍历右结点,以此类推直到遍历整个二叉树
void PreOrderOut(TreeNode *treeList){//先序输出
if(treeList!=NULL){
printf("先根结点:%c\n",treeList->data);//输出根结点
PreOrderOut(treeList->lchild);//遍历左结点,直到左结点为空
PreOrderOut(treeList->rchild);//遍历右结点,直到右结点为空
}
}
中序和后序递归遍历也是再这个基础之上进行变换,是不是很简单,三句话就解决问题,只要你逻辑够清楚,就很好理解了。
void InOrderOut(TreeNode *treeList){//中序输出
if(treeList!=NULL){
InOrderOut(treeList->lchild);
printf("中序根结点:%c\n",treeList->data);
InOrderOut(treeList->rchild);
}
}
void LastOrderOut(TreeNode *treeList){//后序输出
if(treeList!=NULL){
LastOrderOut(treeList->lchild);
LastOrderOut(treeList->rchild);
printf("后续根结点:%c\n",treeList->data);
}
}
层次遍历
所谓层次遍历,就是从根结点开始,从上到下,从左到右依次遍历二叉树每个结点
这里为了方便,我们采用循环队列来存储结点,队列的特点是先进先出,刚好满足我们的层次遍历的特点。依次从根节点开始,将第一个结点存储到队列中,队列的队尾下标加1,然后再输出,然后再保存左结点和右节点,之后再输出左节点,保存左节点的孩子结点,队列的队头下标加1,然后再输出右节点,然后保存右节点的孩子节点,以保证每次循环不丢失下一个结点的位置,直到队头队尾位置相等了,就说明此时已经遍历完毕了。
typedef struct TreeNode{//二叉树存储结构
char data;
struct TreeNode *lchild;
struct TreeNode *rchild;
}TreeNode;
typedef struct QueueNode{//队列存储结构
TreeNode *data[MAXSIZE];
int top;
int base;
}QueueNode;
void push1(QueueNode *queuenode,TreeNode *treeList){//入队方法
printf("进队列:%c\n",treeList->data);
if(treeList!=NULL&&((queuenode->top+1)%MAXSIZE!=queuenode->base)){
queuenode->data[queuenode->top]=treeList;
//printf("打印一下:%c,%d\n",queuenode->data[queuenode->top]->data,queuenode->top);
queuenode->top=(queuenode->top+1)%MAXSIZE;
}
}
TreeNode* pop1(QueueNode *queuenode){//出队方法
TreeNode *treenode;
if(queuenode->top!=queuenode->base){//只要队头不等于队尾,就代表当前队列还不为空,还有结点
treenode=queuenode->data[queuenode->base];
//printf("输出值:%c,%d\n",treenode->data,queuenode->base);
queuenode->base=(queuenode->base+1)%MAXSIZE;//这里我们采用循环队列来实现存储,这样可以更有效的利用空间
}
return treenode;
}
void levelOrderOut(TreeNode *treeList){//层次遍历
QueueNode *queuenode;
TreeNode *p;
queuenode=(QueueNode*)malloc(sizeof(QueueNode));
queuenode->top=queuenode->base=0;
if(treeList!=NULL)
push1(queuenode,treeList);
while(queuenode->top!=queuenode->base){
p=pop1(queuenode);
if(p->lchild!=NULL)//注意细节啊,在完整执行一次后,如果自己估算的没问题,就可能时变量什么的错了
push1(queuenode,p->lchild);
if(p->rchild!=NULL)
push1(queuenode,p->rchild);
}
}
非递归的实现就稍微复杂一点了
先序非递归二叉树
非递归二叉树的遍历方式和递归二叉树的区别在于,我们需要保存其左右结点,以保证每个结点能正确被访问到,而递归层层嵌套,每一层执行完毕会回到上一层,所以我们可以不必保存每个结点。
我们可以使用栈来保存遍历的结点,栈的特点是后进先出,先保存当前根结点,然后输出,再保存右结点,栈顶位置加1,保存左节点,后进先出,所以先输出左节点,栈顶位置减1,然后再保存左节点的孩子节点,再输出右结点,保存右结点孩子结点,以此类推,直到二叉树遍历完毕。
typedef struct StackNode{//栈存储结构
TreeNode *top[MAXSIZE];
int length;
}StackNode;
typedef struct TreeNode{//二叉树存储结构
char data;
struct TreeNode *lchild;
struct TreeNode *rchild;
}TreeNode;
void push(StackNode *stacknode,TreeNode *treeList){//入栈方法
if(treeList!=NULL){
stacknode->top[stacknode->length]=treeList;
stacknode->length++;
}
}
TreeNode* pop(StackNode *stacknode){//出栈方法
TreeNode *node;
if(stacknode->length!=0)
stacknode->length--;
node=stacknode->top[stacknode->length];
printf("出栈一个结点:%c\n",node->data);
return node;
}
void PreOrderIn(TreeNode *treeList){//先序非递归遍历
StackNode *stacknode;
TreeNode *node;
stacknode=(StackNode*)malloc(sizeof(StackNode));
stacknode->length=0;
if(treeList!=NULL){
push(stacknode,treeList);
while(stacknode->length !=NULL){
node=pop(stacknode);
if(node->rchild!=NULL){
push(stacknode,node->rchild);
}
if(node->lchild!=NULL){
push(stacknode,node->lchild);
}
}
}
}
中序非递归二叉树
中序非递归二叉树的特点是先入栈所有的左结点,然后再依次输出左节点,保存其右节点,然后再输出右结点,再以同样的方式遍历输出,直到整个二叉树遍历完毕。
typedef struct StackNode{//栈存储结构
TreeNode *top[MAXSIZE];
int length;
}StackNode;
typedef struct TreeNode{//二叉树存储结构
char data;
struct TreeNode *lchild;
struct TreeNode *rchild;
}TreeNode;
void push(StackNode *stacknode,TreeNode *treeList){//入栈方法
if(treeList!=NULL){
stacknode->top[stacknode->length]=treeList;
stacknode->length++;
}
}
TreeNode* pop(StackNode *stacknode){//出栈方法
TreeNode *node;
if(stacknode->length!=0)
stacknode->length--;
node=stacknode->top[stacknode->length];
printf("出栈一个结点:%c\n",node->data);
return node;
}
void InOrderIn(TreeNode *treeList){//中序非递归遍历
StackNode *stacknode;
TreeNode *p;
TreeNode *node;
p=treeList;
stacknode=(StackNode*)malloc(sizeof(StackNode));
stacknode->length=0;
while(p!=NULL||stacknode->length!=0){//栈不为空或者树不为空则执行
while(p!=NULL){
push(stacknode,p);//让所有左孩子进栈
p=p->lchild;
}
if(stacknode->length!=0){
node=pop(stacknode);
push(stacknode,node->rchild);//每个结点的右孩子进栈
}
}
}
后序非递归遍历
后序非递归与前面不同的是,这里采用了do-while循环,由于我们的栈初始化是为空的,所以不能直接使用while循环,而是先执行一次再判断,后序遍历的规则是左结点,右节点,根节点,所以我们同样遍历所有左结点并保存,然后我们利用flag来表示我们当前在操作栈顶元素,如果当前flag为false,代表当前我们没有在操作栈顶元素,而是在操作未入栈的元素,因此我们将当前未入栈的右节点入栈,再重新按照一开始的遍历方式从栈顶元素开始操作,然后我们获取当前的栈顶元素,判断此时的结点的右孩子是否为空或者是已经遍历过的结点,我们用r保存,如果是,就更新当前变量r的结点,如果不是,就移动到右节点位置,保存右节点到栈中,再重新将所有变量置空,读取当前的右节点,并且出栈,并且将当前t置空,以保证每次操作的都是一个新的结点,而不是已经操作过的旧结点,就这样依次类推,完成遍历。
void push(StackNode *stacknode,TreeNode *treeList){
if(treeList!=NULL){
stacknode->top[stacknode->length]=treeList;
stacknode->length++;
}
}
TreeNode* pop(StackNode *stacknode){
TreeNode *node;
if(stacknode->length!=0)
stacknode->length--;
node=stacknode->top[stacknode->length];
printf("出栈一个结点:%c\n",node->data);
return node;
}
TreeNode* GetTop(StackNode *stacknode,TreeNode *p){//获取栈顶结点
if(p!=NULL){
return p;
}else{
p=stacknode->top[stacknode->length-1];
}
return p;
}
void LastOrderIn(TreeNode *treeList){//后序非递归
StackNode *stacknode;
stacknode=(StackNode*)malloc(sizeof(StackNode));
stacknode->length=0;
TreeNode *p;
TreeNode *t;
TreeNode *r;
bool flag;
p=treeList;
do{
while(p!=NULL){
push(stacknode,p);
p=p->lchild;
}
r=NULL;
t=NULL;
flag=true;
while(flag&&stacknode->length!=0){
t=GetTop(stacknode,t);
if(t->rchild==r){
r=pop(stacknode);
t=NULL;
}else{
t=t->rchild;
push(stacknode,t);
flag=false;
}
}
} while(stacknode->length!=0);
}
后序非递归这一块解释有一点绕可能(我讲的不够好,望各位见谅),如果各位看不懂或者还有什么不明白可以直接评论,我再给你们解释,这些代码都是我实战一遍之后才放出来给大家看的,所以执行上有什么问题可以找我。
虽然看起来复杂,但是我相信天下无难事,只怕有心人。加油!!!