1. 树的表达方式
集合中的元素关系呈现出一对多的情况
1.1 树的定义
- 树(Tree)是n(n≥0)个节点的有限集合T,它满足两个条件 :
- 有且仅有一个特定的称为根(Root)的节点
- 其余的节点可以分为m(m≥0)个互不相交的有限集合T1、T2、……、Tm,其中每一个集合又是一棵树, 并称为其根的子树(Subtree)。
- 树的定义具有递归性,即“树中还有树”。
1.2 树的概念
- 结点:使⽤树结构存储的每一个数据元素都被称为“结点”。例如图中的A就是一个结点。
- 根结点:有一个特殊的结点,这个结点没有前驱,我们将这种结点称之为根结点。
- ⽗结点(双亲结点)、子结点和兄弟结点:对于ABCD四个结点来说,A就是BCD的⽗结点,也称之为双亲结点。 ⽽BCD都是A的子结点,也称之为孩子结点。对于BCD来说,因为他们都有同一个爹,所以它们互相称之为兄弟结点。
- 叶子结点:如果一个结点没有任何子结点,那么此结点就称之为叶子结点。
- 结点的度:结点拥有的子树的个数,就称之为结点的度。
- 树的度:在各个结点当中,度的最⼤值。为树的度。
- 树的深度或者⾼度:结点的层次从根结点开始定义起,根为第一层,根的孩子为第二层。依次类推。
- 结点A的度:3 结点B的度:2 结点M的度:0
- 结点A的孩子:B C D 结点B的孩子:E F
- 树的度:3 树的深度:4
- 叶子结点:K L F G M I J
- 结点A是结点F的祖先
- 结点F是结点K的叔叔结点
1.3 树的存储结构
1.3.1 双亲表示法
双亲表示法采⽤顺序表(也就是数组)存储普通树,其实现的核心思想是:顺序存储各个节点的同时,给各节点附加一个记录其⽗节点位置的变量。
根节点没有⽗节点(⽗节点又称为双亲节点),因此根节点记录⽗节点位置的变量通常置为 -1。
- 利⽤顺序表存储,表元素由数据和⽗结点构成
- 特点分析:
- 根结点没有双亲,所以位置域设置为-1
- 知道一个结点,找他的⽗结点,非常容易,O(1)级
- 找孩子节点,必须遍历整个表
1.3.2 孩子表示法
孩子表示法存储普通树采⽤的是 "顺序表+链表" 的组合结构。
其存储过程是:从树的根节点开始,使⽤顺序表依次存储树中各个节点。需要注意,与双亲表示法不同的是,孩子表示法会给各个节点配备一个链表,⽤于存储各节点的孩子节点位于顺序表中的位置。
如果节点没有孩子节点(叶子节点),则该节点的链表为空链表。
使⽤孩子表示法存储的树结构,正好和双亲表示法相反,查找孩子结点的效率很⾼,⽽不擅长做查找⽗结点的操作。
我们还可以将双亲表示法和孩子表示法合二为一
1.3.3 孩子兄弟表示法
在树结构中,同一层的节点互为兄弟节点。例如普通树中,节点 A、B 和 C 互为兄弟节点,⽽节点 D、E 和 F 也 互为兄弟节点。
所谓孩子兄弟表示法,指的是⽤将整棵树⽤二叉链表存储起来,具体实现方案是:从树的根节点开始,依次存储各 个结点的孩子结点和兄弟结点。
在二叉链表中,各个结点包含三部分内容:
在以孩子兄弟表示法构建的二叉链表中,如果要查找结点 x 的所有孩子,则只要根据该结点的 firstchild 指针找到 它的第一个孩子,然后沿着孩子结点的 nextsibling 指针不断地找它的兄弟结点,就可以找到结点 x 的所有孩子。
2.二叉树简介
2.1二叉树定义
- 二叉树是每个结点最多有两个子树的树结构。二叉树不允许存在度⼤于2的树。
- 它有五种最基本的形态:
- 二叉树可以是空集
- 根可以有空的左子树或者右子树;
- 左右子树都是空。只有左子树或者右子树的叫做斜树。
2.2二叉树的概念和性质
2.2.1完全二叉树和满二叉树
- 满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都 在同一层上,这样的二叉树称为满二叉树。
一棵深度为k且有2 -1个结点的二叉树称为满二叉树。( k ≥ 1)
- 完全二叉树
如果一棵深度为k,有n个结点的二叉树中各结点能够与深度为k的顺序编号的满二叉树从1到n标号的结点相对应的 二叉树称为完全二叉树。
特点:
- 所有的叶结点都出现在第k层或k-1层
- 若任一结点,如果其右子树的最⼤层次为i,则其左子树的最⼤层次为i或i+1
2.2.2二叉树的性质
- 性质1
在二叉树的第i层上的结点最多为2 ^(i-1)个。(i ≥ 1)
- 性质2
深度为k的二叉树至多有2^k -1个结点。(i ≥ 1)
- 性质3
在一棵二叉树中,叶结点的数目比度为2的结点数目多一个。
总节点数为各类节点之和:n = n0 + n1 + n2
总节点数为所有子节点数加一:n = n 1+ 2*n2 + 1
故:n0 = n2 + 1
- 性质4
具有N个结点的完全二叉树的深度为log N+1。(向下取整)
- 性质5
如果有一棵n个结点的完全二叉树,其结点编号按照层次序(从上到下,从左到右),则除根结点外,满足[i/2 , i, 2i, 2i+1]的规则
2.3二叉树的存储
2.3.1顺序存储
- 依靠性质5,可以将任意棵二叉树构造成满二叉树结构或完全二叉树结构,依据下标规则,就可以找到⽗结点,子结点
核心算法:
拥有天然的下标索引0 1 2 3 4 5规定下标索引的运算规则
1.[i/2,i,2i,2i+1]:父节点,本身节点、子节点、子节点
2.3.2链式存储
- 由于二叉树的每个结点最多只能有两个子树,每个结点定义两个指针域和一个数据域即可。
2.4二叉树存储的核心代码
//树的节点结构 typedef class treeNode{ public: Element data; //保存的元素 treeNode *left; //左子节点 treeNode *right; //右子节点 }TreeNode;//二叉树描述信息的结构 typedef class { public: TreeNode* root; //二叉树的根节点 int count; //二叉树的节点数量 }BinaryTree;
3二叉树的遍历
3.1遍历思想
- 遍历 :沿某条搜索路径周游二叉树,对树中的每一个节点访问一次且仅访问一次。
- 对线性结构⽽言,只有一条搜索路径(因为每个结点均只有一个后继),故不需要另加讨论。
- 二叉树是非线性结构,每个结点有两个后继,则存在如何遍历,即按什么样的搜索路径进行遍历的问题。
- 按层次,⽗子关系,知道了⽗,那么就把其所有的子结点都看一遍
- 按深度,一条道走到黑,然后再返回走另一条道
3.2广度遍历(层次遍历)
每到一层,面临多个任务,不能同时处理,于是引入队列。
- 算法思想:
- 引入队列,将根结点入队
- 从队列中取出队头元素,访问该结点,将该结点的所有孩子节点入队
- 再次从队列中取出队头元素,并访问,以此重复
- 直到队列为空,说明所有元素都遍历完成
- 算法实现
void levelOrderBTree(BinaryTree *tree){ ArrayQueue *queue=new ArrayQueue ; //申请一个队列 pTreeNode node; //用来存放取出的节点 enArrayQueue(queue,tree->root); //根节点入队 while(deArrayQueue(queue,&node)!=-1){ //取出节点 //访问节点 visitTreeNode(node); //入队左右节点 if(node->left)enArrayQueue(queue,node->left); if(node->right)enArrayQueue(queue,node->right); } //遍历结束,释放队列 releaseArrayQueue(queue); }
3.3递归
递归的概念
- 递归其实就是某个函数直接或者间接的调⽤了⾃身。这种调⽤方式叫递归调⽤。说⽩了还是一个函数调⽤。
- 既然是函数调⽤,那么就有一个雷打不动的原则:所有被调⽤的函数都将创建一个副本,各⾃为调⽤者服务,⽽不受 其他函数的影响。
递归的条件
递归函数分为两个条件,边界条件和递归条件。
- 边界条件:就是递归中⽌条件,避免出现死循环。也叫做递归出⼝。
- 递归条件:也就是递归体。将一个⼤问题分解为一步步⼩问题。也是递归调⽤的阶段。
递归函数在具备这两个要素以后,才可以在有限次的计算后得出想要的结果。
3.4深度遍历
每个节点处理方式都是一样 大任务 分解成小任务
小任务处理完后,最终回到大任务处结束 递归
3.4.1先序遍历
- 先访问根结点、然后左子树、最后右子树
//递归写法 static void preOrder(TreeNode *node) { if (node) { visitTreeNode(node); //访问根节点 preOrder(node->left); //进入左节点 preOrder(node->right); //进入右节点 } }void preOrderBTreeRecur(BinaryTree *tree) { if (tree) { preOrder(tree->root); } }
中序遍历代码和后序遍历代码与前序代码类似
3.4.2中序遍历
- 先访问左子树、然后根节点、最后右子树
3.4.3后序遍历
- 先访问左子树、然后右子树、最后根节点
3.4.4二叉树的先序、中序、后序互推方法
先序 可以确定一棵树的根节点 后序 从后看也能确定根
中序 一旦确定了根节点后,在中序的序列中,以根为中心,左边的序列一定是这个根的左孩子,另外就是右孩子
只知道先序和后序不能推出二叉树
3.5二叉树非递归深度遍历算法
需要使用栈
1.先序非递归写法
- 先压入根节点
- 弹出元素 访问
- 将这个元素的右边先压栈,然后左边再压栈
2.中序非递归写法
- 以根节点开始,整条左边进栈
- 从栈中弹出节点,访问,然后以这个节点的右孩子为新结点
- 再次让整条左边进栈,再弹栈
3.后序非递归写法
- 非递归的后序遍历,需要两个栈,第一个栈作为赋值,第二个栈作为输出
- 第一个栈压入根节点后,弹到第二个栈,根节点就变成最后输出
- 后序遍历的倒数第二个应该是头节点的右孩子,所以辅助栈,先左后右
- 辅助栈弹出元素放入到第二个栈,这个节点先左后右放第一个栈
3.6根据遍历结果重构二叉树
1. 若某二叉树的前遍历访问顺序是序abdgcefh,中序遍历顺序是dgbaechf,则后序遍历的访问顺序是什么。
2. 已知一棵二叉树的中序序列和后序序列分别是BDCEAFHG 和 DECBHGFA,请画出这棵二叉树。
3.7完整代码
//arrayQueue.h
#ifndef DATA_STRUCTURE_ARRAYQUEUE_H
#define DATA_STRUCTURE_ARRAYQUEUE_H
#include "binaryTree.h"
#define MaxQueue 100
//定义顺序队列的结构
typedef struct {
pTreeNode data[MaxQueue]; //顺序队列数组
int front; //队头指针,出队用
int rear; //队尾指针,入队用
}ArrayQueue;
//创建顺序队列
ArrayQueue *createArrayQueue();
//删除顺序队列
void releaseArrayQueue(ArrayQueue *queue);
//插入元素
int enArrayQueue(ArrayQueue *queue,pTreeNode e );
//删除元素
int deArrayQueue(ArrayQueue *queue,pTreeNode *e);
#endif //DATA_STRUCTURE_ARRAYQUEUE_H
//arrayQueue.cpp
#include "arrayQueue.h"
using namespace std;
//创建顺序队列
ArrayQueue *createArrayQueue(){
ArrayQueue *queue=new ArrayQueue ; //申请顺序队列空间
queue->front=queue->rear=0; //对顺序队列里的头尾指针初始化
return queue; //返回分配空间的地址
}
//删除顺序队列
void releaseArrayQueue(ArrayQueue *queue){
if(queue== nullptr){
cout<<"要删除的顺序队列不存在"<<endl;
return;
}
delete queue;
}
//插入元素
int enArrayQueue(ArrayQueue *queue,pTreeNode e ){
//插入元素前先判断,队列是否为满
if((queue->rear+1)%MaxQueue==queue->front){
cout<<"队列已满"<<endl;
return -1;
}
queue->rear=(queue->rear+1)%MaxQueue; //头指针先加一
queue->data[queue->rear]=e; //对指向的位置赋值
return 0;
}
//删除元素
int deArrayQueue(ArrayQueue *queue,pTreeNode *e){
//删除元素前先判断顺序队列是否为空
if(queue->front==queue->rear){
cout<<"队列为空"<<endl;
return -1;
}
queue->front=(queue->front+1)%MaxQueue; //尾指针++
*e=queue->data[queue->front]; //将要删除的元素赋值给*e
return 0;
}
//arayStack.h
#ifndef ARRAYSTACK_H
#define ARRAYSTACK_H
/* 顺序栈 满递增栈*/
#include "binaryTree.h"
#define MaxStackSize 20
typedef struct {
pTreeNode data[MaxStackSize];
int top;
}ArrayStack;
ArrayStack *createArrayStack();
void releaseArrayStack(ArrayStack *stack);
int pushArrayStack(ArrayStack *stack, pTreeNode e);
int popArrayStack(ArrayStack *stack, pTreeNode *e);
#endif
//arrayStack.cpp
#include "arrayStack.h"
using namespace std;
//创建顺序栈
ArrayStack *createArrayStack(){
ArrayStack *stack=new ArrayStack ; //申请顺序栈的空间
stack->top=-1; //初始化顺序栈
return stack;
}
//删除顺序栈
void releaseArrayStack(ArrayStack *stack){
if(stack){ //判断stack是否存在
delete stack; //释放分配给stack的空间
}else{
cout<<"要被删除的顺序栈不存在"<<endl;
return;
}
}
//入栈
int pushArrayStack(ArrayStack *stack,pTreeNode e){
if(stack->top>=MaxStackSize-1){ //判断是否栈满
cout<<"栈已满"<<endl;
return -1;
}
stack->data[++stack->top]=e; //top++,对应位置写入元素
return 0;
}
//出栈
int popArrayStack(ArrayStack *stack,pTreeNode * e){
if(stack->top<0){ //判断栈是否为空
cout<<"栈以空"<<endl;
return -1;
}
*e=stack->data[stack->top--]; //将top对应元素给e,然后top--
return 0;
}
//binaryTree.h
#ifndef DATA_STRUCTURE_BINARYTREE_H
#define DATA_STRUCTURE_BINARYTREE_H
#include <iostream>
typedef int Element;
//树的节点结构
typedef class treeNode{
public:
Element data; //保存的元素
treeNode *left; //左子节点
treeNode *right; //右子节点
}TreeNode;
typedef TreeNode* pTreeNode;
//二叉树描述信息的结构
typedef class {
public:
TreeNode* root; //二叉树的根节点
int count; //二叉树的节点数量
}BinaryTree;
BinaryTree* createBinaryTree(TreeNode* root);
void releaseBinaryTree(BinaryTree* tree);
TreeNode* createTreeNode(Element e);
void insertBinaryTree(BinaryTree* tree,TreeNode* parent,TreeNode* left,TreeNode* right);
void visitTreeNode(TreeNode* node);
void preOrderBTreeRecur(BinaryTree *tree); // 先序遍历tree,使用递归方法
void inOrderBTreeRecur(BinaryTree *tree); // 中序遍历tree,使用递归方法
void postOrderBTreeRecur(BinaryTree *tree); // 后序遍历tree,使用递归方法
// 层级遍历、广度遍历
void levelOrderBTree(BinaryTree *tree);
//遍历非递归写法
void preOrderBTreeNoRecur(BinaryTree *tree);
void inOrderBTreeNoRecur(BinaryTree *tree);
void postOrderBTreeNoRecur(BinaryTree *tree);
#endif //DATA_STRUCTURE_BINARYTREE_H
//binarryTree.cpp
#include "arrayQueue.h"
#include "arrayStack.h"
/* 申请二叉树的信息体,若有根节点,那么指向根节点,并更新树的节点个数 */
BinaryTree* createBinaryTree(TreeNode* root){
BinaryTree* tree=new BinaryTree ;
if(root){
tree->root=root;
tree->count=1;
}else{
tree->root= nullptr;
tree->count=0;
}
return tree;
}
/*后序递归遍历释放节点*/
static void destoryTreeNode(BinaryTree *tree,TreeNode* node){
if(node){
destoryTreeNode(tree,node->left);
destoryTreeNode(tree,node->right);
delete node;
tree->count--;
}
}
/* 释放二叉树,通过后序遍历的方式,将节点逐个释放 */
void releaseBinaryTree(BinaryTree* tree){
destoryTreeNode(tree,tree->root);
std::cout<<"树还有"<<tree->count<<"个节点";
delete tree;
}
/*产生新节点,初始化左右指针为null*/
TreeNode* createTreeNode(Element e){
TreeNode* node=new TreeNode ;
node->data=e;
node->left=node->right= nullptr;
return node;
}
/*向父节点插入左右节点*/
void insertBinaryTree(BinaryTree* tree,TreeNode* parent,TreeNode* left,TreeNode* right){
if(tree&&parent){
parent->left=left;
parent->right=right;
if(left)tree->count++;
if(right)tree->count++;
}
}
/*查看节点内容*/
void visitTreeNode(TreeNode* node){
std::cout<<node->data<<'\t';
}
static void preOrder(TreeNode* node){
if(node){
visitTreeNode(node);
preOrder(node->left);
preOrder(node->right);
}
}
static void inOrder(TreeNode* node){
if(node){
preOrder(node->left);
visitTreeNode(node);
preOrder(node->right);
}
}
static void postOrder(TreeNode* node){
if(node){
preOrder(node->left);
preOrder(node->right);
visitTreeNode(node);
}
}
/*递归先序遍历*/
void preOrderBTreeRecur(BinaryTree *tree){
if(tree){
preOrder(tree->root);
}
}
/*递归中序遍历*/
void inOrderBTreeRecur(BinaryTree *tree){
if(tree){
inOrder(tree->root);
}
}
/*递归后序遍历*/
void postOrderBTreeRecur(BinaryTree *tree){
if(tree){
postOrder(tree->root);
}
}
// 层级遍历、广度遍历
void levelOrderBTree(BinaryTree *tree){
ArrayQueue *queue=new ArrayQueue ; //申请一个队列
pTreeNode node; //用来存放取出的节点
enArrayQueue(queue,tree->root); //根节点入队
while(deArrayQueue(queue,&node)!=-1){ //取出节点
//访问节点
visitTreeNode(node);
//入队左右节点
if(node->left)enArrayQueue(queue,node->left);
if(node->right)enArrayQueue(queue,node->right);
}
//遍历结束,释放队列
releaseArrayQueue(queue);
}
/* 非递归的先序遍历:
* 1. 压入根节点到栈
* 2. 弹出元素,访问该元素
* 3. 将该元素的所知道的任务压入栈(有右压右,有左压左)
* */
void preOrderBTreeNoRecur(BinaryTree *tree){
treeNode* node;
if(tree){
ArrayStack* stack=createArrayStack() ;
pushArrayStack(stack,tree->root);
while(popArrayStack(stack,&node)!=-1&&node){
visitTreeNode(node);
if(node->right)pushArrayStack(stack,node->right);
if(node->left)pushArrayStack(stack,node->left);
}
releaseArrayStack(stack);
}
}
/* 非递归的中序遍历:
* 以根节点开始,整条左边进栈
* 从栈中弹出节点,访问,然后以这个节点的右孩子为新节点
* 再次安装整条左边进栈,再弹栈
* */
void inOrderBTreeNoRecur(BinaryTree *tree){
TreeNode* node;
if(tree->root){
ArrayStack* stack=createArrayStack() ;
node=tree->root;
while(stack->top>=0||node){
if(node){
pushArrayStack(stack,node);
node=node->left;
}else{
popArrayStack(stack,&node);
visitTreeNode(node);
node=node->right;
}
}
releaseArrayStack(stack);
}
}
/* 1. 非递归的后序遍历,需要两个栈,第一个栈作为辅助,最后一个栈作为输出
* 2. 第一个栈压入根节点后,弹出第二个栈,根节点就变成最后输出
* 3. 后序遍历的倒数第二个应该是头节点的右孩子,所以辅助栈,先左后右
* 4. 辅助栈弹出元素放入到第二个栈,这个节点先左后右放第一个栈
* */
void postOrderBTreeNoRecur(BinaryTree *tree){
TreeNode* node;
if(tree){
ArrayStack* stack1=createArrayStack() ;
ArrayStack* stack2=createArrayStack() ;
pushArrayStack(stack1,tree->root); //初始化辅助栈
while (popArrayStack(stack1,&node)!=-1){
pushArrayStack(stack2,node);
if(node->left)pushArrayStack(stack1,node->left);
if(node->right)pushArrayStack(stack1,node->right);
}
while(popArrayStack(stack2,&node)!=-1){
visitTreeNode(node);
}
releaseArrayStack(stack1);
releaseArrayStack(stack2);
}
}
#include "binaryTree.h"
BinaryTree *initTree1() {
TreeNode *nodeA = createTreeNode('A');
TreeNode *nodeB = createTreeNode('B');
TreeNode *nodeC = createTreeNode('C');
TreeNode *nodeD = createTreeNode('D');
TreeNode *nodeE = createTreeNode('E');
TreeNode *nodeF = createTreeNode('F');
TreeNode *nodeG = createTreeNode('G');
TreeNode *nodeH = createTreeNode('H');
TreeNode *nodeK = createTreeNode('K');
BinaryTree *tree = createBinaryTree(nodeA);
insertBinaryTree(tree, nodeA, nodeB, nodeE);
insertBinaryTree(tree, nodeB, NULL, nodeC);
insertBinaryTree(tree, nodeE, NULL, nodeF);
insertBinaryTree(tree, nodeC, nodeD, NULL);
insertBinaryTree(tree, nodeF, nodeG, NULL);
insertBinaryTree(tree, nodeG, nodeH, nodeK);
return tree;
}
int main() {
BinaryTree *tree = initTree1();
printf("树的节点数: %d\n", tree->count);
printf("先序遍历: ");
preOrderBTreeRecur(tree);
printf("\n中序遍历: ");
inOrderBTreeRecur(tree);
printf("\n后序遍历: ");
postOrderBTreeRecur(tree);
printf("\n层次遍历: ");
levelOrderBTree(tree);
printf("\n非递归先序遍历: ");
preOrderBTreeNoRecur(tree);
printf("\nInorder: ");
inOrderBTreeNoRecur(tree);
printf("\nPostOrder:");
postOrderBTreeNoRecur(tree);
printf("\n");
releaseBinaryTree(tree);
return 0;
}
4.线索二叉树
4.1背景介绍
现有一棵结点数目为n的二叉树,采⽤二叉链表的形式存储,对于每个结点均有指向左右孩子的两个指针域。 结点为n的二叉树一共有n-1条有效分支路径。那么,则二叉链表中存在
2n - ( n-1 ) = n + 1个 空指针域
那么,这些空指针造成了空间浪费。
当对二叉树进行中序遍历时可以得到二叉树的中序序列。
如图所示二叉树的中序遍历结果为DBEAC,可以得知A的前驱结点为E,后继结点为C
这种关系的获得是建⽴在完成遍历后得到 的,那么可不可以在建⽴二叉树时就记录下前驱后继的关系呢,那么在后续寻找前驱结点和后继结点时将⼤⼤提升效率
4.2线索化
- 将某结点的空指针域指向该结点的前驱后继,定义规则如下:
- 若结点的左子树为空,则该结点的左孩子指针指向其前驱结点。
- 若结点的右子树为空,则该结点的右孩子指针指向其后继结点。
- 这种指向前驱和后继的指针称为线索。将一棵普通二叉树以某种次序遍历,并添加线索的过程称为线索化。
4.3线索化的改进
- 可以将一棵二叉树线索化为一棵线索二叉树,那么新的问题产⽣了。我们如何区分一个结点的 lchild指针是指向左孩 子还是前驱结点呢?
- 为了解决这一问题,现需要添加标志位ltag,rtag。并定义规则如下:
- ltag为0时,指向左孩子,为1时指向前驱
- rtag为0时,指向右孩子,为1时指向后继
- 在遍历过程中,如果当前结点没有左孩子,需要将该结点的 lchild 指针指向遍历过程中的前一个结点,所以 在遍历过程中,设置一个指针(名为 pre ),时刻指向当前访问结点的前一个结点。
4.4线索化的优势
- 递归遍历需要使⽤系统栈,非递归遍历需要使⽤内存中的空间来帮助遍历,⽽线索化之后就不需 要这些辅助了,直接可以像遍历数组一样遍历。
- 线索二叉树核心目的在于加快查找结点的前驱和后继的速度。如果不使⽤线索的话,当查找一个 结点的前驱与后继需 要从根节点开始遍历,当然,如果二叉树数据量较⼩时,可能线索化之后作⽤不 ⼤,但是当数据量很⼤时,线索化所 带来的性能提升就会比较明显。
4.5完整代码
//threadedTree.h
#ifndef DATA_STRUCTURE_THREADEDTREE_H
#define DATA_STRUCTURE_THREADEDTREE_H
typedef int Element;
// 线索二叉树的节点结构
typedef struct treeNode {
Element data;
struct treeNode *left;
struct treeNode *right;
int lTag; // 0表示left指向左节点,1表示left指向前驱
int rTag; // 0表示right指向右节点,1表示right指向后继
}TBTNode;
// 二叉树的描述信息结构
typedef struct {
TBTNode *root; // 二叉树的根节点
int count; // 二叉树的节点个数
}ThreadedBTree;
//创建线索树表头
ThreadedBTree *createThreadedBTree(TBTNode *root);
//释放线索树
void releaseThreadedBTree(ThreadedBTree *tree);
//创建线索化二叉树节点
TBTNode *createTBTNode(Element e);
//插入节点
void insertThreadedBTree(ThreadedBTree *tree, TBTNode *parent, TBTNode *left, TBTNode *right);
//访问节点
void visitTBTNode(TBTNode *node);
// 中序线索化树,在中序遍历的过程中,建立左右孩子为空指针的值的确定
void inOrderThreadedTree(ThreadedBTree *tree);
// 按照已经线索化后的树,进行中序遍历
void inOrderTBTree(ThreadedBTree *tree);
#endif //DATA_STRUCTURE_THREADEDTREE_H
//threadedTree.cpp
#include "threadedTree.h"
#include<iostream>
using namespace std;
//创建线索树表头
ThreadedBTree *createThreadedBTree(TBTNode *root) {
ThreadedBTree *tree = new ThreadedBTree ;
if (root) {
tree->root = root;
tree->count = 1;
} else {
tree->root = NULL;
tree->count = 0;
}
return tree;
}
// 后序遍历释放节点,但在找后继节点时,需要找非线索化的节点
static void freeTBTNode(ThreadedBTree* tree,TBTNode* node){
if(node){
if(node->rTag==0)freeTBTNode(tree,node->right);
if(node->lTag==0)freeTBTNode(tree,node->left);
delete node;
tree->count--;
}
}
//释放线索树
void releaseThreadedBTree(ThreadedBTree *tree){
if(tree){
freeTBTNode(tree,tree->root);
cout<<"树还有"<<tree->count<<"个节点。"<<endl;
}
delete tree;
}
/* 产生新节点,初始化左右指针为NULL */
TBTNode *createTBTNode(Element e){
TBTNode* node=new TBTNode ;
node->left=node->right= nullptr;
node->rTag=node->lTag=0;
node->data=e;
return node;
}
//插入节点
void insertThreadedBTree(ThreadedBTree *tree, TBTNode *parent, TBTNode *left, TBTNode *right){
if(tree&&parent){
parent->left=left;
parent->right=right;
if(left)tree->count++;
if(right)tree->count++;
}
}
//访问节点
void visitTBTNode(TBTNode *node){
if(node)cout<<node->data<<"\t";
}
static TBTNode *pre = NULL; //用来保存前一个节点位置的指针
static void inOrderThreading(TBTNode *node){
if(node){
inOrderThreading(node->left);
if(node->left== nullptr){ //更新当前结点的前驱
node->lTag=1;
node->left=pre;
}
// 当前节点会不会是前面那个节点后继节点
if(pre&&pre->right== nullptr){
pre->rTag=1;
pre->right=node;
}
pre=node;
inOrderThreading(node->right);
}
}
// 中序线索化树,在中序遍历的过程中,建立左右孩子为空指针的值的确定
void inOrderThreadedTree(ThreadedBTree *tree){
if(tree){
inOrderThreading(tree->root);
}
}
// 按照已经线索化后的树,进行中序遍历
void inOrderTBTree(ThreadedBTree *tree){
TBTNode *node = tree->root;
while (node) {
while (node->lTag == 0) {
node = node->left;
}
visitTBTNode(node);
// 一直向右开始遍历,只要右边是后继标记,就查看
while (node->rTag && node->right) {
node = node->right;
visitTBTNode(node);
}
node = node->right;
}
}
#include <iostream>
#include "threadedTree.h"
ThreadedBTree *initTree() {
TBTNode *nodeA = createTBTNode('A');
TBTNode *nodeB = createTBTNode('B');
TBTNode *nodeC = createTBTNode('C');
TBTNode *nodeD = createTBTNode('D');
TBTNode *nodeE = createTBTNode('E');
TBTNode *nodeF = createTBTNode('F');
TBTNode *nodeG = createTBTNode('G');
TBTNode *nodeH = createTBTNode('H');
TBTNode *nodeK = createTBTNode('K');
ThreadedBTree *tree = createThreadedBTree(nodeA);
insertThreadedBTree(tree, nodeA, nodeB, nodeE);
insertThreadedBTree(tree, nodeB, NULL, nodeC);
insertThreadedBTree(tree, nodeE, NULL, nodeF);
insertThreadedBTree(tree, nodeC, nodeD, NULL);
insertThreadedBTree(tree, nodeF, nodeG, NULL);
insertThreadedBTree(tree, nodeG, nodeH, nodeK);
return tree;
}
int main() {
ThreadedBTree *tree = initTree();
// 1. 对原二叉树进行中序线索化,n + 1个空节点,按照左孩子指针指向前驱,右孩子指针指向后继
inOrderThreadedTree(tree);
// 2. 根据线索化后的树,进行中序遍历
inOrderTBTree(tree);
releaseThreadedBTree(tree);
return 0;
}