目录
二叉树的结构
首先定义二叉树节点的结构
#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;
}
在先序非递归遍历中,使用到了栈来存储二叉树的每一个结点。
- 如果栈不为空,则获取当前栈顶元素,并将栈顶元素pop,接着打印当前元素(即结点)的值。
- 然后再判断当前结点的右孩子结点是否为空,如果不为空,则将当前结点的右孩子结点入栈。接下来在判断当前结点的左孩子是否为空,如果不为空,则将当前结点的左孩子结点入栈。
这里之所以先判断右孩子结点然后再判断左孩子结点,是因为栈的特点是,先进后出。只有右孩子结点先入栈,左孩子结点后入栈 ,这样左孩子才会先被访问,访问栈的元素时才能和先序遍历的规则相符合,即先访问左结点再访问右结点。
中序遍历二叉树
中序遍历(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;
}
在中序非递归遍历中,使用到了栈来存储二叉树的每一个结点。
- 首先,如果栈不为空或二叉树不为空则进入主循环。
- 进入主循环后,如果是二叉树不为空,则进入子循环,将当前结点和当前结点的左孩子结点全部入栈。
- 子循环结束后,说明此时已到到左子树的最左结点,类似于图中的结点C
- 接着获取当前栈顶元素,并将栈顶元素pop,接着打印当前元素(即结点)的值。
- 最后,让当前结点变为它的右孩子结点,如果它的右孩子结点为空,则会打印它自身即根结点的值,若不为空则会继续遍历它的右子树。
后序遍历二叉树
后序遍历(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);
}
}
实现该查找函数的主要方法还是递归,即不断的在左子树和右子树中查找某一结点的双亲结点。