树,都知道吧!不过在程序员的数据结构里,就是把树倒过来(当然是树形数据结构的抽象描述了)!
树的定义:
树(Tree),是 n(n >= 0)个结点的有限集合。当 n = 0时,该集合满足以下条件:
(1)每个元素称为结点(node);
(2)有一个特定的结点被称为根结点或树根(root)。
(3)除根结点之外的其余数据元素被分为m(m≥0)个互不相交的集合T1,T2,……Tm-1,其中每一个集合Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree)。
树的基本术语:
结点(Node): 包含一个数据元素以及若干指向其子树的分支。(C: 数据域 + 指针域)
结点的度: 拥有子树的个数称为该结点的度。
树的度: 树中所有结点的度的最大值。
叶子结点:度为0 的结点称为叶子结点,也成为终端结点。
结点的层次:结点的层次从树根开始定义,根为第一层,根的孩子为第二层。。。。
树的深度:树中所有结点层次的最大值称为树的深度。
二叉树
定义:每个结点的度不大于2的特殊树。子树分左、右子树。
二叉树的性质 (重要)
1、在二叉树的第i层上至多有 2^(i-1) 个结点。(i >= 1)
2、深度为 k 的二叉树至多有 2^i -1 个结点。
3、对于任意一颗二叉树T,若叶子结点数为 n0,度为 2 的结点数为 n2 ,则 n0 = n2 + 1 ;
提出概念:
满二叉树:深度为 k 且含有 2^k -1 个结点的二叉树称为满二叉树。
解释:就是除了最后一层的叶子结点,其余结点都有2个子树。
完全二叉树:我称为差一点成了满二叉树的二叉树,即一层一层排,一层满了下一层,就是最后一层可能不完美,小于等于 2^(i-1) 个结点。完美了就是满二叉树。所以满二叉树就是完美的完全二叉树。
4、具有 n 个结点的完全二叉树的深度为 log2N +1。
5、对于具有 n 个结点的完全二叉树,如果按照满二叉树结点进行连续编号的方式,对所有结点从 1 开始顺序编号,则对于任意序号为 i 的结点有:
①、如果 i=1,则结点 i 为根;如果 i >1 ,则结点i 的双亲结点序号为 i/1;
②、如果 2i <= n,则结点i 的左孩子结点序号为 2i ,否则,结点 i 无左孩子。
③、如果 2i+1 <= n,则结点i 的左孩子结点序号为 2i+1,否则,结点 i 无右孩子。
二叉树的存储:
常用的有两种,顺序存储、链式存储。
顺序存储:
建立数组,按照满二叉树给结点编号,然后把结点放在数组中编号对应的位置上。
存在问题:对于一般的二叉树,比较浪费空间,合适满二叉树或者完全二叉树。
链式存储:
结点结构分为三个域:数据域,左孩子域,右孩子域。
左孩子域指向该结点的左孩子,右孩子域指向该结点的右孩子。
typedef struct Node
{
DateType data;
struct Node *LChild;
struct Node *RChild;
}BiTNode, *BiTree;
//根据需要还可以建立指向父节点的域
二叉树的遍历:
有前序遍历,中序遍历,后序遍历 和 层次遍历。
前序遍历、中序遍历 和 后序遍历 的实现:
//递归实现:前序遍历(中序遍历和后续遍历类似)
void PreOrder(BiTree root)
{
if(root)
{
Visit(root);//访问根结点
PreOrder(root->LChild);//前序遍历左子树
PreOrder(root->RChild);//前序遍历右子树
}
}
//非递归实现前序遍历:(使用栈)
void PreOrder(BiTree root)
{
stack<BiTree> s;//C++中的 栈 容器
BiTree p;
p = root;
while(p != null | s.empty() != 0)
{
while(p != null)
{
Visit(p->data);//访问数据域
s.push(p);
p = p->LChild;
}
if(s.empty() != 0)
{
p = s.top();//取栈顶
s.pop();//退栈
p = p->RChild;
}
}
}
二叉树进阶
线索二叉树:
前序线索二叉树、中序线索二叉树、后序线索二叉树。
概念:对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树。
二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针fwd和bkd,这种方法增加了存储开销,不可取;二是利用二叉树的空链指针。
现将二叉树的结点结构重新定义如下:
typedef struct Node
{
DateType data;
struct Node *LChild;
int ltag;
struct Node *RChild;
int rtag;
}BiTNode, *BiTree;
其中:ltag=0 时lchild指向左子女;
ltag=1 时lchild指向前驱;
rtag=0 时rchild指向右子女;
rtag=1 时rchild指向后继;
最优二叉树:(哈夫曼树)
用于:构造最优编码,用于信息传输、数据压缩等
相关概念:
路径:从树的一个结点到另一个结点的分支序列构成两个结点间的路径。
路径长度:路径上分支的条数称为路径长度。
树的路径长度:从树根到每个结点的路径长度之和称为树的路径长度。
结点的权:给树中的结点赋予一个数值,该数值称为结点的权。
带权路径长度:结点到树根间的路径长度与结点的权的成绩,称为该结点的带权路径长度。
树的带权路径长度WPL:树中所有叶子结点的带权路径长度之和,称为树的带权路径长度。
最优二叉树:树的带权路径长度WPL最小的二叉树称为最优二叉树。
二叉搜索树(又:二叉查找树。二叉排序树):
它或者是一颗空树,或者是具有以下特性的二叉树:
①若它的左子树不空,则左子树上的所有结点的值均小于它的根结点的值;
②若它的右子树不空,则右子树上的所有结点的值均大于它的根结点的值;
③它的左、右子树也分别是二叉搜索树。
根据二叉搜索树的特性,就可以排序时构造二叉搜索树,查找时时间复杂度是O(log2N),最差的时候化为线性查找时间复杂度为O(N)。
为了在每次查找时间复杂度都是 O()log2N。引出了平衡二叉树。
B树和B+树
B树:
不是二叉树,而是树,一种多路平衡二叉树。
多路:指数的分支多于二叉;
平衡:值所有的叶子结点均在同一层。
B树的阶:树中所有的结点的孩子结点的最大值称为B树的阶,通常用m来表示。(从查找效率来考虑,通常取 m>= 3)。
一个m阶的B树的定义:
该书或者是空树,或者满足以下性质的m叉树。
①树只每个结点至多有m个子结点;
②除根结点和叶子结点意外,其他每个结点至少有 m/2 个子结点;
③若根结点不是叶子结点,则根结点至少有两颗子树。
④所有的叶子结点在同一层,可以有 m/2 -1 到 m-1 个关键字,并且叶子结点所在的层数为树的深度。
⑤有 k 个子结点的分支结点恰好包含 k-1 个关键字。