二叉树的基础

二叉树

:一种非线性结构,它由n(n>=0)个有限结点组成一个具有层次关系的集合。
树的特点:每个节点具有零个或多个子节点,没有父结点的结点称为根结点,如下图树有一个根但有多个树枝树叶;每一个非根结点只有一个父节点,除了根结点之外每个子结点可以分为多个不相交的子树。
:树的子树是不相交的;
除了根结点外,每个结点有且只有一个父结点;
一颗N个结点的树有N-1条边。

现实生活中的树:
在这里插入图片描述
图形化了的树形结构:

在这里插入图片描述

对比:
在这里插入图片描述

数的表示方法:
1、孩子表示法:
顾名思义,在一个节点内部只保存它的值域以及它的孩子节点的位置:

typedef int DataType;
typedef struct TreeNode{
	DataType data;    //该节点的值域
	struct Tree child1;   //第一个孩子节点的位置
	struct Tree child2;  //第二个孩子节点的位置
	struct Tree child3;
	.......
	struct Tree childn;   //最后一个孩子节点的位置
}TreeNode;

由此可见,假如一个节点它有 N 个孩子节点,则它的节点结构中应该具有 N+1 个字段

缺点:
(1)可能会造成空间浪费,按照最大孩子节点个数来创建的 TreeNode 结构,若有节点的孩子节点小于 N,则会造成空间浪费;
(2)无法找到每个节点的双亲结点,该节点结构中只保存了每一个节点的孩子节点的位置,并不能依此找到它的双亲位置。

2、双亲表示法:
只保存节点的值域以及其双亲节点的位置:

typedef int DataType;
typedef struct TreeNode{
	DataType data;   //节点值域
	struct TreeNode * parent;   //双亲节点位置
}TreeNode;

该节点结构内只包含两个字段:值域 、双亲,相比第一种存储方式来说会极大的节省空间,提升空间的利用率

缺点:
只保存了该节点双亲结点的位置,则无法找到它的孩子节点的位置

3、孩子双亲表示法:

孩子双亲表示法===孩子表示法 + 双亲表示法,前两种方式结合起来,既能够找到孩子节点位置,也能够找到双亲结点的位置。

4、孩子兄弟表示法:
存储一个节点的第一个孩子节点位置以及它的兄弟节点的位置:

typedef int DataType;
typedef struct TreeNode{
	struct TreeNode* firstchild1;   //第一个孩子节点
	struct TreeNode* NodeBrother;   //兄弟节点的位置
	DataTypde data;   //节点值域
}TreeNode;

例如:一棵树结构如下,可以画出它所对应的存储结构:

在这里插入图片描述

针对上图中的树,采用孩子兄弟法进行存储的结构为:

在这里插入图片描述

由此可见,采用孩子兄弟法来进行存储会将树中每个节点进行存储。

二叉树

二叉树:是指子结点小于等于 2 (个)的树

二叉树的特点
二叉树的每个结点最多有两颗子树,即不存在度大于2 的结点;
二叉树有左右子树之分,且其子树顺序不能颠倒
图例:
在这里插入图片描述
对比:
在这里插入图片描述
二叉树的基本遍历方式
在这里插入图片描述
按照访问根结点的顺序分为为三种遍历方式:

(1)先序遍历:

先访问根结点------->访问左子树(依旧按照“根左右”顺序遍历)---------->访问右子树(依旧按照“根左右”顺序遍历)
遍历结果为:ABDEHCFG

(2)中序遍历
先访问左子树(依旧按照“左根右”顺序遍历)------->访问根结点---------->访问右子树(依旧按照“左根右”顺序遍历)
遍历结果为:DBEHAFCG

(3)后序遍历
访问左子树(依旧按照“左右根”顺序遍历)---------->访问右子树(依旧按照“左右根”顺序遍历)----------->访问根结点
遍历结果为:DHEBFGCA

二叉树的基本操作

定义一颗二叉树

定义一颗简单的二叉树(如下图),首先需要定义一颗二叉树的根结点,其次是该节点的左孩子结点、右孩子结点
在这里插入图片描述
1、先定义一颗二叉树的结点结构:
包含当前结点、当前结点的左孩子树、当前结点的右孩子树

typedef char DataType;    //结点的数据类型定义为字符型 char
typedef struct BNode {
	BNode* _left;                   //左孩子结点
	BNode* _right;                //右孩子结点
	DataType _data;            //结点中数据
}Node;

2、定义一颗二叉树结构:

typedef struct BTree{ 
	Node* _root;        //二叉树的根节点
}BTree;

二叉树的创建

创建一个二叉树结构,除过根结点以外每个结点都有可能包含0~2个孩子节点,给出示例二叉树结构:在这里插入图片描述
按照先序遍历来创建二叉树

//先序遍历:ABD##E#H##CF##G##
//递归创建
Node* creatBinaryTree(DataType arr[],int* idx)       	//返回二叉树的根节点指针
{
	if (arr[*idx] == '#') {                // # 表示空 NULL
		(*idx)++;
		return NULL;      //空树
	}
	Node* root = (Node*)malloc(sizeof(Node));        //根结点动态开辟空间
	root->_data = arr[*idx];                         //将给定数据存放到二叉树对应结点中
	(*idx)++;
	root->_left = creatBinaryTree(arr, idx);   //按照先序遍历方式创建当前 root 结点的左子树
	root->_right = creatBinaryTree(arr, idx);  //按照先序遍历方式创建当前 root 结点的右子树
	return root;                            //将二叉树的根结点返回
}

先序遍历(递归)

void preOrder(Node* root)     //先序遍历: 根左右
{
	if (root) {                  //循环条件:当前树不为空
		printf("%c ", root->_data);           //首先打印根结点数据
    //再按照先序遍历方式遍历左子树中结点 ,最后按照先序遍历遍历右子树各结点
		preOrder(root->_left);
		preOrder(root->_right);
	}
}

中序遍历(递归)

void inOrder(Node* root)   //中序遍历: 左根右
{
	if (root) {
		inOrder(root->_left);            //中序遍历当前结点的左子树
		printf("%c ", root->_data);       //打印当前根结点的内容
		inOrder(root->_right);                 //中序遍历当前结点的右子树
	}
}

后序遍历(递归)

void postOrder(Node* root)       //后序遍历:左右根
{
	if (root) {
		postOrder(root->_left);                  //后序遍历当前结点的左子树
		postOrder(root->_right);                   //后序遍历当前节点的右子树
		printf("%c ", root->_data);               //打印当前根结点内容
	}
}

二叉树的三种非递归形式遍历

非递归形式需要借助栈结构来实现不同结点的访问顺序(当前假设已经存在栈结构 stack 以及入栈出栈、取栈顶元素等基本操作),之间调用栈结构 stack

先序遍历(非递归)

在这里插入图片描述
思想:
先遍历根结点 A ,然后遍历 A 结点的左子树(按照先序遍历方式),即一直访问最左边结点,访问完之后从 D 结点往上开始访问 A 结点左子树部分的 D 结点的右子树(此时D 结点不存在左右子树,故往上访问 B 结点的右子树部分),因此需要利用栈结构来保存最左边各个结点,再利用栈结构出栈依次从下往上访问各个结点的右子树。

int* preOrderTraversal(Node* root, int* returnSize)
{  //非递归先序遍历
	stack st;                          //定义一个 st 栈结构
	stack_init(&st);              //栈的初始化操作
	int sz = getSize(root);         //获取当前二叉树的结点个数
	int idx = 0;
	int* arr = (int*)malloc(sizeof(int)*sz);   
	                 //定义数组用来保存出栈元素(确定遍历顺序)
	while (root || !stack_empty(&st)) {
		//当前遍历结点不空 或 栈不空
		//访问最左路径
		while (root) {
			arr[idx++] = root->_data;                       //先序遍历:根左右
			stack_push(&st, root);
			root = root->_left;
		}
		//获取栈顶元素,访问右子树
		root = stack_top(&st);
		stack_pop(&st);
		root = root->_right;
	}
	*returnSize = idx;
	return arr;          //数组用来保存先序遍历的结果
}

中序遍历(非递归)

中序遍历思想与先序遍历类似,只是在遍历每个结点时正常入栈,只是访问时候先访问完其左子树之后再访问其根结点(仅更改打印顺序-----出栈之后保存到数组 arr)

int* inOrderTraversal(Node* root, int* returnSize)
{ //非递归中序遍历
	stack st;
	stack_init(&st);
	int sz = getSize(root);
	int idx = 0;
	int* arr = (int*)malloc(sizeof(int)*sz);
	while (root || !stack_empty(&st)) {
		while (root) {         //左节点全部入栈
			stack_push(&st, root);
			root = root->_left;
		}
		//获取栈顶元素
		root = stack_top(&st);
		arr[idx++] = root->_data;    //保存栈顶元素
		stack_pop(&st);
		root = root->_right;
	}
	*returnSize = idx;
	return arr;
}

后序遍历(非递归)

后序遍历需要先遍历某一结点的左子树----->右子树-------->该根结点,因此从上往下遍历时有的结点需要遍历不止一遍,但是访问顺序需要再进一步判断。
即,该结点没有右子树则访问完左子树之后之间访问其根结点;
若该结点存在右子树,再进一步判断:若右子树访问完则可以访问根结点;若右子树未访问则先访问该右子树之后再访问其根结点。
在这里插入图片描述
例如此图遍历时,先遍历 A 的左子树,需一直遍历左子树的左子树直到找到叶结点 D ,在开始回退,访问 D 结点的右子树(NULL),再访问 B 的右子树,此时 B 结点第二次被遍历到但是不打印(即不实际访问 B),等到 B 结点的右子树全部遍历完成之后才能访问 B 结点------即存放入 arr 数组中。

int* postorderTraversal(Node* root, int* returnSize)
{
	//非递归后序遍历!!!!!!!!!!!!!!!!!!!!!!!!!!
	stack st;
	stack_init(&st);
	int sz = getSize(root);
	int idx = 0;
	int* arr = (int*)malloc(sizeof(int)*sz);
	Node* pre = NULL;     //记录上一步访问到的的结点
	while (root && !stack_empty(&st)) {
		while (root) {
			stack_push(&st, root);
			root = root->_left;
		}
		Node* top = stack_top(&st);  //栈顶
		//判断栈顶元素是否可以访问? 没有右子树/右子树已经被访问完成
		if (top->_right == NULL || top->_right == pre) {
			arr[idx++] = top->_data;
			stack_pop(&st);
			pre = top;
		}
		else {
			root = top->_right;   //右子树未访问
		}
	}
	*returnSize = idx;
	return arr;
}

调试

void test()
{
	char arr[] = "ABD##E#H##CF##G##";
	int idx = 0;
	Node* root = creatBinaryTree(arr, &idx);

	preOrder(root);            //先序遍历
	printf("\n");

	inOrder(root);              //中序遍历
	printf("\n");
	 
	postOrder(root);              //后序遍历
	printf("\n");
}

在这里插入图片描述
调试结果与上述分析所得遍历结果一致,因此验证遍历正确
本文主要说明二叉树的概念、二叉树的遍历方式,三种递归/非递归方式的遍历应掌握,读者可自行画图进一步分析。

(博客内容为原创,有任何问题都可评论私聊呀!)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值