1.树的定义:n(n>=0)个结点的有限集。n=0是称为空树。在任意一颗非空树中:(1)有且仅有一个特定的称为根(Root)的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集 T1,T2,...,Tm,其中每一个集合本身又是一棵树,并且称为根的子树。
2.结点分类:(1)结点拥有的子树数目称为结点的度,这棵树的结点的度的最大值也就是树的度。度为0的结点称为Leaf,即叶结点或终端结点。除了叶节点和终端节点的其余都称为内部结点或分支结点。
(2)结点的子树的根节点称为该结点的child,该结点则是其child结点的parent,同一parent的结点之间称为sibling,结点的祖先是从根到该节点所经过分支上的所有结点。
(3)结点的层次:根为第一层,依次往下。树中结点的最大层次称为树的深度或高度。Forest是m(m>=0)颗互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。
3. 树的存储结构
(1)双亲表示法:结点分为数据域和指针域。指针指向parent,parent + firstchild,parent + rightsib均可。
(2)多重链表法,孩子表示法:指针域包含指向其孩子节点的全部指针。而结点的指针域大小可以用树的度来确定,但是这样会在指针域浪费空间。如果在每个结点都包含度域,但由于各个节点的链表是不相同的结构,还要维护结点的度的数值。
另外可以用一个数组来存储所有结点,结点的指针域是firstchild。对每个结点构建一个单链表,这个孩子单链表的结点表示该结点的所有孩子结点的位置。
(3)孩子兄弟法:结点结构包括 data + firstchild + rightsib。
typedef struct CSNode{
TElemType data;
struct CSNode *firstchild, *rightsib;
}CSNode, *CSTree;
这种方法最大好处是它把一颗复杂的树变成了二叉树。
4.二叉树的定义:每个节点最多有两颗子树,二叉树中不存在度大于2的结点。左子树和右子树是有顺序的,次序不能任意颠倒。
5.特殊二叉树:斜树是只有左/右子树的二叉树叫做左/右斜树,线性表结构也可以看做一种斜树的表达式。满二叉树表示所有分支结点都有左右子树,所有叶子都在同一层上的树。完全二叉树表示每个节点按照满二叉树的结构逐层顺序编号,但不一定满。
6.二叉树的性质:
(1)在二叉树的第i层尚至多有2^(i-1)个结点。
(2)深度为k的二叉树至多有2^k -1个结点:2^0+2^1+2^2+....=[1-(2)^k]/(1-2)=2^k -1.
(3)如果其终端节点数为n0,度为2的结点树为n2,度为1的节点数为n1,则n0=n2 +1:
结点总数n = n0+n1+n2;
分支线总数=n-1=n1+2*n2;
所以n0+n1+n2-1=n1+2*n2;
(4)具有n个结点的完全二叉树的深度为[log2 n]+1;
深度为k的满二叉树的节点数为n = 2^k -1; 即k=log2 (n+1);
对于同样深度的完全二叉树,其节点数目满足 2^(k-1)-1<n<=2^k -1; 2^(k-1)<=n<2^k;
k-1<=log2 n<k; k=[log2 n]+1;
(5)对一颗n个结点的完全二叉树,对任意结点:
如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2];
如果2i>n,则结点i为叶子结点;
如果2i+1>n,则结点i无右孩子;
7.二叉树的存储结构:
(1)完全二叉树可以用顺序存储结构严格的表示其逻辑关系。
(2)对于一般二叉树用链表:二叉链表
typedef struct bnode{
elemtype data;
struct bnode *lchild;
struct bnode *rchild;
}bnode;
typedef struct tree_type{
bnode *root;
int num;
}tree_type;
8.二叉树的遍历
(1)先序遍历:根左右。
void preorders(bnode *root, stack_type* stack)
{
while(!(root==NULL && stack->top ==0))
{
if(root!=NULL)
{
printf("%c",root->data);
pushstack(stack,root);
root = root->lchild;
}else
{
popstack(stack,&root);
root = root->rchild;
}
}
}
递归法:
void preorder(bnode *root)
{
if(root == NULL) return;
printf("%c",root->data);
if(root->lchild!=NULL) preorder(root->lchild);
if(root->rchild!=NULL) preorder(root->rchild);
}
(2)中序遍历
void inorder(bnode *root)
{
if(root = NULL) return;
if(root->lchild!=NULL) inorder(root->lchild);
printf("%c",root->data);
if(root->rchild!=NULL) inorder(root->rchild);
}
(3)后序遍历
void postorder(bnode *root)
{
if(root = NULL) return;
if(root->lchild!=NULL) postorder(root->lchild);
if(root->rchild!=NULL) postorder(root->rchild);
printf("%c",root->data);
}
已知前序和后序遍历是无法确定一颗二叉树的。
9.二叉树的建立
void CreateTree(*root)
{
TElemType data;
scanf("%c",&data);
if(data = '#') root = NULL;
else
{
root = (bNode *) malloc(sizeof(bNode));
if(!root) exit(OVERFLOW);
root->data = data;
CreatTree( & root->lchild );
CreatTree( & root->rchild);
}
}
10.线索二叉树
(1)n个结点的链表一共有2n个指针域,n-1个分支数。总共存在2n-(n-1)=n+1个空指针域。我们希望把这些空指针域利用起来存储在遍历二叉树时的前驱和后继关系。这种指向前驱和后继的指针称为线索,加上线索的二叉树称为线索二叉树。
(2)中序遍历线索化:
bnode *pre;//全局变量,指向刚刚访问过的结点
void InThreading(bnode *p)
{
if(p)
{
InThreading(p->lchild);
if(!p->lchild) { p->LTag = Thread; p->lchild = pre;}
if(!pre->rchild) { pre->RTag = Thread; pre->rchild = p;}
pre = p;
InThreading(p->rchild);
}
}
11.二叉排序树
父节点的元素值大于左子树上的任何结点的元素值,小于右子树上任何结点的元素值。当我们以中序遍历顺序遍历这颗二叉树时,获得的是从低到高的排序结果!
int insert(elemtype V)
{
bnode *ptr, *pav;
if(root==NULL){ root = (bnode*)malloc(sizeof(bnode));
root->data = V;
root->lchild = root->rchild = NULL;}
else{
ptr = root;
while(V!=ptr->data)
{
if((V<ptr->data)&&(ptr->lchild!=NULL)) ptr = ptr->lchild;
else if((V>ptr->data)&&(ptr->rchild!=NULL)) ptr = ptr->rchild;
else if(V<ptr->data)&&(ptr->lchild ==NULL))
{
pav = (bnode*)malloc(sizeof(bnode));
ptr->lchild = pav;
pav->data=V;
pav->lchild = pav->rchild = NULL;
return 0;
}
else {
pav=(bnode*)malloc((sizeof(bnode)));
ptr->rchild = pav;
pav->data = V;
pav->lchild = pav->rchild =NULL;
return 0;
}
}
return 1;
}
}
12.树的二叉表示:A.在所有兄弟结点之间加一条连线。B.对树中每个节点只保留与第一个孩子的连线,删除它与其他孩子结点的连线。C.最后调整层次即可。
13.森林转化为二叉树:A。所有树先转化为二叉树。B.依次把后一颗树最为前一棵树的右孩子。
14.赫夫曼树:加权路长之和最小的扩充二叉树。