树形结构是一类重要的非线性数据结构。其中以树和二叉树最为常用,直观来看,树是以分支关系定义的层次结构。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可以用树来形象表示。树在计算机领域也得到广泛应用,如在编译程序中,可用树来表示源程序的语法结构。又如在数据库系统中,树形结构也是信息的重要组织形式之一。
树(Tree)是n(n>=0)个结点的有限集合。在任意一棵非空树中:(1)有且仅有一个特定的称为根(Root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,…Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)
树的结构必须包含以下 三点性质:
1、除了根节点外,每个结点有且仅有一个父节点
2、子树是不相交的
3、一棵有N个结点的树有N-1条边
1、子树相交,不是树
2、结点有两个父节点,不是树
3、结点和边的个数相同,不是树
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的度为3,B的度为3,C的度为2
叶节点或终端节点:度为0的节点称为叶节点;如上图J,K,L,M,N
非终端节点或分支节点:度不为0的节点;如上图A,B,C,D
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;如上图B,C,D的父节点为A。
K的父节点为E
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;如A的子节点有B,C,D。G的子节点有L。H的子节点有MN。
兄弟节点:具有相同父节点的节点互称为兄弟节点;如上图,B,C,D的兄弟结点,他们的父节点都为A
树的度:一棵树中,最大的节点的度称为树的度;如上图树的度为3
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4;
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图K,L互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>0)棵互不相交的树的集合称为森林;
树结构不是线性结构,要存储表示起来就比较麻烦了,实际中树有很多种表示方式,如:双亲表示法,孩子表示法、孩子兄弟表示法等等。
1、孩子兄弟表示法
typedef int DataType;
typedef struct CBTNode
{
struct CBTNode * _Child; //表示孩子
struct CBTNode * _Brother; //表示兄弟
DataType _data; //表示存的数据
}CBTNode;
typedef int DataType;
#define MAX_TREE_SIZE 100
typedef struct PTNode
{
DataType _data; //结点的数据
int _parent; //该结点父节点的位置
}PTNode;
typedef struct Tree
{
PTNode nodes[MAX_TREE_SIZE];
int _r; //根的位置
int _n; //有效结点数
}
typedef int DataType;
#define MAX_TREE_SIZE 100
typedef struct CTNode
{
int _child;
struct CTNode *next;
}CTNode;
typedef struct CTBox
{
DataType _data;
struct CTNode *child;
}
typedef struct CTree
{
CTBox nodes[MAX_TREE_SIZE];
int _n;
int _r;
}
二叉树(Binary Tree)是一种树形结构,它的特点是每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒。
1、空二叉树
2、仅有根节点的二叉树
3、右子树为空的二叉树
4、左子树为空的二叉树
5、左、右子树均为非空的二叉树
一棵深度为k且有2^k -1个结点的二叉树称为满二叉树
3、完全二叉树
完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
性质1:在二叉树的第i层至多有2^(i-1)个结点(i>=1)
性质2:深度为k的二叉树的最大结点数为2^k-1个(满二叉树)
性质3:对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则有n0 = n2 + 1;
证明:设n1为二叉树中度为1的结点的个数,则有
n = n0 + n1 + n2 (1)
设B为边数,利用树的性质可知
n = B + 1 (2)
又因为分支是由度为1和度为2的结点射出的,所以有
B = n0 + 2n2 (3)
由(2)(3)得 n = n1 + 2n2 + 1 (4)
再由(1)(4)得 n0 = n2 + 1
性质4:具有n个结点的完全二叉树的深度为└ log2n┘+1
性质5:对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对 于序号为i的结点有:
- 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
- 若2i+1
=n否则无左孩子 - 若2i+2
=n否则无右孩子
在二叉树的一些应用中,常常需求在树中查找具有某种特征的结点,或者对树中全部结点逐一进行某种处理,这就提出了一个遍历二叉树的问题,即如何按某条搜索路径巡防树中的每一个结点,使得每个结点均被访问一次,而且仅被访问一次。“访问”的含义很广,可以是对结点作各种处理,如输出结点的信息等。
回顾二叉树的递归定义,二叉树是由3种基本单元组成:根节点,左子树,右子树。因此,若能依次遍历这三部分,便是遍历了整个二叉树。
遍历算法:
先序遍历二叉树
若二叉树为空,则空操作;否则
1、访问根节点
2、先遍历左子树
3、先序遍历右子树
中序遍历二叉树
若二叉树为空,则空操作;否则
1、中序遍历左子树
2、访问根节点
3、中序遍历右子树
后序遍历二叉树
若二叉树为空,则空操作;否则
1、后序遍历左子树
2、后序遍历右子树
3、访问根节点
层次遍历
依次从跟结点从左往右访问结点
总结
1、该先序中序后序都是以根节点的访问次序为标准
2、遍历二叉树的过程也是递归的一个过程
应用实例
分别用三种遍历二叉树的算法写出该树的遍历顺序
1、先序遍历:ABDEGHCF
2、中序遍历:DBGEHAFC
3、后序遍历:DGHEBFCA
4、层次遍历:ABCDEFGH
遍历算法代码比较简单,在下文我们会给出
该树的存储在顺序表中的结构是
char str[] = “ABD##EG##H##CF###”; (#表示空)
typedef char DataType;
typedef struct BNode
{
DataType _data;
struct BNode* _left;
struct BNode* _right;
}Node;
typedef struct BTree
{
//二叉树的根节点
Node* _root;
}BTree;
//创建二叉树(以先序遍历的数组为例)
//ABD##EG##H##CF###
//数组名,数组索引(为指针的目的是让函数在调用递归时索引的改变起全局作用)
Node* CreatBinaryTree(DataType arr[], int* idx)
{
if (arr[*idx] == '#')
{
(*idx)++;
return NULL; //空结点
}
//当前树的根节点
Node* root = (Node*)malloc(sizeof(Node));
if (root != NULL)
{
root->_data = arr[*idx];
}
else
return NULL;
(*idx)++;
root->_left = CreatBinaryTree(arr, idx);
root->_right = CreatBinaryTree(arr, idx);
return root;
}
先序遍历
void PreOrder(Node* root)
{
if (root != NULL)
{
//根
printf("%c ", root->_data);
//左子树
PreOrder(root->_left);
//右子树
PreOrder(root->_right);
}
}
void InOrder(Node* root)
{
if (root != NULL)
{
//左子树
InOrder(root->_left);
//根
printf("%c ", root->_data);
//右子树
InOrder(root->_right);
}
}
3、后序遍历
void PostOrder(Node* root)
{
if (root != NULL)
{
//左子树
PostOrder(root->_left);
//右子树
PostOrder(root->_right);
//根
printf("%c ", root->_data);
}
}
int BTreeSize(Node* root)
{
if (root == NULL)
{
return 0;
}
return BTreeSize(root->_left) //左子树结点数 + 右子树结点数+ 当前节点
+ BTreeSize(root->_right) + 1;
}
求二叉树的高度,我们只要求左右子树的最大高度在加上根节点1就可以了得出树的高度
//求二叉树的高度
int BTreeHigh(Node* root)
{
if (root == NULL)
{
return 0;
}
int left = BTreeHigh(root->_left);
int right = BTreeHigh(root->_right);
//返回该结点的左右孩子的度的最大值
return left > right ? left + 1 : right + 1;
}
左右子树叶子结点和
1、空结点 0个叶子结点
2、没有左右孩子的结点 1个叶子结点
3、非叶子结点->递归(左右子树叶子的和)
int BTreeLeafSize(Node* root)
{
//空树
if (root == NULL)
{
return 0;
}
//叶子结点
if (root->_left == NULL
&& root->_right == NULL)
{
return 1;
}
//非叶子结点
return BTreeLeafSize(root->_left)
+ BTreeLeafSize(root->_right);
}
第k层的结点个数 = 左右子树第k-1层的和
//第k层的结点
int BTreeKSize(Node* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BTreeKSize(root->_left, k - 1)
+ BTreeKSize(root->_right, k - 1);
}
//查找二叉树树中的结点
Node* BTreeFind(Node* root, DataType val)
{
if (root != NULL)
{
if (root->_data == val)
{
return root;
}
Node* node = BTreeFind(root->_left, val);
if (node)
{
return node;
}
else
return BTreeFind(root->_right, val);
}
else
{
return NULL;
}
}
//销毁二叉树
void BTreeDestory(Node** root)
{
if (*root == NULL)
{
return;
}
else
{
BTreeDestory(& ( (*root)->_left) );
BTreeDestory(& ( (*root)->_right)) ;
free(*root);
*root = NULL;
}
}
这里要实现层序遍历,我们不能再用递归的方式去实现。我们可以借助一个队列,先将根节点入队。然后再将根节点出队的同时,将根节点的左孩子和右孩子依次入队,注意,这里只能左孩子先入队,右孩子才能入队。当然,这里是在孩子存在的情况下,如果孩子为空,就不入队。然后出队头元素,在出的同时将队头元素的左右孩子依次入队,依次循环,直到队列为空为止
void BinaryTreeLevelOrder(Node* root)
{
//队列
Queue q;
QueueInit(&q);
//将根节点先入队
if (root)
QueuePush(&q, root);
//只要队列不为空就继续循环
while (!QueueEmpty(&q))
{
//获取队头元素
Node* front = QueueFront(&q);
//出队
QueuePop(&q);
printf("%c ", front->_data);
//左孩子非空,入队
if (front->_left)
QueuePush(&q, front->_left);
//右孩子非空,入队
if (front->_right)
QueuePush(&q, front->_right);
}
}
要判断树是否为二叉树,其实跟层序遍历是相似的,我们知道,完全二叉树是连续的结构,层序遍历时,如果遇到空结点,那么它之后的结点就必须为空,否则不是完全二叉树,读者可自行画图。这里直接上代码
int BinaryTreeComplete(Node* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
Node* front = QueueFront(&q);
QueuePop(&q);
if (front)
{
QueuePush(&q, front->_left);
QueuePush(&q, front->_right);
}
else
{ //遇到空结点,结束第一个循环,进入第二个循环去判断后面是否全为空结点
break;
}
}
while (!QueueEmpty(&q))
{
Node* front = QueueFront(&q);
QueuePop(&q);
//有结点不为空,不是完全二叉树
if (front)
return 0;
}
return 1;
}
操作接口
#pragma once
#include
#include
typedef char DataType;
typedef struct BNode
{
DataType _data;
struct BNode* _left;
struct BNode* _right;
}Node;
//创建二叉树
Node* CreatBinaryTree(DataType arr[], int* idx);
//先序遍历
void PreOrder(Node* root);
//中序遍历
void InOrder(Node* root);
//后序遍历
void PostOrder(Node* root);
//求二叉树结点的个数
int BTreeSize(Node* root);
//求二叉树的高度
int BTreeHigh(Node* root);
//叶子结点
int BTreeLeafSize(Node* root);
//第k层的结点
int BTreeKSize(Node* root, int k);
//查找二叉树中的结点
Node* BTreeFind(Node* root, DataType val);
//销毁二叉树
void BTreeDestory(Node** root);
//层序遍历二叉树
void BinaryTreeLevelOrder(Node* root);
//判断树是否为二叉树
int BinaryTreeComplete(Node* root);
操作接口的实现
#include"btree.h"
//数组名,数组索引(为指针的目的是让函数在调用递归时索引的改变起全局作用)
Node* CreatBinaryTree(DataType arr[], int* idx)
{
if (arr[*idx] == '#')
{
(*idx)++;
return NULL; //空结点
}
//当前树的根节点
Node* root = (Node*)malloc(sizeof(Node));
if (root != NULL)
{
root->_data = arr[*idx];
}
else
return NULL;
(*idx)++;
root->_left = CreatBinaryTree(arr, idx);
root->_right = CreatBinaryTree(arr, idx);
return root;
}
void PreOrder(Node* root)
{
if (root != NULL)
{
//根
printf("%c ", root->_data);
//左子树
PreOrder(root->_left);
//右子树
PreOrder(root->_right);
}
}
void InOrder(Node* root)
{
if (root != NULL)
{
//左子树
InOrder(root->_left);
//根
printf("%c ", root->_data);
//右子树
InOrder(root->_right);
}
}
void PostOrder(Node* root)
{
if (root != NULL)
{
//左子树
PostOrder(root->_left);
//右子树
PostOrder(root->_right);
//根
printf("%c ", root->_data);
}
}
int BTreeSize(Node* root)
{
if (root == NULL)
{
return 0;
}
return BTreeSize(root->_left) //左子树结点数 + 右子树结点数+ 当前节点
+ BTreeSize(root->_right) + 1;
}
int BTreeHigh(Node* root)
{
if (root == NULL)
{
return 0;
}
int left = BTreeHigh(root->_left);
int right = BTreeHigh(root->_right);
//返回该结点的左右孩子的度的最大值
return left > right ? left + 1 : right + 1;
}
int BTreeLeafSize(Node* root)
{
//空树
if (root == NULL)
{
return 0;
}
//叶子结点
if (root->_left == NULL
&& root->_right == NULL)
{
return 1;
}
//非叶子结点
return BTreeLeafSize(root->_left)
+ BTreeLeafSize(root->_right);
}
int BTreeKSize(Node* root, int k)
{
if (root == NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BTreeKSize(root->_left, k - 1)
+ BTreeKSize(root->_right, k - 1);
}
Node* BTreeFind(Node* root, DataType val)
{
if (root != NULL)
{
if (root->_data == val)
{
return root;
}
Node* node = BTreeFind(root->_left, val);
if (node)
{
return node;
}
else
return BTreeFind(root->_right, val);
}
else
{
return NULL;
}
}
void BTreeDestory(Node** root)
{
if (*root == NULL)
{
return;
}
else
{
BTreeDestory(& ( (*root)->_left) );
BTreeDestory(& ( (*root)->_right)) ;
free(*root);
*root = NULL;
}
}
void BinaryTreeLevelOrder(Node* root)
{
//队列
Queue q;
QueueInit(&q);
//将根节点先入队
if (root)
QueuePush(&q, root);
//只要队列不为空就继续循环
while (!QueueEmpty(&q))
{
//获取队头元素
Node* front = QueueFront(&q);
//出队
QueuePop(&q);
printf("%c ", front->_data);
//左孩子非空,入队
if (front->_left)
QueuePush(&q, front->_left);
//右孩子非空,入队
if (front->_right)
QueuePush(&q, front->_right);
}
}
int BinaryTreeComplete(Node* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
Node* front = QueueFront(&q);
QueuePop(&q);
if (front)
{
QueuePush(&q, front->_left);
QueuePush(&q, front->_right);
}
else
{ //遇到空结点,结束第一个循环,进入第二个循环去判断后面是否全为空结点
break;
}
}
while (!QueueEmpty(&q))
{
Node* front = QueueFront(&q);
QueuePop(&q);
//有结点不为空,不是完全二叉树
if (front)
return 0;
}
return 1;
}