数据结构笔记整理第5章:树和二叉树
2016-05-22 15:49
316 查看
第5章 树和二叉树
本章内容
本章主要介绍树、二叉树的概念,遍历方法以及应用等,本章在考研中是重点内容。5.1 树相关的基本概念
树是一种非线性的数据结构,是若干结点的集合,有唯一的根结点和若干棵互不相交的子树构成。其中每一棵子树又是一棵树,也是由唯一的根结点和若干棵互不相交的子树组成的,由此可知:树的定义是递归的。树的结点数目可以为0,为0的时候是一棵空树。结点:结点不仅包含数据元素,而且包含指向子树的分支。
结点的度:结点拥有子树的个数或者分支的个数。
树的度:树中各结点度的最大值。
叶子结点:终端结点,度为0的结点。
非终端结点:分支结点,内部结点,度不为0的结点。
以根结点为第一层,以此类推。
树的高度(深度):树中结点的最大层次。
结点的深度:从根结点开始计算,根结点深度为1。
结点的高度:从底层叶子结点开始计算,叶子结点高度为1。
有序树:树中结点子树从左到右有序,不能交换。
丰满树:出最底层外,其他层都是满的。
森林:若干棵互不相交的树的集合。
5.2 树的存储结构
双亲存储结构:一种顺序存储结构,知道了i,tree[i]为其双亲结点。孩子存储结构:同图中的邻接表存储。
5.3 二叉树的相关的基本概念
一般的树加上如下的两个限制条件,就得到了二叉树:每个结点最多只有两棵子树,即二叉树中结点的度只能为0,1以及2;子树有左右之分,不能颠倒。满二叉树:所有的分支结点都有左孩子和右孩子结点,并且叶子结点都集中在二叉树的最下层,这样的二叉树称为满二叉树。对满二叉树编号:编号从1开始,从上到下、从左到右进行。
完全二叉树:一棵深度为K有n个结点的二叉树进行编号后,各结点的编号与深度为K的满二叉树上相同位置的结点的编号相同。一棵完全二叉树一定是由一棵满二叉树从右至左、从下至上挨个删除结点所得到的。
5.4 二叉树的主要性质
1.非空二叉树上叶子结点数等于双分支结点数加1,即总分支数 = 总结点数 - 12.二叉树的第i层上最多有
![](http://i.imgur.com/BKs67Jq.png)
(i >= 1)个结点
3.高度(或深度)为K的二叉树最多有
![](http://i.imgur.com/vHr3fkg.png)
个结点,换句话说:满二叉树中前K层结点个数为:
![](http://i.imgur.com/DipLHLR.png)
4.有n个结点的完全二叉树,对各结点从上到下,从左到右依次编号(范围1~n),则结点之间的关系,若i为某结点编号,则:
若i≠1,双亲编号⌊i/2⌋;
若2i≤n,左孩子为2i,若2i>n,无左孩子;
若2i+1≤n,右孩子为2i+1,若2i+1>n,无右孩子;
5.n个结点,构成h(n)种不同的二叉树,则:
![](http://i.imgur.com/DgGDD7l.png)
6.n个结点的完全二叉树高度(深度)为:
![](http://i.imgur.com/XeevJwU.png)
5.5 二叉树的存储结构
顺序存储:最适合于完全二叉树,使用顺序存储结构要从数组下标为1开始。这里要注意区分树的顺序存储结构和二叉树的顺序存储结构:树:数组下标代表结点的编号,内容表示结点之间的关系;
二叉树:数组下标既指示了编号又指示了关系。
链式存储:
![](http://i.imgur.com/92Zih9m.png)
typedef struct BTNode { char data; struct BTNode *lchild; struct BTnode *rchild; }BTNode;
5.6 树、森林与二叉树的转换
将树转换为二叉树:同一结点的各孩子结点用线串起来,将每个结点的分支从左往右除了第一个以外,其余全部剪掉,擦掉虚线,连成实线,得到二叉树。【例子1】树转换为二叉树
![](http://i.imgur.com/OKgwn9b.png)
![](http://i.imgur.com/sTWFztj.png)
将森林转换为二叉树:根据孩子兄弟的法则,根结点没有右兄弟,所以转换为二叉树没有右孩子。将森林中第二棵树转换成的二叉树,当作第一棵树的右子树,依此类推。
【例子2】森林转换为二叉树
![](http://i.imgur.com/6ROZJRt.png)
![](http://i.imgur.com/PG8VyUb.png)
![](http://i.imgur.com/hsx8JYY.png)
5.7 二叉树的遍历算法
包括:先序遍历、中序遍历、后序遍历和层次遍历。先序遍历:
如果二叉树为空数,什么都不做。
否则,访问根节点->先序遍历左子树->先序遍历右子树
/* pre order by recursion method */ void preOrder(BTNode *p) { if (p != null) { visit(p->data); preOrder(p->lchild); preOrder(p->rchild); } } /* pre order by no recursion method */ void preOrder(BTNode *p) { Stack <BTNode *> s; BTNode *q; q = p; s.initial(); while(q != null || !s.empty()) { if (q != null) { visit(q->data); s.push(q); q = q->lchild; } else { s.pop(q); q = q->rchild; } } }
中序遍历:
如果二叉树为空数,什么都不做。
否则,中序遍历左子树->访问根结点->中序遍历右子树
/* in order by recursion method */ void inOrder(BTNode *p) { if (p != null) { inorder(p->lchild); visit(p->data); inorder(p->rchild); } } /* in order by no recursion method */ void inOrder(BTNode *p) { Stack <BTNode *> s; BTNode *q = p; s.initial(); while(q != null || !s.empty()) { if (q != null) { s.push(q); q = q->lchild; } else { s.pop(q); visit(q->data); q = q->rchild; } } }
后序遍历:
如果二叉树为空数,什么都不做。
否则,后序遍历左子树->后序遍历右子树->访问根结点
/* post order by recursion method */ void postOrder(BTNode *p) { if 4000 (p != null) { postOrder(p->lchild); postOrder(p->rchild); visit(p->data); } } /* post order by no recursion method */ void postOrder(BTNode *p) { Stack <BTNode *> s; Stack <int> tag; BTNode *q = p; int f; s.initial(); tag.initial(); while(q != null || !s.empty()) { if (q != null) { s.push(q); q = q->lchild; tag.push(1); } else { s.pop(q); tag.pop(f); if (f == 1) { s.push(q); tag.push(2); q = q->rchild; } else { visit(q->data); q = null; } } } }
层次遍历:
按照一定的方向(如从左至右,从上至下)每一层次对二叉树各个结点进行访问。要进行层次遍历,需要建立一个循环队列,先将二叉树头结点入队列,然后出队列,访问该结点,如果有左子树,则左子树根结点入队;如果有右子树,则右子树根结点入队。出队列,访问出队列结点,如此反复直到队列空为止。
void levelOrder(BTNode *p) { Queue <BTNode *> q; BTNode *r; q.initial(); if (p != null) { q.push(p); while(!q.empty()) { q.pop(r); visit(r->data); if (r->lchild != null) { q.push(r->lchild); } if (r->rchild != null) { q.push(r->rchild); } } } }
【例子3】二叉树的遍历结果
![](http://i.imgur.com/Mmmnht9.png)
先序遍历:A->B->C->D->E->F->G->H
中序遍历:C->B->E->D->F->A->H->G
后序遍历:C->E->F->D->B->H->G->A
层次遍历:A->B->G->C->D->H->E->F
5.8 树和森林的遍历算法
树的遍历:先根遍历:先访问根结点、再访问子树。
后根遍历:先访问子树,再访问根结点。
树的先根遍历对应二叉树的先序遍历。
树的后根遍历对应二叉树的中序遍历。
森林的遍历:
先序遍历:对应二叉树的先序遍历,先访问第一棵树的根结点,先序遍历第一棵树中根结点的子树,先序遍历森林中除去第一棵树的其他树。
中序遍历:对应二叉树的中序遍历,中序遍历第一棵树根结点的子树,访问第一棵树的根结点,中序遍历森林中除去第一棵树的其他树。
5.9 线索二叉树
![](http://i.imgur.com/QoJ6DsW.png)
ltag=0:lchild为指针,指向结点左孩子;ltag=1,表示lchild为线索,指向结点直接前驱。
rtag=0:rchild为指针,指向结点右孩子;rtag=1,表示rchild为线索,指向结点直接后驱。
typedef struct TBTNode { char data; int ltag, rtag; struct TBTNode *lchild; struct TBTNode *rchild; } TBTNode;
5.10 二叉排序树(BST)
这部分内容主要应用在“查找或者排序部分”。二叉排序树或者是空树,或者是满足以下性质的二叉树:若左子树不空,则左子树所有值 < 根
若右子树不空,则右子树所有值 > 根
左右子树各是一棵二叉排序树
输出二叉排序树的中序遍历,则该输出序列为递增序列。
typedef struct BTNode { int key; struct BTNode *lchild; struct BTNode *rchild; } BTNode;
5.11 平衡二叉树(AVL)
这部分内容主要应用在“查找或者排序部分”。左右子树都是平衡二叉树,并且左右子树的高度之差的绝对值不超过1(引入了平衡因子的概念)。
一个结点的平衡因子:左子树高度 - 右子树高度。取值:-1,0,1
当失去平衡的最小子树被调整为平衡子树之后,无需调整原有其他所有不平衡子树,整个二叉排序树会成为一棵平衡二叉树。
最小子树:以距离插入结点最近,且平衡因子绝对值大于1的结点作为根的子树。
将失去平衡的二叉树进行平衡调整有4种情况:LL型、RR型、LR型和RL型。
LL型:在左子树根结点的右子树上插入结点 -> 右旋
LR型:在左子树根结点的右子树上插入结点 -> 左旋 + 右旋
RR型:在右子树根结点的右子树上插入结点 -> 左旋
RL型:在右子树根结点的左子树上插入结点 -> 右旋 + 左旋
【例子4】平衡二叉树的插入删除和平衡调整
以关键字{16、3、7、11、9、26、18、14、15}构造一棵AVL树,构造完成后依次删除16、15、11
插入部分:
![](http://i.imgur.com/GrDgiIG.png)
![](http://i.imgur.com/tnw2x12.png)
![](http://i.imgur.com/nbe58iY.png)
删除部分:
![](http://i.imgur.com/oDoLr0J.png)
5.12 B-树与B+树
B-树可以看作是二叉排序树的扩展,二叉排序树是二路查找,B-树的多路查找。因为B-树结点内的关键字是有序的,在结点内查找的时候除了顺序查找外,可以用折半查找。B-树需要满足以下条件:每个结点最多有m个分支(子树);而最少分支棵树要看是否为根结点,根结点最少两个分支,非根结点最少有
![](http://i.imgur.com/8IxpJl0.png)
个分支;
有n个分支的结点有n-1个关键字,从左至右递增顺序排列;
各个底层是叶结点,叶结点下面是失败结点(空指针表示)。
B+树是B-树的一种变形。它们之间的差别主要有:
B+树中,n个关键字的结点含有n个分支,B-树中有n+1个分支;
B+树中叶子结点包含信息,并且包含了全部关键字,B-树的叶子只包含关键字(索引)
由于这里并不是考研重点考察的部分,所以关于B+树和B-树的详细操作会在后面的文章中单独描述。
![](http://i.imgur.com/DsYsKOC.png)
5.13 哈夫曼树和哈夫曼编码
哈夫曼树是最优二叉树,带权路径最短。路径:树中一个结点到另一个结点分支构成的路线。
路径长度:路径上的分支数目。
树的路径长度:从根到每个结点的路径长度之和。
带权路径长度:结点有权值,结点到根之间的路径长度乘以结点的权值。
**树的带权路径长度:**WPL,树中所有叶子结点带权路径长度之和。
哈夫曼树的特点:权值越大的结点,距离根结点越近。树中没有度为1的结点的哈夫曼树称为严格(正则)二叉树。
哈夫曼树的构造方法:
给定n个权值
(1)将n个权值看作只有根的n棵二叉树,集合记为F。
(2)F中挑选两棵根结点权值最小的(a, b)作为左右子树,构成新树c。
(3)F中删除a与b,加入c。
【例子5】哈夫曼树的构建
![](http://i.imgur.com/460fd6v.png)
哈夫曼编码:
前缀编码:任一字符的编码都不是另一个字符编码的前缀。
而哈夫曼编码就是长度最短的前缀编码。对构造好的哈夫曼树,左分支表示0,右分支表示1(根据题目要求来)
【例子6】哈夫曼编码
A、B、C、D的权值(或者出现频率)为0.4、0.3、0.1和0.2,求哈夫曼编码。
![](http://i.imgur.com/VFT0Kss.png)
相关文章推荐
- AVL树-自平衡二叉查找树(Java实现)
- C#数据结构之顺序表(SeqList)实例详解
- Lua教程(七):数据结构详解
- 解析从源码分析常见的基于Array的数据结构动态扩容机制的详解
- C#数据结构之队列(Quene)实例详解
- C#数据结构揭秘一
- C#数据结构之单链表(LinkList)实例详解
- C#实现获取系统目录并以Tree树叉显示的方法
- 数据结构之Treap详解
- C语言实现输入一颗二元查找树并将该树转换为它的镜像
- C语言二叉树的非递归遍历实例分析
- 使用C语言构建基本的二叉树数据结构
- 一波二叉树遍历问题的C++解答实例分享
- 举例讲解C语言程序中对二叉树数据结构的各种遍历方式
- C++非递归队列实现二叉树的广度优先遍历
- PHP实现的线索二叉树及二叉树遍历方法详解
- C#使用前序遍历、中序遍历和后序遍历打印二叉树的方法
- 用C语言举例讲解数据结构中的算法复杂度结与顺序表
- C#数据结构之堆栈(Stack)实例详解
- C#数据结构之双向链表(DbLinkList)实例详解