一.二叉树
本文主要介绍1.二叉树的基本概念。2.二叉树与树,森林的转换。3.二叉树的存储结构。4.二叉树的基本运算以及实现。5.二叉树的遍历。
二叉树的基本概念
1.任何m次树都可以转化为二叉树,二叉树结构简单,存储效率高,运算算法简单。
2.二叉树和度为2的数的区别:
度为2的数至少有一个节点度数为2,但是二叉树没有这个要求。
度为2的树不分左右子树,而二叉树是严格区分左右子树的。
满二叉树:所有分支节点都有左孩子结点和右孩子节点,并且叶子节点都集中在二叉树的最下一层,这样的二叉树称为满二叉树。另一种说法为:一颗高度为h且有2^h - 1个节点的二叉树称为满二叉树。
满二叉树特点:
叶子在最下面一层。
只有度为0和度为2的节点。
完全二叉树:若二叉树中只有最下面2层的节点的度数小于2,并且最下面一层的叶子节点都依次排列在该层最左边位置上,则称为完全二叉树。不难看出,满二叉树是完全二叉树的一种特例,完全二叉树与等高的满二叉树对应的位置上的节点编号相同。
完全二叉树特点:
1.叶子节点只可能出现在最下两层上。
2.对于最大层次的叶子节点,都依次排在最左位置上。
3.如果有度为1的节点,只可能有一个且该节点只有左孩子节点没有右孩子节点。
4.出现某节点(编号为i)为叶子节点或者只有左孩子,则编号大于i的节点均为叶子节点。
5.当节点总数n为奇数时,度数为1的节点个数为n1 = 0;
当节点总数n为偶数时,度数为1的节点个数为n1 = 1;
二.二叉树与树,森林的转换
任何一个森林或者一颗树可以唯一地对应一颗二叉树,同样,任何一个二叉树可以唯一对应一个森林或者一棵树。
1.森林,树转换为二叉树:
(1)在所有相邻兄弟节点(森林中每棵树的根节点可以看成是兄弟节点)之间加以条水平线。
(2)对每个非叶子节点k,除了其最左边的孩子节点外,删去k与其他孩子节点的连线。
(3)所有水平线段以左边节点为轴心顺时针旋转45度。
例子:
2.二叉树还原为森林,树
(1)对于一颗二叉树中任一节点k1,沿着k1右孩子节点的右子树方向搜索所有的右孩子节点,即搜索序列k2,k3,...,km,其中ki+1为ki的右孩子节点。
(2)删除k1,k2,...,km之间的连线。
(3)若k1有双亲节点k,则连接k与ki。
(4)将图形规整化,使各节点按层次排列。
例如:
三.二叉树的存储结构
主要有顺序存储结构和链式存储结构。
1.顺序存储结构
先对树进行编号,从上到下,从左到右。当某节点是编号为i的双亲节点的左孩子节点时,它的编号为2i,当为右节点时,编号为2i + 1。同理,对于编号为i的节点,其双亲节点为i/2。
注意:当二叉树中某节点为空节点或者无效节点时,对应的位置用特殊值代替。
特点:对于完全二叉树来说,采用顺序存储结构十分合适,能充分利用时间,但一般二叉树会浪费空间,最好用链式存储结构。
2.链式存储结构
static class TreeNode
{
private char data;
private TreeNode lchild;
private TreeNode rchild;
//setters and getters
......
}
Data为值域,lchild和rchild分别表示左右孩子节点。
四.二叉树的基本运算以及实现
1.创建,输出二叉树。2.查找某一节点。3.求高度。
import java.util.Scanner;
public class 二叉树的创建_输出_查找_求高度
{
public static void main(String args[])
{
String str = "A(B(G(,H)),C(D(F),E))";
char chstr[] = str.toCharArray();
TreeNode root;
/*
* 创建打印二叉树
*/
root = CreateBTree(chstr);
printBTree(root);
/*
* 查找某一节点
*/
System.out.print("\n输入需要查找的数据:");
char ch = InputData();
if(FindNode(root,ch) != null)
System.out.println("存在该节点!");
else
System.out.println("不存在该节点");
/*
* 计算二叉树的高度
*/
int height = CountBTreeHeight(root);
System.out.println("\n该二叉树的高度为:" + height);
}
/*
* 创建二叉树存储结构
*/
static class TreeNode
{
private char data;
private TreeNode lchild;
private TreeNode rchild;
public char getData() {
return data;
}
public void setData(char data) {
this.data = data;
}
public TreeNode getLchild() {
return lchild;
}
public void setLchild(TreeNode lchild) {
this.lchild = lchild;
}
public TreeNode getRchild() {
return rchild;
}
public void setRchild(TreeNode rchild){
this.rchild = rchild;
}
}
/*
* 创建二叉树
*/
public static TreeNode CreateBTree(char chstr[])
{
char ch = chstr[0];
int MaxSize = 10;
int top = -1;
int k = 1;
int i = 0;
TreeNode treeroot = new TreeNode();
TreeNode flag;
TreeNode node = new TreeNode();
TreeNode St[] = new TreeNode[MaxSize];
while(true)
{
switch(ch)
{
case '(':top++;St[top] = node;k = 1;break;
case ')':top--;break;
case ',':k = 2;break;
default:
node = new TreeNode();
node.setData(ch);
node.setLchild(null);
node.setRchild(null);
if(treeroot.getData() == '\0')
treeroot = node;
else
{
flag = St[top];
switch(k)
{
case 1:flag.setLchild(node);break;
case 2:flag.setRchild(node);break;
}
}
}
i++;
if(i == chstr.length) break;
ch = chstr[i];
}
return treeroot;
}
/*
* 打印出二叉树
*/
public static void printBTree(TreeNode root)
{
if(root != null)
{
System.out.print(root.getData());
if((root.getLchild() != null) || (root.getRchild() != null))
{
System.out.print("(");
printBTree(root.getLchild());
if(root.getRchild() != null) System.out.print(",");
printBTree(root.getRchild());
System.out.print(")");
}
}
}
public static char InputData()
{
Scanner reader = new Scanner(System.in);
String str = reader.next();
return str.charAt(0);
}
public static TreeNode FindNode(TreeNode root,char ch)
{
TreeNode node;
if(root == null)
return null;
else
{
if(root.getData() == ch)
return root;
else
{
node = FindNode(root.getLchild(), ch);
if(node != null)
return node;
else
return FindNode(root.getRchild(),ch);
}
}
}
public static int CountBTreeHeight(TreeNode root)
{
int lchild;
int rchild;
if(root == null)
return 0;
else
{
lchild = CountBTreeHeight(root.getLchild());
rchild = CountBTreeHeight(root.getRchild());
return (lchild > rchild) ? (lchild + 1):(rchild + 1);
}
}
}
五.二叉树的遍历
二叉树的遍历有4种,分别为先序遍历,中序遍历,后序遍历,层次遍历。
1.先序遍历
(1)访问根节点。
(2)先序遍历左子树。
(3)先序遍历右子树。
2.中序遍历
(1)中序遍历左子树。
(2)访问根节点。
(3)中序遍历右子树。
3.后序遍历
(1)后序遍历左子树。
(2)后序遍历右子树。
(3)访问根节点。
4.层次遍历
(1)访问第一层(即根节点)
(2)访问第二次(即根节点的孩子节点)
(3)依次类推。
例如:
则先序遍历为:ABDGCEF,中序遍历为:DGBAECF,后序遍历为:GDBEFCA,层次遍历为:ABCDEFG
六.哈夫曼树
1.基本概念:
权:在许多应用中,常常将树中的节点赋上一个有着某种意义的数值,该数值称为该节点的权。
节点的带权路径长度:从树根节点到某节点之间的路径长度与该节点上权值的乘积称为该节点的带权路径长度。
树的带权路径长度:树中所有叶子节点的带权路径长度之和称为该树的带权路径长度。
哈夫曼树:对于一组具有确定权值的叶子节点可以构造出多个具有不同带全路径长度的二叉树。把其中带权路径长度最小的二叉树称为哈夫曼树,又称为最优二叉树。
2.哈夫曼树的构造算法:
(1)根据给定的n个权值,使对应节点构成n颗二叉树的森林,其中每颗二叉树中都只有一个带权值的根节点,其左右子树都为空。
(2)在森林中选择两个权值最小的叶子节点作为左右子树构造一个新的二叉树,且置新的二叉树的根节点权值为左右子树上根节点的权值之和。
(3)在森林中,用新得到的二叉树代替选取的二个树。
(4)重复(2)(3)步骤,直到只含一棵树位置,该树便是哈夫曼树。
例如:
1.2.
3.
4.
哈夫曼编码:
编码:在数据通信中,经常需要将传送的文字转换为二进制字符0和1组成的字符串,这个过程称为编码。
哈夫曼编码:设需要编码的字符集合为:{d1,d2,...,dn},各个字符在电文中出现的次数集合为{w1,w2,...,wn},那么以d1,d2...,dn作为叶子节点,以w1,w2,...,wn作为个节点的权值构造一颗哈夫曼树,规定哈夫曼树中左分支为0,右分支为1,则从根节点到叶子节点所经过的分支对应的0和1序列就称为该节点对应字符的编码。这样的编码就是哈夫曼编码。
哈夫曼编码的平均长度=d1*w1 + d2*w2+...+dn*wn
补充
一..线索二叉树。
1.概念:
n个结点的二叉链表中含有n+1(2n-(n-1)=n+1)个空指针域。利用二叉链表中的空指针域,存放指向结点在某种遍历次序下的前趋和后继结点的指针(这种附加的指针称为"线索")。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
2.线索二叉树的结构
二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针fwd和bkd,这种方法增加了存储开销,不可取;二是利用二叉树的空链指针。现将二叉树的结点结构重新定义如下:
二.平衡二叉树
1.概念:
平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。构造与调整方法 平衡二叉树的常用 算法有红黑树、AVL、Treap、伸展树等。
2.平衡二叉树的作用:
我们知道,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n))同时也由此而决定。但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过 随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度。
平衡二叉搜索树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。常用算法有红黑树、AVL、Treap、伸展树等。在平衡二叉搜索树中,我们可以看到,其高度一般都良好地维持在O(log2n),大大降低了操作的时间复杂度。