树的相关知识
树型结构是一类重要的非线性数据结构。其中以树和二叉树最为常用,直观看来,树是以分支关系定义的层次结构。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构可用树来形象表示。
树的定义
树是n(n ≥ 0 )个结点的有限集。
若n = 0 ,称为空树;
若n>0,则它满足如下两个条件:
(1).有且仅有一个特定的称为根的结点;
(2)其余结点可分为m(m ≥ 0 )个互不相交的有限集T1,T2,T3,…,Tm,其中每一个集合本身又是一棵树,并称为根的子树。
显然,树的定义是一个递归的定义。
树一般表示为一个层次结构,如下图:
树的相关术语
结点 :数据元素以及指向子树的分支。图中的A,B,C等都是结点。
根结点 :非空树中无前驱结点的结点。图中的A结点。
结点的度 :结点拥有的子树数。图中A结点有3个子树,其度为3。
树的度 :树内各结点的度的最大值。上图中树的度为3。
叶子结点(终端结点) :度为0的结点。图中的F,I,J等都是叶子结点。
分支结点(非终端结点) :度不为0的结点。A,B,C,D等都是分支结点。
内部结点(中间结点) :根结点以外的分支结点。B,C,D等都是内部结点。
孩子 结点的子树的根称为该结点的孩子,该结点称为孩子的双亲。图中,E是L的双亲,L是E的孩子。
兄弟节点 有一些结点,它们有共同的双亲,则称这些结点为兄弟结点。图中的H,I,J就是兄弟结点。
双亲在同一层上的结点称为 堂兄弟结点 。图中的G,H就是堂兄弟结点。
结点的祖先 :从根到该结点所经分支上的所有的结点。A,D,H结点都是结点M的祖先。
结点的子孙 :以某结点为根的子树中的任一结点。
树的深度 :树中结点的最大层次。图中的树的深度为4。
有序树 :树中结点的各子树从左至右右次序。
无序树 :树中结点的各子树无次序。
森林 :是m(m ≥ 0)棵互不相交的树的集合。把根结点删除树就变成了森林;一棵树可以看成是一个特殊的森林;给森林中的各子树加上一个双亲结点,森林就变成了树。
树一定是森林,而森林不一定是树。
树与线性结构的比较
线性结构 | 树型结构 |
---|---|
第一个数据元素(无前驱) | 根结点(无双亲,只有一个) |
最后一个数据元素(无后继) | 叶子结点(无孩子,可以有多个) |
其他数据元素:一个前驱,一个后继 | 其它结点(中间结点):一个双亲,多个孩子 |
一对一 | 一对多 |
树的存储结构
双亲表示法
采用的一组连续的存储空间来存储每个节点。
根节点没有双亲,所以其在数组中存储的值为-1。
其余的节点,只需要存储其父节点对应的数组下标即可。
看图,一目了然。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
元素 | A | B | C | D | E | F | G | H | I | J | K | L | M |
父节点 | -1 | 0 | 0 | 0 | 1 | 1 | 2 | 3 | 3 | 3 | 4 | 4 | 7 |
代码
// 双亲表示法
#define MAX_SIZE 100
typedef int ElemType;
typedef struct PTNode{
ElemType data;//结点元素
int parent;//存储父节点下标
}PTNode;
typedef struct PTree
{
PTNode nodes[MAX_SIZE];//创建树
int n;
}PTree;
孩子表示法
将每个节点的孩子节点都用单链表连接起来形成一个线性结构,n个节点具有n个孩子链表。
代码
// 孩子表示法
typedef int ElemType;
#define MAX_SIZE 100
typedef struct CNode
{
int child;
struct CNode *next;
}CNode;
typedef struct PNode
{
ElemType data;
struct CNode *child;
}PNode;
typedef struct CTree
{
PNode nodes[MAX_SIZE];
int n;
}CTree;
孩子兄弟表示法
以二叉链表作为树的存储结构,又称二叉树表示法。
指针域 | 数据域 | 指针域 |
---|---|---|
FirstChild | Data | NextBrother |
结点第一个孩子 | 结点值 | 结点的下一个兄弟 |
二叉树
二叉树的特点:
(1)每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
(2)二叉树的子树有左右之分,其子树的次序不能颠倒。
二叉树的性质
二叉树具有以下几个性质:
1、二叉树中,第 i 层最多有 2^i-1 个结点。
2、如果二叉树的深度为 K,那么此二叉树最多有 2^K-1 个结点。
3、二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1 。
4、有n个结点的完全二叉树,对各节点从上到下、从左到右依次编号(1~n),则结点之间有如下关系。
若i为某结点a的编号。则a左孩子的编号为2i,右孩子编号为2i+1
满二叉树
如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。
性质:
1、满二叉树中第 i 层的节点数为 2n-1 个。
2、深度为 k 的满二叉树必有 2^k-1 个节点 ,叶子数为 2^(k-1)。
3、满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。
4、具有 n 个节点的满二叉树的深度为 log2(n+1)。
完全二叉树
如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树
二叉树的存储方式
孩子表示法
struct node{
char value;//结点元素的值
int left,right;//左孩子下标,右孩子下标
}data[101];
int root=0;//根结点下标
int cnt=0;//下标
二叉树的建立
//注:空结点用字符'.'填充
//方法1
int buildTree(int bt)//输入一个数,就采用先序的方式添加进二叉树中。
{
char ch;
cin>>ch;
if(ch=='.'){
bt=0;
return bt;
}
else{
bt=++cnt;
data[bt].value=ch;
data[bt].left=0;
data[bt].right=0;
data[bt].left=buildTree(bt);
data[bt].right=buildTree(bt);
}
return bt;
}
//方法2
int buildTree2(int bt)
{
char ch;
cin>>ch;
if(ch=='.')return 0;
data[bt].value=ch;
data[bt].left=bt*2;
data[bt].right=bt*2+1;
buildTree2(data[bt].left);
buildTree2(data[bt].right);
return 0;
}
二叉树的四种遍历方式
前序遍历(先序遍历):
先访问一棵树的根节点,再访问左子树,最后访问右子树。
遍历序列:ABDHIEJKCFG
void preorder(int bt)
{
if(bt)
{
cout<<data[bt].value;//访问节点元素
preorder(data[bt].left);//遍历左子树
preorder(data[bt].right);//遍历右子树
}
}
中序遍历:
先访问一棵树的左子树,再访问根节点,最后访问右子树。
遍历序列:HDIBJEKAFCG
void inorder(int bt)
{
if(bt)
{
inorder(data[bt].left);//遍历左子树
cout<<data[bt].value;//访问节点元素
inorder(data[bt].right);//遍历右子树
}
}
后序遍历:
先访问一棵树的左子树,再访问右子树,最后访问根节点。
遍历序列:HIDJKEBFGCA
void postorder(int bt)
{
if(bt)
{
postorder(data[bt].left);//遍历左子树
postorder(data[bt].right);//遍历右子树
cout<<data[bt].value;//访问节点元素
}
}
层序遍历:
首先访问第一层的根结点,然后从左到右访问第2层上的节点,接着访问第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
遍历序列:ABCDEFGHIGK
//层次遍历需要用到队列
void levelorder(int bt)
{
queue<int> q;
q.push(bt);
while(!q.empty())
{
int t=q.front();
cout<<data[t].value;
if(data[t].left!=0) q.push(data[t].left);
if(data[t].right!=0) q.push(data[t].right);
q.pop();
}
}