二叉树基础篇

目录

二叉树的结构

二叉树的建立 

方法一

方法二

方法三

先序遍历二叉树

递归形式

非递归形式

中序遍历二叉树

递归形式

非递归形式

后序遍历二叉树

递归形式

非递归形式

打印二叉树某一层的所有结点

计算二叉树的所有结点个数

计算二叉树的深度

查找二叉树中值为value的节点

寻找某一节点的双亲节点


二叉树的结构

首先定义二叉树节点的结构

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stack>
#include<queue>
#define END '#'
using namespace std;
typedef char ElemType;

typedef struct BtNode
{
	struct BtNode* leftchild;//左孩子结点
	struct BtNode* rightchild;//右孩子结点
	ElemType data;//char类型
}BtNode,*BinaryTree;

图示二叉树节点的结构:

现有一个如下图所示的二叉树;

一个二叉树是由若干个结点组成的,因此要想建立一个二叉树,就要先创建一个个二叉树结点,我们首先给出二叉树结点创建和销毁的code。

BtNode* NewNode()//构造一个新的二叉树节点
{
	BtNode* s = (BtNode*)malloc(sizeof(BtNode));
	if (NULL == s)	exit(1);
	memset(s, 0, sizeof(BtNode));//此时左右孩子节点也都指向空了
	return s;
}

void FreeNode(BtNode* node)//释放一个二叉树节点
{
	free(node);
	node = nullptr;
}

二叉树的建立 

我们将使用如下三种方法建立一个二叉树。

方法一

利用递归

BtNode* CreateTree1()//第一种方法创建一个二叉树
{
	BtNode* p = NULL;
	ElemType item;
	scanf("%c", &item);
	if (item != END)
	{
		p = NewNode();
		p->data = item;
		p->leftchild = CreateTree1();
		p->rightchild = CreateTree1();
	}
	return p;
}

方法二

同利用递归,但使用这种方法建立二叉树时,需传入参数

BtNode* CreateTree2(const char*& str)//引用
{
	BtNode* p = NULL;
	if (NULL != str && *str != END)
	{
		p = NewNode();
		p->data = *str;
		p->leftchild = CreateTree2(++str);
		p->rightchild = CreateTree2(++str);
	}
	return p;
}

方法三

与方法二类似

BtNode* CreateTree3(const char** const pstr)
{
	BtNode* p = NULL;
	if (NULL != pstr && NULL != *pstr && **pstr != END)
	{
		p = NewNode();
		p->data = **pstr;
		p->leftchild = CreateTree3(&++ * pstr);
		p->rightchild = CreateTree3(&++ * pstr);
	}
	return p;
}

先序遍历二叉树

先序遍历也叫做先根遍历、前序遍历,可记做根左右(二叉树父结点向下先左后右)。

遍历规则是:首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树,如果二叉树为空则返回。

例如:

这颗二叉树的先序遍历结果是:ABCDEFGH。

遍历二叉树有多种方法,总的来说可以分为递归和非递归两种形式。

递归形式

void PreOrder(BtNode* p)/*先序递归遍历*/
{
	if (p != NULL)
	{
		printf("%c ", p->data);
		PreOrder(p->leftchild);//遍历左子树
		PreOrder(p->rightchild);//遍历右子树
	}
}

非递归形式

void NicePreOrder(BtNode* p)/*先序非递归遍历*/
{
	if (p == NULL) return;
	std::stack<BtNode*> st;
	st.push(p);
	while (!st.empty())
	{
		p = st.top();
		st.pop();
		std::cout << p->data << " ";
		if (p->rightchild != NULL)
		{
			st.push(p->rightchild);
		}
		if (p->leftchild != NULL)
		{
			st.push(p->leftchild);
		}
	}
	std::cout << std::endl;
}

在先序非递归遍历中,使用到了栈来存储二叉树的每一个结点。

  1. 如果栈不为空,则获取当前栈顶元素,并将栈顶元素pop,接着打印当前元素(即结点)的值。
  2. 然后再判断当前结点的右孩子结点是否为空,如果不为空,则将当前结点的右孩子结点入栈。接下来在判断当前结点的左孩子是否为空,如果不为空,则将当前结点的左孩子结点入栈。

这里之所以先判断右孩子结点然后再判断左孩子结点,是因为栈的特点是,先进后出。只有右孩子结点先入栈,左孩子结点后入栈 ,这样左孩子才会先被访问,访问栈的元素时才能和先序遍历的规则相符合,即先访问左结点再访问右结点。

中序遍历二叉树

中序遍历(LDR)也叫做中根遍历、中序周游。

遍历规则是中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。若二叉树为空则结束返回,否则:

(1)中序遍历左子树

(2)访问根结点

(3)中序遍历右子树

例如:

这颗二叉树的中序遍历结果是:CBEDFAGH

递归形式

void InOrder(BtNode* p)/*中序递归遍历*/
{
	if (p != NULL)
	{
		InOrder(p->leftchild);//遍历左子树
		printf("%c ", p->data);
		InOrder(p->rightchild);//遍历右子树
	}
}

非递归形式

void NiceInOrder(BtNode* p)/*中序非递归遍历*/
{
	if (p == NULL) return;
	std::stack<BtNode*> st;
	while (!st.empty() || p != NULL)
	{
		while (p != NULL)
		{
			st.push(p);
			p = p->leftchild;
		}
		p = st.top();
		st.pop();
		std::cout << p->data << " ";
		p = p->rightchild;
	}
	std::cout<<std::endl;
}

在中序非递归遍历中,使用到了栈来存储二叉树的每一个结点。

  1. 首先,如果栈不为空或二叉树不为空则进入主循环。
  2. 进入主循环后,如果是二叉树不为空,则进入子循环,将当前结点和当前结点的左孩子结点全部入栈。
  3. 子循环结束后,说明此时已到到左子树的最左结点,类似于图中的结点C
  4. 接着获取当前栈顶元素,并将栈顶元素pop,接着打印当前元素(即结点)的值。
  5. 最后,让当前结点变为它的右孩子结点,如果它的右孩子结点为空,则会打印它自身即根结点的值,若不为空则会继续遍历它的右子树。

后序遍历二叉树

后序遍历(LRD)是二叉树遍历的一种,也叫做后根遍历、后序周游,可记做左右根。 

遍历方法:中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。若二叉树为空则结束返回,否则:

(1)中序遍历左子树

(2)访问根结点

(3)中序遍历右子树

例如:

这颗二叉树的中序遍历结果是:CEFDBHGA

递归形式

void PastOrder(BtNode* p)/*后序递归遍历*/
{
	if (p != NULL)
	{
		PastOrder(p->leftchild);//遍历左子树
		PastOrder(p->rightchild);//遍历右子树
		printf("%c ", p->data);
	}
}

非递归形式

void NicePastOrder(BtNode* p)/*后序非递归遍历*/
{
	if (p == NULL) return;
	std::stack<BtNode*> st;
	BtNode* tag = NULL;
	while (!st.empty() || p != NULL)
	{
		while (p != NULL)
		{
			st.push(p);
			p = p->leftchild;
		}
		p = st.top();
		st.pop();
		if (p->rightchild == NULL || p->rightchild == tag)
		{
			std::cout << p->data << " ";
			tag = p;
			p = NULL;
		}
		else
		{
			st.push(p);
			p = p->rightchild;
		}
	}
		std::cout << std::endl;
}

在后序非递归遍历中,使用到了栈来存储二叉树的每一个结点。

打印二叉树某一层的所有结点

/*打印第k层所有节点的值*/
void PrintK(struct BtNode* ptr, int k)
{
	if (ptr != NULL && k == 0)/*当k=0时就到了树的第K层了*/
	{
		std::cout << ptr->data << " ";
	}
	else if (ptr != NULL)
	{
		PrintK(ptr->leftchild, k - 1);
		PrintK(ptr->rightchild, k - 1);
	}
}

以打印第三层为例,因为二叉树中根结点所在的那一次为第0层,所以结点CDH所存在的那一层为第2层,因此k为2。

具体的过程图如下: 

最终的打印结果:

计算二叉树的所有结点个数

/*树所有节点的个数*/
int GetSize(struct BtNode* ptr)
{
	if (ptr == NULL) return 0;
	else return GetSize(ptr->leftchild) + GetSize(ptr->rightchild) + 1;//这里的+1,相当于+ptr这个节点
}

       

最终的打印结果:

计算二叉树的深度

/*树的深度*/
int GetDepth(struct BtNode* ptr)
{
	if (ptr == NULL) return 0;
	else return std::max(GetDepth(ptr->leftchild), GetDepth(ptr->rightchild)) + 1;//这里的+1,相当于+ptr这个节点的这一层
}

                  

 

                

最终左子树(红色)的深度为4,右子树(绿色)的深度为3,取max,因此这颗二叉树的深度为4。

最终的打印结果:

查找二叉树中值为value的节点

/*查找二叉树中值为value的节点*/
struct BtNode* FindValue(struct BtNode* ptr, ElemType val)
{
	if (ptr == NULL || ptr->data == val)
	{
		return ptr;
	}
	else
	{
		struct BtNode* p = FindValue(ptr->leftchild, val);
		if (NULL == p)/*左边没有找到*/
		{
			p = FindValue(ptr->rightchild, val);
		}
		return p;
	}
}

实现该查找函数的主要方法还是递归,即不断的在左子树和右子树中查找。

寻找某一节点的双亲节点

struct BtNode* Parent(struct BtNode* ptr, struct BtNode* child)//ptr为二叉树的根结点(入口),child是要寻找双亲结点的孩子结点
{
	if (ptr == NULL || ptr->leftchild == child || ptr->rightchild == child)//找到了
	{
		return ptr;
	}
	else
	{
		struct BtNode* p = Parent(ptr->leftchild, child);//在左子树寻找
		if (NULL == p)
		{
			p = Parent(ptr->rightchild, child);//在右子树寻找
		}
		return p;
	}
}
struct BtNode* FindParent(struct BtNode* ptr, struct BtNode* child)
{
	if (NULL == ptr || NULL == child || ptr == child)
	{
		return NULL;
	}
	else
	{
		return Parent(ptr, child);
	}
}

实现该查找函数的主要方法还是递归,即不断的在左子树和右子树中查找某一结点的双亲结点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值