[数据结构]史上最详细!用栈实现二叉树的建立以及先/中/后序遍历_C语言

链栈及树的基本操作

链栈的数据域存放的是树结点的指针 BiTNode* data;
top指针始终指向头结点, 入栈时对头结点进行后插操作

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

//树结点
typedef struct bitree {
	char data;
	struct bitree* lchild, * rchild;
}BiTNode, *BiTree;

//链栈结点
typedef struct LinkNode {
	BiTNode* data;
	struct LinkNode* next;
}LinkNode, *LinkList;

//链栈初始化, 栈顶指针指向头结点
LinkList InitLinkStack() {
	LinkList top = (LinkList)malloc(sizeof(LinkNode));
	if (top != NULL) {
		top->data = NULL;
		top->next = NULL;
	}
	return top;
}

//判空
bool IsEmpty(LinkList top) {
	if (top->next == NULL)
		return true;
	else return false;
}

//入栈,对top指针指向的结点后插
void Push(LinkList top, BiTNode* x) {
	LinkNode* p = (LinkList)malloc(sizeof(LinkNode));
	if (p != NULL) {
		p->data = x;
		p->next = top->next;
		top->next = p;
	}
}

//出栈
BiTNode* Pop(LinkList top) {
	if (top->next == NULL)
		return false;
	BiTNode* ret = top->next->data;
	LinkNode* p = top->next;
	top->next = p->next;
	free(p);
	return ret;
}

//取栈顶元素
BiTNode* GetTop(LinkList top) {
	return top->next->data;
}

//创建树结点
BiTNode* CreateTreeNode(char c) {
	BiTNode* t = (BiTree)malloc(sizeof(BiTNode));
	if (t != NULL) {
		t->data = c;
		t->lchild = NULL;
		t->rchild = NULL;
	}
	return t;
}

二叉树的建立

  • 输入先序序列建立二叉树, 若结点无孩子用#表示
  • 建立如下二叉树, 输入ABD###CE#G##F##
    在这里插入图片描述

递归

递归的方法实现起来肥肠煎蛋啦

注意函数的参数是二级指针

//递归先序创建二叉树
void CreateBiTree(BiTNode **t) {	//二级指针
	char c = getchar();
	if (c == '#')
		*t = NULL;
	else {
		*t = CreateTreeNode(c);
		CreateBiTree(&(*t)->lchild);
		CreateBiTree(&(*t)->rchild);
	}
}

非递归

  • 先创建根结点, 定义一个指针r指向根结点, 将r指向的结点入栈.
  • 定义一个标志flag. flag == 'l’时, 创建r的左孩子并将其入栈; flag == 'r’时, 创建r的右孩子并将其入栈.
  • 若是字母, 先创建r的左/右孩子, 再将r指向左/右孩子, 将r指向的结点入栈. 入栈后flag赋值为’l’.
  • 若是#, 栈顶元素出栈, r指向出栈的结点; 并将flag赋值为’r’.

注意与遍历不同的是, 创建完一个树结点后, r指向新建的结点, 此时不能使用r = r->lchild这样的操作. 应在r->lchild指向已存在的结点后, 再将r=r->lchild. 否则树的各个结点是断开的.

//非递归先序创建二叉树
BiTree PreCreateBiTree() {
	printf("请输入先序序列(无左右孩子用#表示)建立二叉树:");

	LinkList top = InitLinkStack();	//栈顶指针

	char c = getchar();
	BiTree root = CreateTreeNode(c);	//树的根结点

	BiTNode* r = root;
	Push(top, r);

	char flag = 'l';
	do {
		c = getchar();
		if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
			if (flag == 'l') {
				r->lchild = CreateTreeNode(c);
				r = r->lchild;
				Push(top, r);
			}
			if (flag == 'r') {
				r->rchild = CreateTreeNode(c);
				r = r->rchild;
				Push(top, r);
				flag = 'l';
			}
		}
		else if (c == '#') {
			if (!IsEmpty(top)) {
				r = Pop(top);
				flag = 'r';
			}
		}
	} while (c != '\n');
	free(top);
	return root;
}

先序遍历

  • 定义t指向根结点
  • (1)若t指向的结点非空, 访问输出, 入栈, 指向左孩子
  • (2)若t指向的结点为空, 栈顶元素出栈, t指向出栈结点的右孩子
  • 循环结束条件: 栈空且t指向空
//非递归先序遍历二叉树
void PreOrder(BiTree* root) {
	printf("先序:");
	LinkList top = InitLinkStack();	//栈顶指针
	BiTNode* t = root;
	while (!IsEmpty(top) || t != NULL) {
		if (t != NULL) {
			printf("%c ", t->data);
			Push(top, t);
			t = t->lchild;
		}
		else {
			t = Pop(top)->rchild;
		}
	}
	printf("\n");
	free(top);
}

中序遍历

  • 与先序遍历基本相同. 区别在于先序是入栈时访问根结点, 中序是出栈时访问根结点
//非递归中序遍历二叉树
void InOrder(BiTree* root) {
	printf("中序:");
	LinkList top = InitLinkStack();	//栈顶指针
	BiTNode* t = root;
	while (!IsEmpty(top) || t != NULL) {
		if (t != NULL) {
			Push(top, t);
			t = t->lchild;
		}
		else {
			t = Pop(top);
			printf("%c ", t->data);
			t = t->rchild;
		}
	}
	printf("\n");
	free(top);
}

后序遍历

最难的来了~ 先序/中序遍历相比可以说是肥肠煎蛋了(doge

  • 定义t指向根结点, 定义pre
  • (1)让t入栈并一直指向自己的左孩子, 直到t指向空. 此时再让t指回栈顶元素
  • (2)若右孩子也为空, 则栈顶元素出栈并访问. 此时让t指向空
  • 若右孩子非空, 则t指向右孩子. 重复(1)
  • 栈空并且t指向空, 循环结束.

以上过程有一个问题, 当一个结点的右孩子访问完之后, t指向空了, 然后t会指回这个结点(栈顶结点), 然后判断右孩子非空, 又指向了右孩子变成死循环了. 所以需再定义一个指针pre指向刚刚访问完的结点. 所以 右孩子为空或 非空但已经访问完了 时, 进行(2).

一图胜千言: 可以结合代码来看哦
在这里插入图片描述

//非递归后序遍历二叉树
void PostOrder(BiTree* root) {
	printf("后序:");
	LinkList top = InitLinkStack();	//栈顶指针
	BiTNode* t = root;
	BiTNode* pre = NULL;	//记录最近一次被访问的结点

	while (t != NULL || !IsEmpty(top)) {
		if (t != NULL) {
			Push(top, t);
			t = t->lchild;
		}
		else {	//t == NULL
			t = GetTop(top);
			if (t->rchild == NULL || t->rchild == pre) {	//右孩子空 或 (非空且已被访问过)
				pre = Pop(top);		//出栈访问并记录
				printf("%c ", pre->data);
				
				t = NULL;
			}
			else {	//若右孩子非空且未被访问过
				t = t->rchild;
				Push(top, t);
				t = t->lchild;
			}
		}
	}
	printf("\n");
	free(top);
}

最后main里面非常干净~

int main() {
	BiTree root = PreCreateBiTree();
	//BiTree root;
	//CreateBiTree(&root);
	PreOrder(root);
	InOrder(root);
	PostOrder(root);

	return 0;
}

运行结果

在这里插入图片描述

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值