链栈及树的基本操作
链栈的数据域存放的是树结点的指针 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;
}