树的学习——(递归构建二叉树、递归非递归前序中序后序遍历二叉树、根据前序序列、中序序列构建二叉树)

20 篇文章 0 订阅
7 篇文章 16 订阅

前言

最近两个星期一直都在断断续续的学习二叉树的数据结构,昨晚突然有点融汇贯通的感觉,这里记录一下吧

题目要求

给定前序序列,abc##de#g##f###,构建二叉树,并且用递归和非递归两种方法去做前序,中序和后序遍历

二叉树的数据结构

#define STACKSIZE 10005

/**
 * 二叉树的数据结构
 */
typedef struct btree {
	struct btree *lchild;
	struct btree *rchild;
	char item;
} btree;

typedef btree *bt;

/**
 * 定义顺序栈数据结构(非递归遍历)
 */
typedef struct stack {
	btree *db[STACKSIZE];
	int top;
} stack;


递归构建二叉树

构建二叉树有固定的几种考察类型:
  1. 根据完整的先序序列构建二叉树
  2. 根据前序和中序序列构建二叉树

根据先序序列构建二叉树(c语言实现)

char str[101] = "abc##de#g##f###";
int count = 0;

/**
 * 根据先序序列构建二叉树(因为涉及到对根节点指针修改,因此传递根节点指针的指针)
 */
void createBtree(btree **t)
{
	if (str[count ++] == '#') {
		*t = NULL;
	} else {
		*t = (btree *)malloc(sizeof(btree));
		(*t)->item = str[count - 1];
		createBtree(&(*t)->lchild);
		createBtree(&(*t)->rchild);
	}
}


递归的前序、中序、后序算法(c语言实现)

/**
 * 递归先序遍历二叉树
 */
void recPreorder(btree *t)
{
	if (t) {
		printf("%c", t->item);
		recPreorder(t->lchild);
		recPreorder(t->rchild);
	}
}

/**
 * 递归中序遍历二叉树
 */
void recInorder(btree *t)
{
	if (t) {
		recInorder(t->lchild);
		printf("%c", t->item);
		recInorder(t->rchild);
	}
}

/**
 * 递归后序遍历二叉树
 */
void recPostorder(btree *t)
{
	if (t) {
		recPostorder(t->lchild);
		recPostorder(t->rchild);
		printf("%c", t->item);
	}
}


非递归前序、中序遍历算法(c语言实现)

/**
 * 非递归前序遍历
 */
void preorderTraverse(btree *t)
{
	btree *p = t;

	// 初始化栈
	stack *s = (stack *)malloc(sizeof(stack));
	s->top = 0;

	while (p || s->top > 0) {
		if (p) {
			printf("%c", p->item);
			s->db[s->top ++] = p;
			p = p->lchild;
		} else {
			p = s->db[-- s->top];
			p = p->rchild;
		}
	}
}

/**
 * 非递归中序遍历
 */
void inorderTraverse(btree *t)
{
	btree *p = t;

	// 初始化栈
	stack *s = (stack *)malloc(sizeof(stack));
	s->top = 0;

	while (p || s->top > 0) {
		if (p) {
			s->db[s->top ++] = p;
			p = p->lchild;
		} else {
			p = s->db[-- s->top];
			printf("%c", p->item);
			p = p->rchild;
		}
	}
}


非递归后序遍历算法(c语言实现)

算法思想:

  1. 首先,也是找到最左边的叶子结点并把路上遇到的节点依次入栈
  2. 然后,弹出栈顶元素(该元素为最左边的叶子),判断(1)它是否有右节点(2)如果有右节点,是否被访问过。如果满足(1)有右节点并且(2)右节点没有访问过,说明这是后序遍历的相对根节点,因此需要将这个节点再次入栈,并且它的右节点入栈,然后重新执行第一步。否则,就访问该节点,并且设置pre为此节点,同时把将遍历节点附空值,访问进入无限循环

算法代码:

/**
 * 非递归后序遍历
 */
void postTraverse(btree *t)
{
	btree *p, *pre;
	p = t;
	pre = NULL;

	// 初始化栈
	stack *s = (stack *)malloc(sizeof(stack));
	s->top = 0;

	while (p || s->top > 0) {
		if (p) {
			s->db[s->top ++] = p;
			p = p->lchild;
		} else {
			p = s->db[-- s->top];
			if (p->rchild != NULL && p->rchild != pre) { // p为相对根节点
				s->db[s->top ++] = p;
				p = p->rchild;
			} else {
				printf("%c", p->item);
				pre = p;
				p = NULL;
			}
		}
	}
}

注意:

严蔚敏的<<数据结构>>上有一段话很经典,摘录如下:”从二叉树遍历的定义可知,三种遍历算法之不同处仅在于访问根节点和遍历左、右子树的先后关系。如果在算法中暂且抹去和递归无关的visit语句,则三个遍历算法完全相同。因此,从递归执行过程的角度来看,前序、中序、后序遍历也完全相同。“ 这段话给我们的提示就是,前序、中序、后序遍历的算法相同,只是printf()语句位置而已。


根据前序序列、中序序列构建二叉树

函数定义

bt rebuildTree(char *pre, char *in, int len);

参数:
* pre:前序遍历结果的字符串数组
* in:中序遍历结果的字符串数组
len : 树的长度
例如:
前序遍历结果: a b c d e f g h
中序遍历结果: c b e d f a g h

算法思想

  1. 递归思想,递归的终止条件是树的长度len == 0
  2. 在中序遍历的数组中找到前序数组的第一个字符,记录在中序数组中的位置index.如果找不到,说明前序遍历数组和中序遍历数组有问题,提示错误信息,退出程序即可;找到index后,新建一个二叉树节点t,t->item = *pre,然后递归的求t的左孩子和有孩子
  3. 递归的左孩子:void rebuildTree(pre + 1, in, index)
  4. 递归的右孩子:void rebuildTree(pre + (index + 1), in + (index + 1), len - (index + 1))

实现代码(c语言版)

/**
 * Description:根据前序和中序构建二叉树
 */
bt rebuildTree(char *pre, char *in, int len)
{
	bt t;
	if(len <= 0)
	{ 
		//递归终止
		t = NULL;
	}else
	{ 
		//递归主体
		int index = 0;
		
		while(index < len && *(pre) != *(in + index))
		{
			index ++;
		}
		
		if(index >= len)
		{
			printf("前序遍历或者中序遍历数组有问题!\n");
			exit(-1);
		}
		
		t = (struct bintree *)malloc(sizeof(struct bintree));
		t->item = *pre;
		t->lchild = rebuildTree(pre + 1, in, index);
		t->rchild = rebuildTree(pre + (index + 1), in + (index + 1), len - (index + 1));
	}
	return t;
}

根据中序序列、后序序列构建二叉树

函数定义

/**
 * 中序、后序序列构建二叉树
 */
btree* rebuildTree(char *order, char *post, int len);

算法思想

中序序列:C、B、E、D、F、A、H、G、J、I

后序序列:C、E、F、D、B、H、J、I、G、A

递归思路:

  1. 根据后序遍历的特点,知道后序遍历最后一个节点为根节点,即为A
  2. 观察中序遍历,A左侧CBEDF为A左子树节点,A后侧HGJI为A右子树节点
  3. 然后递归的构建A的左子树和后子树

实现代码(c代码)

/**
 * 根据中序和后序序列构建二叉树
 * (ps:昨晚参加阿里笔试,等到最后说可以免笔试直接面试,今天估计还是要根据学校筛选,哈哈,为了这点
 * 也得参加阿里笔试,不能让自己的学校受到鄙视)
 */

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

int n;

typedef struct btree {
	struct btree *lchild;
	struct btree *rchild;
	char data;
} btree;

/**
 * 中序、后序序列构建二叉树
 */
btree* rebuildTree(char *order, char *post, int len)
{
	btree *t;

	if (len <= 0) {
		return NULL;
	} else {
		int index = 0;

		while (index < len && *(post + len - 1) != *(order + index)) {
			index ++;
		}	

		t = (btree *)malloc(sizeof(btree));
		t->data = *(order + index);
		t->lchild = rebuildTree(order, post, index);
		t->rchild = rebuildTree(order + index + 1, post + index, len - (index + 1));
	}

	return t;
}

/**
 * 前序遍历二叉树
 */
void preTraverse(btree *t)
{
	if (t) {
		printf("%c ", t->data);
		preTraverse(t->lchild);
		preTraverse(t->rchild);
	}
}

int main(void)
{
	int i;
	char *post, *order;
	btree *t;

	while (scanf("%d", &n) != EOF) {
		post = (char *)malloc(n);
		order = (char *)malloc(n);
		
		getchar();
		for (i = 0; i < n; i ++)
			scanf("%c", order + i);

		getchar();
		for (i = 0; i < n; i ++)
			scanf("%c", post + i);

		t = rebuildTree(order, post, n);

		preTraverse(t);
		printf("\n");

		free(post);
		free(order);

	}

	return 0;
}


递归清理二叉树

复习了c语言的内存分配,参考链接: http://blog.csdn.net/zinss26914/article/details/8687859, 要点就是malloc分配的内存在堆上,使用完后应该由程序员手动释放,这里写一下递归清理二叉树的代码

/**
 * 清理二叉树
 */
void cleanBtree(btree *t)
{
	if (t) {
		cleanBtree(t->lchild);
		cleanBtree(t->rchild);
		free(t);
	}
}


后记

2012年今天是最后一天了,哈哈,终于把二叉树该掌握的部分都掌握了,还是不错的,期待新的一年2013年有更多的收获,2013年可能又是我人生发生抉择和变化的一年,我依然会坚持自己的价值观,踏踏实实的走下去,现在我学会最多的就是坚持,坚忍,光说不做是没有的,写程序如此,做人亦如此!

今天是2013年7月23日,重写了部分二叉树操作的代码,对指针的掌握和对数据结构的掌握更加熟练,希望校招一切顺利,自己加油!
  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值