今天我们来整理一下有关于树的知识点,这个地方非常的繁杂,大家要耐心学习。


树是数据结构中重要的一个知识点,我们会整理到的有:树和二叉树的定义及抽象数据类型、树的存储结构及常见的三种表示方法、二叉树的性质和存储结构、遍历二叉树和线索二叉树、数和二叉树的转换及应用、哈夫曼树及应用。

7.1初识树

在紫薇星上的数据结构(1)中,也就是本系列的开头我们讲过,逻辑结构包括四种:集合结构、线性结构、树形结构、图形结构;其中集合结构我们整理过,就是数据元素之间除了“同属一个集合”外,无其他关系;线性结构则表示“一对一”的关系,线性表、栈、队列、串这些都是线性结构;而今天我们来认识一个新的逻辑结构:树形结构,也就是“一对多”的关系,之后我们还会整理图形结构,也就是“多对多”的关系,可以将树形结构看作简化版的图形结构。

树的定义

树形结构,也就是“一对多”的关系,我们生活中常常能见到树形结构,比如书中的目录、游戏中的科技树、家族的族谱、电脑中的文件夹等都是一对多的关系,是树的结构。

在《数据教程|C语言版 第二版》中定义:树(Tree)是 n (n ≥ 0)个结点的有限集合,它或为空树(n = 0);或为非空树,对于非空树T:

  • 有且仅有一个称之为根的结点;
  • 除根结点以外的其余结点可分为 m (m > 0)个互不相交的有限集T1,T2,...,Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

在这个树中,我们可以看到有根结点、子结点、叶子结点,其中叶子结点是指树的子主题,也就是终端结点。

每个结点挂接的子树的个数就称为度,一般度表示的是树中子树的度的最大值,在上面的树中的度为3,是根节点的度;而从根结点到最终端的叶子结点所经过的层数就成为树的层次,整个树的层数就成为树的深度,上图中树的深度为3。

在树中有以下几个概念需要理解,我们用家谱的方式来解释:

  • 双亲结点:上层的结点,也就是当前结点的直接前驱;
  • 孩子结点:下层结点的子树的根,也就是当前结点的直接后继;
  • 兄弟结点:同一双亲下的同层次结点;
  • 堂兄弟结点: 不同双亲下的同层次结点;
  • 祖先结点:从根结点到该结点所经过的分支中的所有结点;
  • 子孙结点:该结点下的子树中任一结点。

森林的定义

森林就是去掉根节点的树,我们将一个树的根结点删除掉,剩下的子树的集合就是森林,森林里的树是各不相交的,同时树还分为有序树与无序树两种,我们举个例子:

  • 我们将树的根结点A删掉,只留下子树B,C,D,那么B,C,D所组成的集合就是森林;
  • 有序树:结点各子树从左至右有序,不能互换(左为第一),子树B的顺序就是E→F→G;
  • 无序树:结点各子树之间可以互换位置,如果B为无序树,那么顺序可以为EFG,也可以为GFE。

7.2初识二叉树

刚才我们了解了树,那么现在我们来看一下树中比较特殊的一个——二叉树。

二叉树(Binary Tree)是n(n > 0)个结点所构成的集合,它或为空树(n = 0);或为非空树,对于非空树T:

  • 有且仅有一个称之为根的结点;
  • 除根结点以外的其余结点可分为2个互不相交的有限子集T1,T2,并且称为根的左子树和右子树,且T1和T2本身也是二叉树。

我们举一个例子来看一下二叉树,二叉树与树一样都有递归性质,但是他们也有不同:

二叉树
至多只有两棵子树(结点的度不能超过二)

没有限制子树个数

子树有左右之分,次序不能任意颠倒(有序树) 可以是有序树,也可以是无序树

我们在生活中也能找到二叉树的影子,例如各种比赛的晋级流程图:32进16,16进8,8进4,4进2,2进1,这种的流程就是一个典型的二叉树。二叉树说白了就是只有两个“叉”的有序树,除了空树和只有根结点的情况,二叉树的情况大概有这么多:

前面我们都可以容易看出结构,最后两种结构特别相似,但其实不是一种结构,他们的终端结点分别是左结点和右结点。

如果我们要问只有三个结点有几种形态,我们可以容易得出有五种形态:

那么我们为什么从研究树要跳转到研究二叉树呢?如果不把普通的树(多叉树)转换成二叉树,那么运算就很难实现,因为二叉树的结构最简单,规律性最强;可以证明得出,所有的树都可以转化成唯一对应的二叉树,并且不失二叉树的一般性,这里我们就不证明了。

7.3二叉树的性质

性质1:在二叉树的第 i 层上至多有个结点。

这是因为二叉树的每个结点最多只有两个子结点,所以在第一层最多有一个结点,在第二层最多有两个结点,第三层最多有四个结点,...,以此类推,第 i 层上至多有个结点。同时因为每个结点最多只有两个子结点,所以下一层的结点数最多是上一层的2倍。

性质2:深度为 k 的二叉树至多有个结点。

注意这里说的是深度与整个树的结点的关系,性质1是层数与当前层数结点的关系,大家不要搞混,因为性质1的存在,所以深度为1的树最多有一个结点,深度为2的树最多有 1 + 2 = 3个结点,深度为3的树最多有 1 + 2 + 4 = 7个结点,...,以此类推,深度为 k 的二叉树至多有个结点。

性质3:对于任何一个二叉树,若度为2的结点有 个,那么叶子数 必定为 + 1,即​​​​

这个性质可以证明,我们设度为0的结点总数为,度为1的结点总数为,度为2的结点总数为,总结点数称为 n ,分支数称为B,就可以得出:

分支数,结点数,那么就可以得出,也就是。我们用一张图来解释一下:

在这张图中我们将所有的度为0的结点个数总和称为,度为1的结点个数总和称为,度为2的结点个数总和称为​​​​​​​,总结点数称为 n ,分支数称为B,那么就有:,代入上面的结论,就可以验证了。

完全二叉树与满二叉树

完全二叉树:深度为K且含有个结点的二叉树;每层结点数都是最大结点数。

满二叉树:深度为K,有 n 个结点的二叉树,当且仅当其中每一个结点都与深度为K的满二叉树中的结点编号顺序一一对应。

当我们遇到一个深度为K的二叉树,我们只需要按照深度为K进行满顺序编号,如果此二叉树的编号顺序与满顺序编号完全相同,则是满二叉树;如果在某个编号后免得全都没有,那么就是完全二叉树;我们举几个例子来看一下:

完全二叉树的特点,可以与上图中进行对比理解:

  • 叶子结点只能出现在最下面两层;
  • 最下面一层的节点一定是在左部的连续位置;
  • 倒数第二层若有叶子结点,那么一定在右侧连续位置;
  • 如果有度为1的结点,那么这个结点一定只有左孩子;
  • 不存在只有右子树、右孩子的情况出现;
  • 同样结点数的二叉树,完全二叉树的深度最小。

性质4:具有 n 个结点的完全二叉树的深度必定为,其中表示对x进行向下取整,

由性质2可得,在 k - 1 层至多有个结点,在 k 层至多有个结点,那么就可以得出结点数 n 在第 k 层是有,也就是 (k为整数),所以

性质5:完全二叉树,若从上至下、从左至右的进行顺序编号,则编号为 i 的结点,其左孩子编号必定为 2i ,其右孩子编号必定为 2i + 1,其双亲编号必定为

二叉树的抽象数据类型

ADT BinaryTree
Data    D是具有相同特性的数据元素的集合
Relative若D = Ф,则R = Ф;若D ≠ Ф,则R = {H}; //存在二元关系①root唯一          //关于根的说明②Dj ∩ Dk = Ф       //关于子树不相交的说明③......            //关于数据元素的说明④......            //关于左子树和右子树的说明
OperationcreateBiTree(&T, defination);    //构建二叉树preOrderTraverse(T);             //先序遍历inOrderTraverse(T);              //中序遍历postOrderTraverse(T);            //后序遍历levelOrderTraverse(T);           //层序遍历......                           //其他20多个操作
endADT

7.4二叉树的顺序存储结构

因为二叉树是顺序树,结点编号是由从上至下、从左至右的规律编号的,所以我们可以使用数组进行顺序存储结构的二叉树建立,将根结点放在第一个位置,按照满二叉树顺序的下一个结点放在第二个位置,因为二叉树不一定都是满二叉树,所以只要遇到没有结点的位置,我们在数组中存储一个‘0’进去,这样就能实现顺序存储结构存储二叉树,我们举个例子:

不过这样存储的话有个最大的缺陷就是:这是一个深度为4,且结点为4的二叉树,也就是说这个二叉树中只有一个根结点和三个左子树,那么它只有四个结点但存放它需要一个长度为15的数组。

也就是说在最坏的情况下,存放一个深度为 k 且只有 k 个结点的单支树(即不存在度为2的结点)却需要个长度的一维数组。

我们如何正确存放呢?举个例子说明一下:

这是一个满二叉树,我们已B结点为例子:B结点在数组中的下标为1,B结点的左子树D在数组中的下标为3,右子树E在数组中的下标为4;那么我们假设B结点的下标为 i ,那么D的下标为 2 * i + 1,E的下标为 2 * (i + 1);我们使用这个规律来存放。

我们使用代码来实现一下顺序存储二叉树,首先建立一个SeqTree.h和SeqTree.c文件,在SeqTree.h中编写操作:

#ifndef SEQTREE_H_INCLUDED
#define SEQTREE_H_INCLUDED#include <stdio.h>
#include <stdlib.h>//最大结点数
#define MAX_SIZE 1024//定义顺序树类型
typedef char SeqTree[MAX_SIZE];//初始化
void InitSeqTree(SeqTree tree);//创建完全二叉树,i为数组中的下标
void CreateSeqTree(SeqTree tree, int i);//获取树的根结点元素
char GetSeqTreeRoot(SeqTree tree);//获取树的长度
int GetSeqTreeLength(SeqTree tree);//获取树的深度
int GetSeqTreeDepth(SeqTree tree);#endif // SEQTREE_H_INCLUDED

然后在SeqTree.c中添加操作:

#include "SeqTree.h"
#include <math.h>
//初始化
void InitSeqTree(SeqTree tree){//将字符数组中的每个元素都设置为空字符for(int i = 0; i < MAX_SIZE; i++){tree[i] = '\0';}
}//创建完全二叉树,i为数组中的下标
void CreateSeqTree(SeqTree tree, int i){char ch;ch = getchar();fflush(stdin);if(ch == '^'){tree[i] = '\0';return;}tree[i] = ch;//某个结点输入完成后,再让用户输入左孩子和右孩子printf("左孩子结点:");CreateSeqTree(tree, 2 * i + 1); //递归调用printf("右孩子结点:");CreateSeqTree(tree, 2 * (i + 1)); //递归调用}//获取树的根结点元素
char GetSeqTreeRoot(SeqTree tree){return tree[0];
}//获取树的长度
int GetSeqTreeLength(SeqTree tree){int len;//从最后一位开始检查,不为‘\0’时即为总长度for(len = MAX_SIZE; len >= 1; len--){if(tree[len - 1] != '\0'){break;}}return len;
}//获取树的深度
int GetSeqTreeDepth(SeqTree tree){//由性质2可得int depth = 0;int len = GetSeqTreeLength(tree);while((int)pow(2, depth) - 1 < len){depth++;}return depth;
}

这些就是一些基本的操作,我们在main.c中实现来看一下:

#include <stdio.h>
#include <stdlib.h>
#include "SeqTree.h"void TestSeqTree();int main(){//printf("Hello world!\n");TestSeqTree();return 0;
}void TestSeqTree(){SeqTree tree;InitSeqTree(tree);printf("请输入数据,如果结点无数据,请输入‘ ^ ’\n请输入根节点:");CreateSeqTree(tree, 0);for(int i = 0; i < 15; i++){printf("%c, ", tree[i]);}printf("\n");printf("树的根节点为:%c\n", GetSeqTreeRoot(tree));printf("树的长度为:%d\n", GetSeqTreeLength(tree));printf("树的深度为:%d\n", GetSeqTreeDepth(tree));}

编译通过,我们就随便输入一个只有A、B、C的满二叉树进去看一下,自己输的时候要注意我们只设置了数组长为15,也就是说只能输到第四层:

请输入数据,如果结点无数据,请输入‘ ^ ’
请输入根节点:A
左孩子结点:B
左孩子结点:^
右孩子结点:^
右孩子结点:C
左孩子结点:^
右孩子结点:^
A, B, C,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,
树的根节点为:A
树的长度为:3
树的深度为:2Process returned 0 (0x0)   execution time : 8.562 s
Press any key to continue.

7.5二叉树的链式存储结构

相信大家也可以看出来,顺序存储的时候有一个很大的问题就是浪费空间,如果有一个之前那样的单支树,那么就要建立一个很长的数组来存放几个数据,这样是很划不来的,所以我们通常都使用链式存储结构来建立二叉树,我们来举一个例子:

像这样的二叉树,我们使用顺序存储结构至少要建立一个长度为15的数组才能完全存放,如果以后想要对其进行增加,那就非常麻烦;不过我们使用链式存储结构时,就很容易了:每个结点都有两个指针域,分别指向它的左孩子和右孩子,这样存储不仅方便而且容易对其进行增删改查。

但同时我们要知道,如果指向的结点没有元素数据,那么就存储为空,虽然存储为空占的空间很小,但是仍然占据了一定空间,这时我们就要了解,在含有 n 个结点的二叉链表中有 n + 1 个空链域,利用这些链域可以存储其他有效信息,最大化利用空间,从而得到另一种链式存储结构——线索链表,这种链表我们之后会整理到。

使用二叉链表可以使用指针域可以快速地得到某个结点的孩子信息,甚至在孩子信息的空链域中可以得到一些譬如兄弟结点、堂兄弟结点的信息、也可以存放双亲结点的信息,但这只是在空链域中,真正的要从孩子结点中获得双亲结点的信息还是有些困难的,所以我们还有一种链表——三叉链表:

在三叉链表中每个结点有三个指针域,除了指向左右孩子之外,还有一个指针指向双亲结点,这样就能很快地寻找双亲结点,就相当于我们学习链表时的双向链表。

使用代码来实现一下二叉链,首先需要建立ElementType.h、BinaryTree.h和BinaryTree.c文件,在ElementType.h中编写:

#ifndef ELEMENTTYPE_H_INCLUDED
#define ELEMENTTYPE_H_INCLUDED#include <stdio.h>
#include <stdlib.h>//最大结点数
#define MAX_SIZE 1024
#define NAME_SIZE 255typedef struct{int id;char name[NAME_SIZE];
}ElementType;#endif // ELEMENTTYPE_H_INCLUDED

然后在BinaryTree.h中编写操作:

#ifndef BINARYTREE_H_INCLUDED
#define BINARYTREE_H_INCLUDED#include <stdio.h>
#include <stdlib.h>
#include "ElementType.h"
#include "TreeNode.h"typedef struct{TreeNode *root; //根结点int length; //二叉链结点总数int depth; //二叉链深度int diameter; //从叶子节点到叶子结点的最长路径
}BinaryTree;//初始化
void InitBinaryTree(BinaryTree *tree);//构造二叉树,外部需要实现为结点分配内存
//返回值为0时表示创建失败(不创建)
int CreateBinaryTree(TreeNode *root);#endif // BINARYTREE_H_INCLUDED

然后在BinaryTree.c中实现操作:

#include "BinaryTree.h"
#include <string.h>//用来实现结点ID的自增长
static int id = 0;//初始化
void InitBinaryTree(BinaryTree *tree){tree->root = NULL;tree->length = 0;tree->depth = 0;tree->diameter = 0;
}//构造二叉树,外部需要实现为结点分配内存
//返回值为0时表示创建失败(不创建)
int CreateBinaryTree(TreeNode *root){//根结点为空,退出创建if(!root){return 0;}char inputName[NAME_SIZE]; //用户输入的结点名gets(inputName);//输入回车表示结束当前子树的创建if(strcmp(inputName, "\0") == 0){return 0;}//创建当前结点root->data.id = ++id;strcpy(root->data.name, inputName);//为左右结点做准备-为左右结点指针分配内存root->left = (TreeNode*)malloc(sizeof(TreeNode));root->right = (TreeNode*)malloc(sizeof(TreeNode));//分别递归创建左右子树printf("左结点:");if(CreateBinaryTree(root->left) == 0){//不再创建这个结点则销毁刚分配的内存free(root->left);root->left = NULL;}printf("右结点:");if(CreateBinaryTree(root->right) == 0){//不再创建这个结点则销毁刚分配的内存free(root->right);root->right = NULL;}
}

这些结构原理在注释中都讲的很清楚,大家一定要多看几遍注释,然后main.c中实现:

#include <stdio.h>
#include <stdlib.h>
#include "BinaryTree.h"void TestBinaryTree();int main(){TestBinaryTree();return 0;
}
void TestBinaryTree(){BinaryTree tree;InitBinaryTree(&tree);//容易遗漏的点:根结点需要事先分配内存tree.root = (TreeNode*)malloc(sizeof(TreeNode));printf("请输入根结点内容:");CreateBinaryTree(tree.root);free(tree.root);
}

编译通过,运行结果如下:

请输入根结点内容:A
左结点:B
左结点:
右结点:
右结点:C
左结点:
右结点:Process returned 0 (0x0)   execution time : 10.633 s
Press any key to continue.

这样就实现了二叉树的链式存储结构,这里只实现了初始化和创建结点构造二叉树,至于遍历二叉树我们单独整理。

7.6四种遍历方式

遍历是二叉树操作中最常见的,它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。同时我们知道二叉树是树形结构,我们在观看的时候很容易可以找到规律,但是计算机不会,计算机只能将它处理为线性结构再来进行运算,所以我们就会使用遍历这种方式将二叉树变为线性结构。

遍历二叉树时从根节点出发,按照某种次序依次的访问二叉树中的所有结点,使得每个结点均被访问一次且仅被访问一次。

前序遍历

前序遍历的操作过程为先访问根结点、然后访问左子树、然后访问右子树。

在进行前序遍历时,如果二叉树为空,则返回空;否则:

  • 访问根结点;
  • 前序遍历访问左子树;
  • 前序遍历访问右子树。

这样写大家可能不能直观理解,解释一下:

  • 在每次访问左右子树时,被访问的结点就是它所在的子树的根结点;
  • 所以依然使用前序遍历先访问左结点再访问右结点;
  • 再次访问到下一个左子树时这个左结点又变为了它所在的子树的根结点;
  • 如此递归循环直到没有左子树可以访问,然后退出循环
  • 此时左子树已被访问,根据前序遍历应该访问右子树
  • 在右子树中重复上面的操作,直到所有的右子树也被访问完;
  • 这时候我们的位置一定在最下层的最有结点处,退出遍历。

或者如图所示:

要记得我们第一位访问的一定是根结点,然后是左子树,然后是右子树,所以前序遍历又可以记为:根左右遍历,代码非常简单,我们要了解的是思想。

使用代码实现一下,每次都是要自己输入非常麻烦,所以我们可以写一个测试用的满二叉树,在BinaryTree.h中编写操作:

//前序遍历:根左右
void PreOrderTraverse(TreeNode *root);//测试版的创建函数
int CreateBinaryTree_Test(TreeNode *root);

在BinaryTree.c中实现操作:

//模仿用户输入顺序
char *nodeNames[] = {"A","B", "D", "#", "#", "E", "#", "#","C", "F", "#", "#", "G", "#", "#"};
//访问nodeNames数组中的下标
static int nodeNamesIndex = 0;//测试版的创建函数
int CreateBinaryTree_Test(TreeNode *root){//根结点为空,退出创建if(!root){return 0;}char inputName[NAME_SIZE]; //用户输入的结点名//gets(inputName);strcpy(inputName, nodeNames[nodeNamesIndex++]);//输入回车表示结束当前子树的创建if(strcmp(inputName, "#") == 0){return 0;}//创建当前结点root->data.id = ++id;strcpy(root->data.name, inputName);//为左右结点做准备-为左右结点指针分配内存root->left = (TreeNode*)malloc(sizeof(TreeNode));root->right = (TreeNode*)malloc(sizeof(TreeNode));//分别递归创建左右子树//printf("左结点:");if(CreateBinaryTree_Test(root->left) == 0){//不再创建这个结点则销毁刚分配的内存free(root->left);root->left = NULL;}//printf("右结点:");if(CreateBinaryTree_Test(root->right) == 0){//不再创建这个结点则销毁刚分配的内存free(root->right);root->right = NULL;}
}//前序遍历:根左右
void PreOrderTraverse(TreeNode *root){if(root){printf("[%d, %s]-", root->data.id, root->data.name);PreOrderTraverse(root->left);PreOrderTraverse(root->right);}
}

然后main.c中实现:

void TestBinaryTree(){BinaryTree tree;InitBinaryTree(&tree);//容易遗漏的点:根结点需要事先分配内存tree.root = (TreeNode*)malloc(sizeof(TreeNode));printf("请输入根结点内容:");//CreateBinaryTree(tree.root);CreateBinaryTree_Test(tree.root);printf("\n前序遍历:\n");PreOrderTraverse(tree.root);free(tree.root);
}

一个简单的满二叉树,编译通过,运行结果如下:

请输入根结点内容:
前序遍历:
[1, A]-[2, B]-[3, D]-[4, E]-[5, C]-[6, F]-[7, G]-
Process returned 0 (0x0)   execution time : 0.025 s
Press any key to continue.

中序遍历

有了前序遍历,那么就会有中序遍历、后序遍历。中序遍历与前序遍历大同小异,不过中序遍历的流程为左根右遍历

在进行中序遍历时,如果二叉树为空,则返回空;否则:

  • 中序遍历访问左子树;
  • 访问根节点;
  • 中序遍历访问右子树。

这里要注意我们遍历一定是从根结点开始,然后在中序遍历过程中访问的都是子树的根结点

我们使用代码来实现一下,有了上次的经验这次就好很多了,先在BinaryTree.h中编写操作:

//中序遍历:左根右
void InOrderTraverse(TreeNode *root)

在BinaryTree.c中实现操作:

//中序遍历:左根右
void InOrderTraverse(TreeNode *root){if(root){PreOrderTraverse(root->left);printf("[%d, %s]-", root->data.id, root->data.name);PreOrderTraverse(root->right);}
}

然后main.c中实现:

void TestBinaryTree(){BinaryTree tree;InitBinaryTree(&tree);//容易遗漏的点:根结点需要事先分配内存tree.root = (TreeNode*)malloc(sizeof(TreeNode));printf("请输入根结点内容:");//CreateBinaryTree(tree.root);CreateBinaryTree_Test(tree.root);printf("\n前序遍历:\n");PreOrderTraverse(tree.root);printf("\n中序遍历:\n");InOrderTraverse(tree.root);free(tree.root);
}

编译通过,运行结果如下:

请输入根结点内容:
前序遍历:
[1, A]-[2, B]-[3, D]-[4, E]-[5, C]-[6, F]-[7, G]-
中序遍历:
[2, B]-[3, D]-[4, E]-[1, A]-[5, C]-[6, F]-[7, G]-
Process returned 0 (0x0)   execution time : 0.026 s
Press any key to continue.

后序遍历

看到这里大家应该对后序遍历有点把握了吧,后序遍历是左右根遍历:

在进行后序遍历时,如果二叉树为空,则返回空;否则:

  • 后序遍历访问左子树;
  • 后序遍历访问右子树。
  • 访问根节点;

我们使用代码来实现一下,先在BinaryTree.h中编写操作:

//后序遍历
void PostOrderTraverse(TreeNode *root);

在BinaryTree.c中实现操作:

//后序遍历
void PostOrderTraverse(TreeNode *root){if(root){PreOrderTraverse(root->left);PreOrderTraverse(root->right);printf("[%d, %s]-", root->data.id, root->data.name);}
}

然后main.c中实现:

void TestBinaryTree(){BinaryTree tree;InitBinaryTree(&tree);//容易遗漏的点:根结点需要事先分配内存tree.root = (TreeNode*)malloc(sizeof(TreeNode));printf("请输入根结点内容:");//CreateBinaryTree(tree.root);CreateBinaryTree_Test(tree.root);printf("\n前序遍历:\n");PreOrderTraverse(tree.root);printf("\n中序遍历:\n");InOrderTraverse(tree.root);printf("\n后序遍历:\n");PostOrderTraverse(tree.root);free(tree.root);
}

编译通过,运行结果如下:

请输入根结点内容:
前序遍历:
[1, A]-[2, B]-[3, D]-[4, E]-[5, C]-[6, F]-[7, G]-
中序遍历:
[2, B]-[3, D]-[4, E]-[1, A]-[5, C]-[6, F]-[7, G]-
后序遍历:
[2, B]-[3, D]-[4, E]-[5, C]-[6, F]-[7, G]-[1, A]-
Process returned 0 (0x0)   execution time : 0.035 s
Press any key to continue.

层次遍历

最后我们来说一下层次遍历,我们还可以使用从上至下、从左至右的方式一层一层的遍历结点,这样看起来和我们储存时差不多,不过不能使用前面那样的递归实现了,因为他不是通过递归来存放的,而是遍历顺序与存储顺序相同,再深入一点就是打印顺序与存储顺序相同,先入先出式,所以我们就使用队列来实现。

首先我们要创建新的文件LinkedQueue.h与LinkedQueue.c,在LinkedQueue.h中编写操作:

#ifndef LINKEDQUEUE_H_INCLUDED
#define LINKEDQUEUE_H_INCLUDED#include <stdio.h>
#include <stdlib.h>
#include "ElementType.h"
#include "TreeNode.h"//链队结点
typedef struct qNode{TreeNode *data;struct qNode *next;
}QueueNode;//链队列
typedef struct{QueueNode *qFront;QueueNode *qRear;
}LinkedQueue;void InitLinkedQueue(LinkedQueue *linkQueue);void enQueue(LinkedQueue *linkedQueue, TreeNode *data);TreeNode *deQueue(LinkedQueue *linkedQueue);int IsLinkedQueueEmpty(LinkedQueue *linkedQqueue);#endif // LINKEDQUEUE_H_INCLUDED

在LinkedQueue.c编写操作:

#include "LinkedQueue.h"void InitLinkedQueue(LinkedQueue *linkQueue){linkQueue->qFront = (QueueNode*)malloc(sizeof(QueueNode));linkQueue->qFront->next = NULL;linkQueue->qRear = linkQueue->qFront;}void enQueue(LinkedQueue *linkedQueue, TreeNode *data){QueueNode *node = (QueueNode*)malloc(sizeof(QueueNode));node->data = data;node->next = NULL;linkedQueue->qRear->next = node;linkedQueue->qRear = node;
}TreeNode *deQueue(LinkedQueue *linkedQueue){TreeNode *data = NULL;if(linkedQueue->qFront == linkedQueue->qRear){return data;}QueueNode *node = linkedQueue->qFront->next;data = node->data;linkedQueue->qFront->next = node->next;if(linkedQueue->qRear == node){linkedQueue->qRear = linkedQueue->qFront;}free(node);return data;
}int IsLinkedQueueEmpty(LinkedQueue *linkedQqueue){if(linkedQqueue->qFront == linkedQqueue->qRear){return 1;}return 0;
}

在BinaryTree.h中编写操作:

//层序遍历
void ZOrderTraverse(TreeNode *root)

在BinaryTree.c中实现操作:

//层序遍历
void ZOrderTraverse(TreeNode *root){LinkedQueue queue;InitLinkedQueue(&queue);//根结点入队enQueue(&queue, root);while(!IsLinkedQueueEmpty(&queue)){TreeNode *root= deQueue(&queue);printf("[%d, %s]-", root->data.id, root->data.name);if(root->left != NULL){enQueue(&queue, root->left);}if(root->right != NULL){enQueue(&queue, root->right);}}
}

然后main.c中实现:

void TestBinaryTree(){BinaryTree tree;InitBinaryTree(&tree);//容易遗漏的点:根结点需要事先分配内存tree.root = (TreeNode*)malloc(sizeof(TreeNode));printf("请输入根结点内容:");//CreateBinaryTree(tree.root);CreateBinaryTree_Test(tree.root);printf("\n前序遍历:\n");PreOrderTraverse(tree.root);printf("\n中序遍历:\n");InOrderTraverse(tree.root);printf("\n后序遍历:\n");PostOrderTraverse(tree.root);printf("\n层序遍历:\n");ZOrderTraverse(tree.root);free(tree.root);
}

编译通过,运行结果如下:

请输入根结点内容:
前序遍历:
[1, A]-[2, B]-[3, D]-[4, E]-[5, C]-[6, F]-[7, G]-
中序遍历:
[2, B]-[3, D]-[4, E]-[1, A]-[5, C]-[6, F]-[7, G]-
后序遍历:
[2, B]-[3, D]-[4, E]-[5, C]-[6, F]-[7, G]-[1, A]-
层序遍历:
[1, A]-[2, B]-[5, C]-[3, D]-[4, E]-[6, F]-[7, G]-
Process returned 0 (0x0)   execution time : 0.042 s
Press any key to continue.

今天我们整理了树和二叉树的一些知识点,明天我们将把剩下的关于树的知识点整理完,我们下次见

紫薇星上的数据结构(7)相关推荐

  1. 紫薇星上的数据结构(1)

    部分观点与数据来自https://study.163.com/与https://www.baidu.com/ 今天开一个新系列:数据结构,其实也是填之前的坑,第一次写紫薇星系列的时候就说过会开数据结构 ...

  2. 紫薇星上的数据结构(10)

    终于来到最后一部分了,算法,这篇文章的出现也意味着这个系列就结束了,向着最后的胜利冲冲冲! 算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法 ...

  3. 紫薇星上的Java——映射转换

    在之前我们有讲过一节引用传递,当我们了解引用传递后就可以在实际开发中运用到它,那今天我们就来实践一下叭! 1.数据表与简单Java类映射转换 简单Java类是现在面向对象设计的主要分析基础,但对于死机 ...

  4. 自从我上了数据结构课之后……

    在 Reddit 上看到一个英文帖子,问:上了数据结构课后,还有正常生活么? 有人引用了 Quora 上的一个英文回答,大意如下: 嗯,没有!你看东西的眼光,不可能和以前一样了.不管你信不信?反正我是 ...

  5. 实时获取滚动条的高度_适用于星上快速处理的雷达高度计有效波高反演技术

    卫星雷达高度计的主要观测参数之一为有效波高,数据产品中其数值的确定通过波形重跟踪来实现(Retracking),波形重跟踪的实现主要通过最大似然迭代完成,其主要的反演公示为W(t) = PFS(t)* ...

  6. 将图像DN值定标维热辐射强度之后,可用Planck函数求解出星上亮度温度

    将图像DN值定标维热辐射强度之后, 可用Planck函数求解出星上亮度温度, 计算公式如下: T i = K i 2 / l n ( 1 + K i 1 /I i ) 式中, K i 1和 K i 2 ...

  7. 星上维智能科技CEO左作人,机器视觉会越来越智能化

    机器视觉的发展并非单一的应用.机器视觉技术使机器具有感知外界的眼睛,使机器具有与人类相同的视觉功能,从而实现各种检测,判断,识别和测量功能.现在机器视觉的软硬件产品逐渐演变为产品生产和制造各阶段的重要 ...

  8. 人造地球卫星轨道外推Matlab,一种适用于圆轨道卫星的星上自主轨道外推方法

    一种适用于圆轨道卫星的星上自主轨道外推方法 [专利摘要]本发明公开了一种适用于圆轨道卫星的星上自主轨道外推方法,其利用地面注入的平根数,采用简化的外推模型,由星载计算机轨道处理模块根据该简化的外推模型 ...

  9. 调频连续波(FMCW)SAR星上实时处理调研

    目录 1. 调频连续波(FMCW)SAR 1.1 FMCW SAR简介 1.2 FMCW SAR的典型应用 2. 实时信号处理 2.1 SAR实时信号处理国内外发展现状 2.2 SAR信号实时处理 2 ...

最新文章

  1. Block 底层值__Block修饰符
  2. Linux下修复修改profile文件导致命令不用可的解决方法
  3. 利用Charles抓https包
  4. 三国轶事——巴蜀之危
  5. git c#,子文件的添加
  6. Mysql学习总结(84)—— Mysql的主从复制延迟问题总结
  7. [快速入门]Spring Boot+springfox-swagger2 之RESTful API自动生成和测试
  8. archlinux安装个简单桌面icewm
  9. java poi 只能创建?,Java POI使用SS模型创建新的工作簿?
  10. github视频教程-02 建立项目仓库以及代码上传
  11. Windows 系统中 hosts 文件无法修改的问题
  12. 计算机图形学 全局光照及方法,实时全局光照渲染研究
  13. python3 荣誉证书(奖状)批量打印
  14. win10 LTSC无损升级 win11专业版 记录
  15. 微信小程序开发前准备
  16. 京东百万年薪大佬用JAVA绘制“五子棋棋盘”(附代码)
  17. LiveGBS接入LiveQing流媒体服务实现云端录像和大屏展示
  18. 在 Mac 山猫 10.8 中从代码编译安装 vim
  19. 大龄计算机考研 考研帮,以自己的亲身经历,献给那些大龄的考研朋友们,加油!...
  20. 简直无敌!5年crud经验,全网独家首发!

热门文章

  1. 【MySQL】函数提取字符串中的数字
  2. powerbuilder操作excel命令大全
  3. C++病毒-----------混乱鼠标
  4. 百度地图墨卡托坐标转高德经纬度坐标(偏移小)
  5. img标签插入图片的方法
  6. bootstrap table获取表格数据方式
  7. Spark的宽窄依赖
  8. 【360补天计划】记第一次漏洞提交
  9. 每日一个小技巧:今天告诉你拍照识别文字的软件有哪些
  10. webUploader上传demo