文章目录
1.导言
2.思维导图
3.树的基本概念
结点的度与树的度
-
结点的度:结点拥有的子树数目称为结点的度,叶子结点 就是度为0的结点
-
树的度:树内各结点的度的最大值
-
分支结点:度不为0的结点
-
双亲结点:树中某节点有孩子结点,则这个结点称为它孩子节点的双亲结点,双亲结点也成为前驱结点
-
孩子结点:树中一个结点的子树的根节点称为该结点的孩子结点,孩子结点也称为后继结点
-
兄弟结点:具有相同双亲结点的结点称为兄弟结点
——————————————————————————— -
结点数n=n0+n1+n2, ( n0:度为0的结点数,n1:度为1的结点 n2:度为2的结点数。 n是总结点)
-
非空二叉树,n0=n2+1;
-
当结点数n为奇数,无度为1的结点;结点n为偶数,有一个度为1的结点;
——————————————————————————— -
分支数=n-1 =1n1+ 2n2+3n3
n0+n1+n2+n3 = n = 分支数+1 = 1n1+ 2n2+3n3+1
树的深度与高度
-
结点 ni 的深度:从根结点到 ni 的的唯一路径长。即,结点 ni 所在的层次(根结点为0层),树的深度 = 树中结点的最大层次。
-
结点 ni 的高度:从 ni 到一片树叶的最长路径长。即,叶子结点的高度为0,树的高度 = 根的高度。
-
树的深度 = 树的高度
-
高度为h的二叉树至少2^h 个节点,至多有2^(h+1)-1 个结点。
-
含有n≥1 个结点的二叉树的高度范围:[ | log2 n」,n-1]
树的存储结构
双亲表示法,孩子表示法,双亲孩子表示法,孩子兄弟表示法
- 双亲表示法:用指针表示出每个节点的双亲节点
typedef int DataType;
struct Node{
struct Node* Parent;//指向双亲节点指针域
DataType data;//节点中的数据
};
优点:寻找一个节点得双亲节点操作实现很方便
缺点:寻找一个节点的孩子节点很不方便
- 孩子表示法: 用指针指出每个节点的孩子节点
typedef int DataType;
struct Node{
struct Node* Child1;
struct Node* Child2;
struct Node* Child3;
DataType data;
};
优点:寻找一个节点的孩子节点比较方便
缺点:寻找一个节点得双亲节点很不方便
- 双亲孩子表示法:用指针既表示出每个节点得双亲节点,也表示出每个节点的孩子节点,即双亲表示法+孩子表示法
typedef int DataType;
struct Node{
struct Node* Parent;
struct Node* Child1;
struct Node* Child2;
struct Node* Child3;
DataType data;
};
优点:找某个节点的双亲节点和孩子节点非常方便
- 孩子兄弟表示法:即表示出每个节点的第一个孩子节点,也表示出每个节点的下一个兄弟节点。
typedef int DataType;
struct Node{
struct Node* Child1;//第一个孩子节点
struct Node* NextBrother;//指向其下一个兄弟节点
DataType data;//节点中的数据域
};
4.二叉树
二叉树的存储结构
- 顺序存储结构:对于一棵完全二叉树所有节点按照层序自顶向下,同一层自左向右顺序编号,就得到一个节点的顺序序列。
优点:存储完全二叉树,简单省空间
缺点:存储一般二叉树尤其单支树,存储空间利用不高。
- 链式存储:
struct BinTreeNode{
struct BinTreeNode* LChild;//当前节点左子树
struct BinTreeNode* RChild;//当前节点右子树
DataType data;//当前节点值域
};
struct BinTreeNode{
struct BinTreeNode* Parent;//当前节点的双亲
struct BinTreeNode* LChild;//当前节点左子树
struct BinTreeNode* RChild;//当前节点右子树
DataType data;//当前节点值域
};
满二叉树
一棵深度为k,且有2^k - 1个节点的树是满二叉树。
通俗解释:除叶结点外,每一个结点都有左右子树,且叶结点都处在最底层的二叉树。
- 如果一颗树深度为h,最大层数为k,且深度与最大层数相同,即k=h;
- 它的叶子数是: 2(h-1)
- 第k层的结点数是: 2(k-1)
- 总结点数是: 2k - 1
- 树高:h=log2(n+1)
完全二叉树
完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h 层所有的结点都连续集中在最左边,这就是完全二叉树。
满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。
- 深度为k的完全二叉树,至少有2^(k-1)个节点, 至多有2^k-1个节点。
- 树高h=log2n + 1。
二叉树的遍历
先序遍历
- 访问根结点
- 访问左子树的左子树,再访问左子树中的右子树
- 访问右子树的左子树,再访问右子树中的右子树
- 任意子树输出顺序为:父结点——左子结点——右子结点
void PreOrder(BTNode *b)
{ if(b!=NULL)
{ cout<<b->data;
PreOrder(b->lchild);
PreOrder(b->rchild);
}
}
中序遍历
- 先访问左子树中的左子树,再访问左子树中的右子树
- 访问根结点
- 后访问右子树中的左子树,再访问右子树中的右子树
- 任意子树输出顺序为:左子结点——父结点——右子结点
void InOrder(BTNode *b)
{ if(b!=NULL)
{ PreOrder(b->lchild);
cout<<b->data;
PreOrder(b->rchild);
}
}
后序遍历
- 先访问左子树中的左子树,再访问左子树中的右子树
- 再访问右子树中的左子树,再访问右子树中的右子树
- 访问根结点
- 任意子树输出顺序为:左子结点——右子结点——父结点
void PostOrder(BTNode *b)
{ if(b!=NULL)
{ PreOrder(b->lchild);
PreOrder(b->rchild);
cout<<b->data;
}
}
层次遍历
- 访问根结点,即第1层,设为 i 层
- 访问i+1层的结点,从左到右顺序访问
- 层次遍历输出顺序为:根结点—i 层结点从左到右— i +1层结点从左到右
struct node{
int data;
int layer;
node *lchild;
node *rchild;
};
void layerOrder(node *root)
{
queue<node *> q;
q.push(root);
root->layer = 1;
while(!q.empty())
{
node *now = q.front();
q.pop();
printf("%d ",now->data);
if(now->lchild != NULL)
{
q.push(now->lchild);
now->lchild->layer += 1;
}
if(now->rchild != NULL)
{
q.push(now->rchild);
now->rchild->layer += 1;
}
}
}
根据遍历结果推出树形结构
- 已知先序与中序遍历结果,可以推导出树形结构
- 已知中序与后序遍历结果,可以推导出树形结构
- 已知先序与后序遍历结果,无法推导出树形结构,因为无法判断根结点的之前或之后的结点是属于左子树还是右子树
5.线索二叉树
定义:充分利用二叉链表中的空链域,将遍历过程中结点的前驱、后继信息保存下来。
- 若结点有左子树,则其 lchild 域指向其 左孩子,否则 lchild 域指向其 前驱结点。
- 若结点有右子树,则其 rchild 域指向其 右孩子,否则 rchild 域指向其 后继结点。
- 线索:在这种存储结构中,指向前驱和后继结点的指针叫做线索。
- 线索链表:以这种结构组成的二叉链表作为二叉树的存储结构,叫做线索链表。
- 线索化:对二叉树以某种次序进行遍历并且加上线索的过程叫做线索化。
typedef struct TBTNode
{
char data;
int ltag,rtag;
struct TBTNode *lchild;
struct TBTNode *rchild;
}TBTNode;
二叉树中序线索化:
- (1)建立中序线索二叉树
void InThread(TBTNode *p,TBTNode *&pre)
{
if(p!=NULL)
{
InThread(p->lchild,pre); // 递归,左子树线索化
if(p->lchild==NULL) // 建立当前结点的前驱线索
{
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL) //建立前驱结点的后继线索
{
pre->rchild=p;
pre->rtag=1;
}
pre=p;
InThread(p->rchild,pre); // 递归,右子树线索化
}
}
void createInThread(TBTNode *root)
{
TBTNode *pre=NULL; // 前驱结点指针
if(root!=NULL)
{
InThread(root,pre);
pre->rchild=NULL; // 处理最后一个结点的后继
pre->rtag=1;
}
}
- (2)遍历中序线索二叉树
TBTNode *First(TBTNode *p) // 找到最左下的结点
{
while(p->ltag==0)
p=p->lchild;
return p;
}
TBTNode *Next(TBTNode *p)
{
if(p->rtag==0) // 如果p存在右孩子,则p的后继结点是p的右子树中的最左下结点
return First(p->rchild);
return p;
}
void Inorder(TBTNode *root)
{
for(TBTNode *p=First(root);p!=NULL;p=Next(p))
Visit(p);
}
6.哈夫曼树
定义:设二叉树具有n个带权值的叶节点,那么从根节点到各个叶节点的路径长度与相应节点权值的乘积的和,叫做二叉树的带权路径长度。
-
具有最小带权路径长度的二叉树称为哈夫曼树(也称最优树)。
-
哈夫曼树的构造:
- 权值越大的叶节点越靠近根节点。
- 权值越小的叶节点越远离根节点。
-
过程:
- 1.给定的n个权值{W1,W2,…,Wn }构造n棵只有一个叶节点的二叉树, 从而得到一个二叉树的集合F={T1,T2,…,Tn }。
- 2.在F中选取根节点的权值最小和次小的两棵二叉树作为左、右子树 构造一棵新的二叉树,这棵新的二叉树根节点的权值为其左、右子树根节点 权值之和。
- 3.在集合F中删除作为左、右子树的两棵二叉树,并将新建立的二叉 树加入到集合F中。
- 4.重复2和3两步,当F中只剩下一棵二叉树时,这棵二叉树 便是所要建立的哈夫曼树。
7.平衡二叉树
概念:
- 平衡因子:将二叉树上节点的左子树高度减去右子树高度的值称为该节点的平衡因子BF(Balance Factor)。
- 平衡二叉树就是一棵二叉树上所有节点的平衡因子的绝对值小于1的树
1.LL型调整:
- 由于在A的左孩子(L)的左子树(L)上插入新结点,使原来平衡二叉树变得不平衡,此时A的平衡因子由1增至2。下面图1是LL型的最简单形式。显然,按照大小关系,结点B应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡,A结点就好像是绕结点B顺时针旋转一样。
BTNode *ll_rotate(BTNode *y)
{
BTNode *x = y->left;
y->left = x->right;
x->right = y;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x;
}
- RR型调整:
- 由于在A的右孩子®的右子树®上插入新结点,使原来平衡二叉树变得不平衡,此时A的平衡因子由-1变为-2。图3是RR型的最简单形式。显然,按照大小关系,结点B应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡,A结点就好像是绕结点B逆时针旋转一样。
BTNode *rr_rotate(struct Node *y)
{
BTNode *x = y->right;
y->right = x->left;
x->left = y;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x;
}
3.LR型调整:
- 由于在A的左孩子(L)的右子树®上插入新结点,使原来平衡二叉树变得不平衡,此时A的平衡因子由1变为2。图5是LR型的最简单形式。显然,按照大小关系,结点C应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡。
BTNode* lr_rotate(BTNode* y)
{
BTNode* x = y->left;
y->left = rr_rotate(x);
return ll_rotate(y);
}
- RL型调整:
- 由于在A的右孩子®的左子树(L)上插入新结点,使原来平衡二叉树变得不平衡,此时A的平衡因子由-1变为-2。图7是RL型的最简单形式。显然,按照大小关系,结点C应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡。
Node* rl_rotate(Node* y)
{
Node * x = y->right;
y->right = ll_rotate(x);
return rr_rotate(y);
}
8.二叉排序树
二叉排序树又叫二叉查找树或者二叉搜索树,它首先是一个二叉树,而且必须满足下面的条件:
- 若左子树不空,则左子树上所有结点的值均小于它的根节点的值
- 若右子树不空,则右子树上所有结点的值均大于它的根结点的值
- 左、右子树也分别为二叉排序树
- 没有键值相等的节点
二叉排序树的查找过程:
BiTree SearchBST(BiTree T, KeyType key)
{
if ((!T)||EQ(key, T->data.key)) return(T); //查找结束
else if LT(key, T->data.key) return (SearchBST(T->lchild, key)); //在左子树中继续查找
else return (SearchBST(T->rchild,key)); //在右子树中继续查找
}
二叉排序树的插入:
SearchBST (K, &t) { //K为待查关键字,t为根结点指针
p=t; //p为查找过程中进行扫描的指针
while(p!=NULL)
{
case {
K= p->data: {查找成功,return true;}
K< p->data : {q=p;p=p->lchild } //继续向左搜索
K> p->data : {q=p;p=p->rchild } //继续向右搜索
}
} //查找不成功则插入到二叉排序树中
s =(BiTree)malloc(sizeof(BiTNode));
s->data=K; s ->lchild=NULL; s ->rchild=NULL;
//查找不成功,生成一个新结点s,插入到二叉排序树叶子处
case {
t=NULL: t=s; //若t为空,则插入的结点s作为根结点
K < q->data: q->lchild=s; //若K比叶子小,挂左边
K > q->data: q->rchild=s; //若K比叶子大,挂右边
}
return OK;
}