树的存储结构:
(1)双亲表示法:
#define Maxsize 100
typedef struct Node{ //树结点定义
ElementType data; //数据
int parent; //双亲位置标记
}TreeNode;
struct Tree{
TreeNode nodes[ Maxsize ]; //结点数组
int n; //结点个数
};
特点:找双亲结点容易,找孩子结点不方便(需要遍历整个树找到双亲标记是所要查找的那个结点)。
(2)孩子表示法:
每个结点的孩子结点都用单链表链接起来形成一个线性结构,然后把所有孩子链表的头指针放在一个指针数组里。
特点:找孩子结点很方便,但找双亲结点需要遍历n个指针所指向的n条孩子链表(直到找到某条链表里面包含查找的孩子结点,就返回这条孩子链表的双亲结点)。
如果要改善的话,可以在给指针数组每一个头指针增加一个双亲标志,就像双亲表示法当中的做法一样。
(3)孩子兄弟表示法(二叉树表示法):
typedef struct TreeNode{
ElementType data; //数据域
struct TreeNode* firstchild; //指向第一个孩子的指针
struct TreeNode* nextsibling; //指向右兄弟的指针
}TNode;
树、二叉树、森林之间的相互转换:
(1)树转二叉树:
“兄弟相连留长子”——把每个结点的指向孩子结点的边抹去(除了左边第一个孩子结点(即长子),然后把这些孩子结点依次相连),最后绕树根顺时针旋转45度。
(2)森林转二叉树:
“树变二叉根相连”——把森林中的每棵树都先转化为二叉树然后依次把这些树的根结点相连。
(3)二叉树转森林:
“去掉所有右孩线,孤立二叉再还原”——从根结点开始沿右子树把最右边一条线上的所有边去掉,然后再把这些孤立的二叉树还原成树。
(4)二叉树转树:
“左孩右右连双亲,去掉所有右孩线”——从根结点开始沿右子树把最右边一条线上的所有边去掉,然后把这些子树的根结点与根结点相连。
树和森林的遍历:
树:
(1)先根遍历;
(2)后根遍历;
(3)之所以没有中根遍历是因为树结点的度可能大于2,此时没有办法确定什么是中。
森林:
(1)先序遍历;
(2)中序遍历。
树的应用——并查集:
集合运算:交、并、补、差,判定一个元素属于哪一个集合。
并查集:集合并,判断元素属于哪一个集合。
存储方式:
树的双亲表示法。
操作集即实现:
//并查集
#include <stdio.h>
#define ElementType int
#define Maxsize 10
typedef struct{
ElementType data;
int parent;
}SetType;
SetType s[Maxsize];
void Init( SetType s[] ); //初始化全集合,即让每个元素自成一个单元素子集合
int find( SetType s[], ElementType x ); //查找元素所在的子集合
void Union( SetType s[], ElementType x1, ElementType x2 ); //集合的并运算
int main ()
{
}
void Init( SetType s[] )
{
for( int i; i < Maxsize; i++ )
s[i].parent = -1;
}
int find( SetType s[], ElementType x )
{
int i;
for( i = 0; i < Maxsize && s[i].data != x; i++ )
;
if( i >= Maxsize )
return -1;
for( ; s[i].parent >= 0; i = s[i].parent )
;
return i;
}
void Union( SetType s[], ElementType x1, ElementType x2 )
{
int r1, r2;
r1 = find( s, x1 );
r2 = find( s, x2 );
if( s[r1].parent < s[r2].parent ) //尽量把小集合并入大集合
s[r2].parent = r1;
else if( s[r1].parent > s[r2].parent )
s[r1].parent = r2;
}
暂时没有遇到好的可以用来检验的例子,以后来补。
这里面注意尽量把小集合并到大集合是为了防止树的高度太大导致find查找函数的效率降低。