基于C语言的数据结构_二叉树 学习笔记

目录

07 树与二叉树

07.1 创建二叉树(问询法)

07.2 遍历二叉树(先序 中序 后序)

07.3 层次遍历二叉树

07.4 统计二叉树深度

07.5 统计二叉树叶子数

07.6 统计二叉树节点数

07.7 补空法创建二叉树

07.8 线索二叉树

07.9 遍历线索二叉树

07.10 操作线索二叉树


前面我们学习了 一对一的线性关系——栈 队列 链表

本次来学习一对多的非线性关系——树

至于这些数据结构的特点 优点 缺点 一些概念名词什么的 初学者不用急着看书上的总结  我们先从实际出发 谈逻辑 看代码 学会了 自然融会贯通,一些概念的自然一点就明白了。

07 树与二叉树

07.1 创建二叉树(问询法)

看图  我们称这样的数据结构为树结构 根树A下有两个子树。 分别为左子树B 右子树C ,其中左子树B下又有左右子树DE    右子树C下又右子树F.....依此类推  就像套娃一样  。这里就是函数嵌套的思想。

那么如何用这种结构存储数据呢? 可以分析出根树与所有的子树 数据结构应该的一样的!除了要存储数据外 还要两个指针来指向左右子树的地址,类似链表的思想。如此我们使用结构体数据类型 来存储这三个成员。

代码实现

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

struct BinTree_node
{
	unsigned char elem;
	struct BinTree_node *ltree, *rtree;
};

struct BinTree_node *create_bintree(void);

int main(void)
{
	struct BinTree_node *mytree;

	mytree = create_bintree();


	return 0;
}

struct BinTree_node *create_bintree(void)
{
	unsigned char flag;
	struct BinTree_node *tree;

	tree = (struct BinTree_node *)malloc(sizeof(struct BinTree_node));
	printf("Please input the node elem:\n");
	while((tree->elem = getchar()) == '\n');
	printf("Do you want to insert l_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->ltree = create_bintree();
	else
		tree->ltree = NULL;

	printf("Do you want to insert r_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->rtree = create_bintree();
	else
		tree->rtree = NULL;

	return tree;
}

结合上述代码 创建一个你的二叉树结构 下节我们讲一下如何遍历出你的二叉树。

07.2 遍历二叉树(先序 中序 后序)

二叉树的遍历用到了函数递归的思想,这种递归的思想可以将一些繁琐的操作步骤给简化 就比如说汉诺塔的代码实现。

递归就是函数自己调用自己,处理完成后返回函数调用的位置继续执行,建议用堆栈图的方式来学习函数的调用 学到本质。

1).先序遍历
        先访问根节点,然后先序遍历左子树,再先序遍历右子树

void pre_order(struct BinTree_node *tree)
{
	if(tree)
	{
		printf("%c", tree->elem);
		pre_order(tree->ltree);
		pre_order(tree->rtree);
	}
}

å¨è¿éæå¥å¾çæè¿°

2).中序遍历
        先中序遍历左子树,再访问根节点,再中序遍历右子树

void in_order(struct BinTree_node *tree)
{
	if(tree)
	{
		in_order(tree->ltree);
		printf("%c", tree->elem);
		in_order(tree->rtree);
	}
}

å¨è¿éæå¥å¾çæè¿°

 3).后续遍历
        先后序遍历左子树,再后序遍历右子树,最后访问根节点

void pos_order(struct BinTree_node *tree)
{
	if(tree)
	{
		pos_order(tree->ltree);
		pos_order(tree->rtree);
		printf("%c", tree->elem);
	}
}

å¨è¿éæå¥å¾çæè¿°

源代码参考

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

struct BinTree_node
{
	unsigned char elem;
	struct BinTree_node *ltree, *rtree;
};

void pre_order(struct BinTree_node *tree);
void in_order(struct BinTree_node *tree);
void pos_order(struct BinTree_node *tree);

struct BinTree_node *create_bintree(void);

int main(void)
{
	struct BinTree_node *mytree;

	mytree = create_bintree();

//	pre_order(mytree);
//	in_order(mytree);
	pos_order(mytree);

	printf("\n");
	return 0;
}

struct BinTree_node *create_bintree(void)
{
	unsigned char flag;
	struct BinTree_node *tree;

	tree = (struct BinTree_node *)malloc(sizeof(struct BinTree_node));
	printf("Please input the node elem:\n");
	while((tree->elem = getchar()) == '\n');
	printf("Do you want to insert l_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->ltree = create_bintree();
	else
		tree->ltree = NULL;

	printf("Do you want to insert r_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->rtree = create_bintree();
	else
		tree->rtree = NULL;

	return tree;
}

void pre_order(struct BinTree_node *tree)
{
	if(tree)
	{
		printf("%c", tree->elem);
		pre_order(tree->ltree);
		pre_order(tree->rtree);
	}
}

void in_order(struct BinTree_node *tree)
{
	if(tree)
	{
		in_order(tree->ltree);
		printf("%c", tree->elem);
		in_order(tree->rtree);
	}
}

void pos_order(struct BinTree_node *tree)
{
	if(tree)
	{
		pos_order(tree->ltree);
		pos_order(tree->rtree);
		printf("%c", tree->elem);
	}
}

07.3 层次遍历二叉树

        无论是先序遍历还是中序后序遍历法 ,它们都有共同的一个特点 那就是使用了递归算法。这种函数调用其实是非常消耗资源的!一个函数的创建不光要保存当前栈区域的现场信息,还要为新栈开辟缓冲区,退出时还要还原现场,保持堆栈平衡。 这里可以参考的汇编语言。所以现实编程中我们能使用循环就是不使用递归,那么有没有使用循环的遍历二叉树的算法? 这里介绍一种名为层次遍历的算法。


层次遍历算法思想
        利用队列先入先出的特性; 先将根树入队列,随后在循环中 “先出一个队列打印它的树元素值 若当前树有左右子树 则依次入队列.......”往复循环 直到队列全部出完 此时队列为空。

void level_traverse(struct BinTree_node *tree)
{
	struct BinTree_node node;

	enqueue(*tree);

	while(!is_empty())
	{
		node = dequeue();
		printf("%c", node.elem);
		if(node.ltree)
			enqueue(*node.ltree);
		if(node.rtree)
			enqueue(*node.rtree);
	}
}

 原代码参考

main.c

#include "binary_tree.h"

int main(void)
{
	struct BinTree_node *mytree;

	mytree = create_bintree();

	level_traverse(mytree);

	printf("\n");
	return 0;
}

binary_tree.h

#ifndef __BINARY_TREE_H__
#define __BINARY_TREE_H__

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

struct BinTree_node
{
	unsigned char elem;
	struct BinTree_node *ltree, *rtree;
};

#include "queue.h"

struct BinTree_node *create_bintree(void);

void level_traverse(struct BinTree_node *tree);

#endif

binary_tree.c

#include "binary_tree.h"

struct BinTree_node *create_bintree(void)
{
	unsigned char flag;
	struct BinTree_node *tree;

	tree = (struct BinTree_node *)malloc(sizeof(struct BinTree_node));
	printf("Please input the node elem:\n");
	while((tree->elem = getchar()) == '\n');
	printf("Do you want to insert l_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->ltree = create_bintree();
	else
		tree->ltree = NULL;

	printf("Do you want to insert r_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->rtree = create_bintree();
	else
		tree->rtree = NULL;

	return tree;
}



void level_traverse(struct BinTree_node *tree)
{
	struct BinTree_node node;

	enqueue(*tree);

	while(!is_empty())
	{
		node = dequeue();
		printf("%c", node.elem);
		if(node.ltree)
			enqueue(*node.ltree);
		if(node.rtree)
			enqueue(*node.rtree);
	}
}

queue.h

#ifndef __QUEUE_H__
#define __QUEUE_H__

#include <stdio.h>
#include "binary_tree.h"

#define SIZE	512

void enqueue(struct BinTree_node c);
struct BinTree_node dequeue(void);
int is_empty(void);
int is_full(void);

#endif

queue.c

#include "queue.h"

struct BinTree_node queue[SIZE];

int head = 0, tail = 0;

void enqueue(struct BinTree_node c)
{
	queue[tail] = c;
	tail = (tail + 1) % SIZE;
}

struct BinTree_node dequeue(void)
{
	struct BinTree_node ch;
	ch = queue[head];
	head = (head + 1) % SIZE;

	return ch;
}

int is_empty(void)
{
	return head == tail;
}

int is_full(void)
{
	return (tail + 1) % SIZE == head;
}

07.4 统计二叉树深度

二叉树深度是指根树离最远的子树距离+1,在只有根数的情况下与最远的子树为0 其深度为1.当二叉数为空 其深度为零;常使用递归思想获取二叉树深度。

算法思想
        树为空的时候返回0; 否者继续递归调用赋值给两个临时变量, 直到树为空,比较两个临时变量 返回最大的+1;
        相当于一直遍历到最远的子树节点; 每次递归结束变量+1 可以理解成 统计递归的次数=深度;注意:递归包含第一次调用函数

unsigned int depth(struct BinTree_node *tree)
{
	unsigned int l_depth;
	unsigned int r_depth;

	if(tree == NULL)
		return 0;
	else
	{
		l_depth = depth(tree->ltree);
		r_depth = depth(tree->rtree);

		return (l_depth > r_depth) ? (l_depth + 1) : (r_depth + 1);
	}
}

07.5 统计二叉树叶子数

什么是叶子
        不再有子树的树可以称为叶子

递归的思想统计叶子分三种情况
        1) 二叉树为空 ,叶子树为0
        2) 二叉树只有根节点 无左右子树 ,叶子树为1
        3) 一般情况下 叶子树=左子树叶子树+右子树叶子树

代码参考

unsigned int leaf_num(struct BinTree_node *tree)
{
	if(tree == NULL)
		return 0;
	else if((tree->ltree == NULL) && (tree->rtree == NULL))
		return 1;
	else
		return leaf_num(tree->ltree) + leaf_num(tree->rtree);
}

07.6 统计二叉树节点数

什么是节点?

递归的思想统计节点分二种情况
        1) 二叉树为空 节点数为0
        2) 左子树节点数+右子树节点数+1

代码参考

unsigned int node_num(struct BinTree_node *tree)
{
	if(tree == NULL)
		return 0;
	else
		return node_num(tree->ltree) + node_num(tree->rtree) + 1;
}

07.7 补空法创建二叉树

问询法:麻烦; 需要用户反复输入;易出错。

那么能不能将一串带有节点信息的字符串一次性转化为 指定的二叉树?
这串信息应该怎样写才能代表这个二叉树的信息?回想以下二叉树的遍历输出顺序能不能利用一下?

补空法
       1. 将二叉树补空(特点:原二叉树的叶子不再是叶子由#补充为新叶子),写出该二叉树的先序序列。
        2. 转换先序序列
                1) ch == '#' 节点为空子树
                2) ch != '#' 创建新节点;左右子树再次调用(还是递归思想)

struct BinTree_node *Create_BinTree(void) //fill_blank_method
{
	char ch;
	struct BinTree_node *tree;

	scanf("%c", &ch);

	if(ch == '#')
		tree = NULL;
	else
	{
		tree = (struct BinTree_node *)malloc(sizeof(struct BinTree_node));
		tree->elem = ch;
		tree->ltree = Create_BinTree();
		tree->rtree = Create_BinTree();
	}

	return tree;
}

07.8 线索二叉树

         上述的二叉树都是一种非线性的数据结构,我们可以通过指针找到该树的左右子树, 但无法得知这个树从哪里来!前驱点是谁!而且并不是所有的树都有左右子树 这就导致的了有些树的指针指向为空 没有被利用上,平且我们前面说过递归调用是非常消耗资源的!有没有循环算法替代像中序遍历这种算法?
        也许你会想到在添加的一个指针 用来指向父节点,是个不错的方法 也送你一个名词三叉链表。但这种增加指针的方法无疑是增加了资源开销和空间浪费,实际上 我们不一定要该节点的前驱点一定是该节点的父节点。这里我也犯了 文字概念的上错误 既然提到了前驱后继 那这个数据结构一定是一个线性化的数据结构。对于线性化的数据结构是非常容易用循环来遍历的。
        

        解决上述问题最好的方式将非线性的二叉树线性化 线性化的目的就是快速地找到前驱后继节点,实现循环遍历。那么谁来做前驱后继点?这里我们选用中序遍历的序列,实现较为简单。
        我们需要改造下最初的树的结构体,创建标志位来表示左右指针指向的是子树还是前驱后继节点。

我们规定 如果左指针为空(没有被利用),那么左边的flag置为1 ,该指针指向前驱节点,如果右指针为空(没有被利用),那么右边的flag置为1 该指针指向后继节点。

 代码实现

void In_order_Thread(struct BinTree_node *tree)
{
	if(tree)
	{
		//1.Do_Inorder_Thread to ltree
		In_order_Thread(tree->ltree);

		//2.Deal with current node, 
		if(!tree->ltree)
		{
			tree->lflag = 1;
			//current node's ltree points to the pre node
			tree->ltree = pre;
		}
		else
			tree->lflag = 0;

		if(pre)
		{
			if(!pre->rtree)
			{
				pre->rflag = 1;
				pre->rtree = tree;
			}
			else
				pre->rflag = 0;
		}
		pre = tree;

		//3.Do_Inorder_Thread to rtree
		In_order_Thread(tree->rtree);
	}
}

void Create_Inorder_Thread(struct BinTree_node *T)
{
	if(T)
	{
		In_order_Thread(T);
		pre->rtree = NULL;
		pre->rflag = 1;
	}
}

07.9 遍历线索二叉树

遍历不过这次是使用中序序列的循环遍历

算法思想就是先遍历到最左边的树打印出值 如果右flag存在 就跟过去再打印节点值 再有就再一次跟过去, 因为设计的右flag指针就是这个序列的后继节点,如果没有右flag 那就将右子树更新为当前再遍历到最左边的树.....(循环思想)。

void Traverse_Inorder_Thread(struct BinTree_node *tree)
{
	while(tree)
	{
		while(tree->lflag == 0)
			tree = tree->ltree;
		printf("%c ", tree->elem);

		while((tree->rflag == 1) && (tree->rtree))
		{
			tree = tree->rtree;
			printf("%c ", tree->elem);
		}
		tree = tree->rtree;
	} 
}

07.10 操作线索二叉树

1).搜索某节点 跟遍历的代码差不多我们只要添加判断 将值返回即可。

struct BinTree_node *Search_Inorder_Thread(struct BinTree_node *tree, char ch)
{
	while(tree)
	{
		while(tree->lflag == 0)
			tree = tree->ltree;
		if(tree->elem == ch)
			return tree;

		while((tree->rflag == 1) && (tree->rtree))
		{
			tree = tree->rtree;
			if(tree->elem == ch)
				return tree;
		}
		tree = tree->rtree;
	} 	
}

2).找到该节点的前驱点,根据设计如果该节点的lflag存在 那么指向的节点就是前驱点,如果不存在就需要找到左子树最右边的点

struct BinTree_node *Prenode_Inorder_Thread(const struct BinTree_node *node)
{
	struct BinTree_node *nd;

	if(node->lflag == 1)
		return node->ltree;
	else
	{
		nd = node->ltree;
		while(nd->rflag == 0)
			nd = nd->rtree;
		return nd;
	}
}

3).找到该节点的后继点,思想跟找前驱点一样 不过如果rflag不存在 后继点为右子树的最左边的点。

struct BinTree_node *Succnode_Inorder_Thread(const struct BinTree_node *node)
{
	struct BinTree_node *nd;

	if(node->rflag == 1)
		return node->rtree;
	else
	{
		nd = node->rtree;
		while(nd->lflag == 0)
			nd = nd->ltree;
		return nd;
	}
}

还需要注意节点的前驱点和后继点存在null的现象

猜你喜欢

转载自blog.csdn.net/shelter1234567/article/details/129967368