任意的一个树(不管是否是二叉树)均由N个节点和N-1条边构成。
说明:每个节点(根节点除外)都需要一条边链接到其父节点,N个节点就需要N-1条边。
1、二叉树的实现
最常见的是链表的实现方式(由引用相连),例如:
class Node{
int iData;
Node leftChild;
Node rightChild;
}
有的也采用数组的实现形式,此时,节点存在于数组中,节点在数组中的位置对应于它在树中的位置,节点之间的位置关系也暗示了节点之间的联系。0代表根节点,1是根节点的左子节点,2是根节点的右子节点,依次类推,按从上到下,从左到右的顺序存储每一层的每个节点。请注意,树中的每个位置,不管是否存在节点,都在数组中占一个空间(可以只存储null)。
进行插入时,是要在特定位置插入。设节点索引是index,那么其父节点是(index-1)/2,左子节点是 index*2+1,右子节点是index*2+2。
大多数情况下,用数组来表示树,效率比较低,一方面可能会造成空间浪费,另外,如果要删除某个节点,会造成大量的移动。不过,如果不允许删除,并且由于某个原因,动态分配空间非常耗时时,可以利用数组来表示树。2、二叉树的性质
①对于任意一颗二叉树,若其叶子节点树记为x,其度为2的节点数记为y,则有x=y+1.也就是度为0的节点个数比度为2的结点个数多1.
②一颗有N个节点的完全二叉树,如果从左到右,从上到下的,对每个节点从1开始编号,对于其中任意编号为i的节点(1<=i<=N)有:
若i != 1,则i的父亲是i/2;若i==1,则i是根节点
若2i<=N,则i的左孩子是2i;若2i>N,则i无左孩子
若2i+1<=N,则i的右孩子是2i+1;若2i+1>N,则i无右孩子
②一颗有N个节点的完全二叉树,如果从左到右,从上到下的,对每个节点从0开始编号,对于其中任意编号为i的节点(0<=i<=N-1)有:
若i != 0,则i的父亲是i/2;若i==0,则i是根节点
若2i+1<=N,则i的左孩子是2i+1;若2i+1>N,则i无左孩子
若2i+2<=N,则i的右孩子是2i+2;若2i+2>N,则i无右孩子
3、分类二叉树(又称为二叉搜索树、二叉排序树),其定义是:
①每个节点都有一个关键字
②根节点的左子树根的关键字(如果存在)小于根节点的关键字
③根节点的右子树根的关键字(如果存在)大于根节点的关键字
④根节点的左右子树也都是二叉树
分类二叉树具有以下特点:
①具有很高的查找效率,时间复杂度为O(logN),以2为底,查找次数最多是树的层数,事实上,对分类二叉树的插入、删除、查询时间复杂度都是O(logN),以2为底。
②如果按照中序遍历,结果是按关键字排序的线性序列。
③分类二叉树的前序和后序遍历对解析代数表达式是有用的。
4、哈夫曼树(最优二叉树)
具有N个叶节点的二叉树,可以有多种表现形式,这些二叉树具有不同的加权路径长度,其中加权路径长度最小的那个二叉树就是哈夫曼树(最优二叉树)。
叶节点可以用来存储信息,叶节点中存储的信息不同,被查找的频率就不同,使用频率高的信息,重要性越高,权值越大。
如果树中每个叶节点都有权值,那么该叶节点的加权路径长度=该叶节点的权值 * 该叶节点到根节点的路径长度。树的加权路径长度就是该树所有叶节点加权路径长度之和。
哈夫曼树的重要应用就是数据压缩:例如为字符编码时,应尽量减少最常用字符的位数量。假如E是最常用的字母,就应该用最少的位数来表示E。
①构造哈夫曼树
a:为每个要编码的字符创建节点对象,该节点对象的数据项包括该字符和该字符的频率
b:每个节点都作为一棵树对待,并且每个节点都是树的根节点
c:把这些树插入到优先级队列中,其中频率高的优先级低,排序后如(a)所示
接下来做下面的事:
从优先级队列中删除两棵树,并把它们作为新节点的左右子节点,新节点的频率是其所有子节点频率之和。
把新节点插入到优先级队列中(插入后还是有序的)
反复重复上两步,直到优先级队列中只剩一颗树。这棵树就是构造好的哈夫曼树了。
②编码
构造完哈夫曼树,接下来就开始为字符编码了。
假设向左的边标记是0,向右的边标记是1,那么从根节点到某个叶节点所走过的边上的标记序列就是该叶节点包含的字符节点的编码了。
哈夫曼编码保证了任意字符的编码序列都不是其他任意字符的前缀。
5、完全二叉树
完全二叉树是由满二叉树而引出来的。对于深度为K的,有N个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。