第六章 树
1、树:是n节点的有限集树是n(n=>0)个节点的有限集。 N=0时成为空树。在任意一颗非空树中:(1)有且仅有一个称为根的节点;(2)当n>0时,其余节点可分为m(m>0)个互不相交的有限集T1、T2、T3、Tm,其中每个节点又是一棵树,并且称为根的子树。
2 、节点分类:节点拥有的的子树的称为节点的度。度为0的称为终端节点或者叶节点;度不为0的称为非终端节点或者分支节点。树的度是树内个节点的度的最大值。
3、节点间关系:节点的子树的根称为该节点的孩子, 该节点称为孩子的双亲。同一个双亲的孩子之间称为兄弟。如果将树种节点的各子树看成是从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。
4、结点的层次:从根开始起根为第一层,根的孩子为第二层,树中结点最大层次成为树的深度或高度。如果树中结点的各子树看做是有序的不能互换的则称该树为有序树否则为无序树。森林是m(m>=0)颗互不相交的树的集合。
5、线性结构和树结构对比:线性节点的第一个元素无前驱,树结构的根节点无双亲,且唯一。线性表最有一个元素无后继, 树节点的叶节点无还是,但可以有多个中间元素一个前驱一个后继, 树节点一个双亲多个孩子。
6、树的存储结构:双亲表示法、孩子表示法、孩子兄弟表示法。以一组连续空间存储树的结点,同时每个结点中附设一个指示器指示其双亲结点在数组中的位置。找根容易O(1)找孩子难。
孩子表示法思路(右图):把每个节点的孩子节点排列起来,以单链表做存储结构,则n个节点有n个孩子链表,如果是叶子节点则次单链表为空,然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一位数组中。
双亲孩子表示法:是孩子表示法的改进,添加了双亲的存储结构。
孩子兄弟表示法:任意一棵树,它的节点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此我们设置两个指针分别指向该节点的第一个孩子和此节点的有兄弟。节点结构
data | firstchild | rightright |
二叉树性质:1.在二叉树的第i层上最多有2 i-1 个节点 。(i>=1)。2.二叉树中如果深度为k,那么最多有2k-1个节点。(k>=1)。3.n0=n2+1 n0表示度数为0的节点 n2表示度数为2的节点。4.在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]+1是向下取整。
5.若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点:(1) 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;
(2) 若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;
(3) 若 2i+1>n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子结点。
7、二叉树的存储:顺序存储结构一般只用于完全二叉树二叉树的链式存储结构:二叉链表,三叉链表,线索链表。
遍历二叉树,次序、访问、所有结点、有且仅有一次被访问遍历次序,从左到右:前序遍历(根左右)、中序遍历(左根右)、后序遍历(左右根)、层序遍历。
二叉树遍历(递归):
class node {
private int data;
private node leftNode;
private node rightNode;
public node(int data, node leftNode, node rightNode){
this.data = data;
this.leftNode = leftNode;
this.rightNode = rightNode;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public node getLeftNode() {
return leftNode;
}
public void setLeftNode(node leftNode) {
this.leftNode = leftNode;
}
public node getRightNode() {
return rightNode;
}
public void setRightNode(node rightNode) {
this.rightNode = rightNode;
}
}
public class BinaryTree {
/**
* 二叉树的先序、中序、后序遍历(递归方法)
*/
public node init() {//注意必须逆序建立,先建立子节点,再逆序往上建立,
node J = new node(8, null, null);
node H = new node(4, null, null);
node G = new node(2, null, null);
node F = new node(7, null, J);
node E = new node(5, H, null);
node D = new node(1, null, G);
node C = new node(9, F, null);
node B = new node(3, D, E);
node A = new node(6, B, C);
return A; //返回根节点
}
public void printNode(node node){
System.out.print(node.getData());
}
public void theFirstTraver(node root) { //先序遍历
printNode(root);
if (root.getLeftNode() != null) { //使用递归进行遍历左孩子
theFirstTraver(root.getLeftNode());
}
if (root.getRightNode() != null) { //递归遍历右孩子
theFirstTraver(root.getRightNode());
}
}
public void theInOrderTraversal(node root) { //中序遍历
if (root.getLeftNode() != null) {
theInOrderTraversal(root.getLeftNode());
}
printNode(root);
if (root.getRightNode() != null) {
theInOrderTraversal(root.getRightNode());
}
}
public void thePostOrderTraversal(node root) { //后序遍历
if (root.getLeftNode() != null) {
thePostOrderTraversal(root.getLeftNode());
}
if(root.getRightNode() != null) {
thePostOrderTraversal(root.getRightNode());
}
printNode(root);
}
public static void main(String[] args) {
BinaryTree tree = new BinaryTree();
node root = tree.init();
System.out.println("先序遍历");
tree.theFirstTraver(root);
System.out.println("");
System.out.println("中序遍历");
tree.theInOrderTraversal(root);
System.out.println("");
System.out.println("后序遍历");
tree.thePostOrderTraversal(root);
System.out.println("");
}
}
非递归借助栈:
public void theFirstTraversal_Stack(node root) { //先序遍历
Stack stack = new Stack();
node no = root;
while (no != null || stack.size() > 0) { //将所有左孩子压栈
if (no != null) { //压栈之前先访问
printNode(no);
stack.push(no);
no = no.getLeftNode();
} else {
no = stack.pop();
no = no.getRightNode();
}
}
}
public void theInOrderTraversal_Stack(node root) { //中序遍历
Stack stack = new Stack();
node no = root;
while (no != null || stack.size() > 0) {
if (no != null) {
stack.push(no); //直接压栈
no = no.getLeftNode();
} else {
no = stack.pop(); //出栈并访问
printNode(no);
no = no.getRightNode();
}
}
}
public void thePostOrderTraversal_Stack(node root) { //后序遍历
Stack stack = new Stack();
Stack output = new Stack();//构造一个中间栈来存储逆后序遍历的结果
node no = root;
while (no != null || stack.size() > 0) {
if (no != null) {
output.push(no);
stack.push(no);
no = no.getRightNode();
} else {
no = stack.pop();
no = no.getLeftNode();
}
}
while (output.size() > 0) {
printNode(output.pop());
}
}
8、线索二叉树:
指向前驱或者后继得指针称为线索,加上线索的二叉树链表称为线性链表,相应的二叉树就称为线索二叉树。(n个结点的二叉链表一共2n个指针域,一共n-1条分支数一共2n-(n-1)=n+1个空指针域。)
9、树、森林、二叉树的相互转化:将树转换成二叉树的步骤是:
(1)加线。就是在所有兄弟结点之间加一条连线;
(2)抹线。就是对树中的每个结点,只保留他与第一个孩子结点之间的连线,删除它与其它孩子结点之间的连线;
(3)旋转。就是以树的根结点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。
森林转换为二叉树:森林是由若干棵树组成,可以将森林中的每棵树的根结点看作是兄弟,由于每棵树都可以转换为二叉树,所以森林也可以转换为二叉树。将森林转换为二叉树的步骤是:
(1)先把每棵树转换为二叉树;
(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子结点,用线连接起来。当所有的二叉树连接起来后得到的二叉树就是由森林转换得到的二叉树。
二叉树转换为树:二叉树转换为树是树转换为二叉树的逆过程,其步骤是:
(1)若某结点的左孩子结点存在,将左孩子结点的右孩子结点、右孩子结点的右孩子结点……都作为该结点的孩子结点,将该结点与这些右孩子结点用线连接起来;
(2)删除原二叉树中所有结点与其右孩子结点的连线;(3)整理(1)和(2)两步得到的树,使之结构层次分明。
二叉树转换为森林:二叉树转换为森林比较简单,其步骤如下:
(1)先把每个结点与右孩子结点的连线删除,得到分离的二叉树;
(2)把分离后的每棵二叉树转换为树;(3)整理第(2)步得到的树,使之规范,这样得到森林。
根据树与二叉树的转换关系以及二叉树的遍历定义可以推知,树的先序遍历与其转换的相应的二叉树的先序遍历的结果序列相同;树的后序遍历与其转换的二叉树的中序遍历的结果序列相同;树的层序遍历与其转换的二叉树的后序遍历的结果序列相同。由森林与二叉树的转换关系以及森林与二叉树的遍历定义可知,森林的先序遍历和中序遍历与所转换得到的二叉树的先序遍历和中序遍历的结果序列相同。
10、哈夫曼树:我们称判定过程最优的二叉树为哈夫曼树,又称最优二叉树。
路径: 树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
路径长度:路径上的分枝数目称作路径长度。树的路径长度:从树根到每一个结点的路径长度之和。
结点的带权路径长度:在一棵树中,如果其结点上附带有一个权值,通常把该结点的路径长度与该结点上的权值之积称为该结点的带权路径长度。
树的带权路径长度:如果树中每个叶子上都带有一个权值,则把树中所有叶子的带权路径长度之和称为树的带权路径长度。其中带权路径长度最小的二叉树就称为哈夫曼树或最优二叉树。
哈夫曼树的构造:
按照构造哈夫曼树得到的编码成为哈夫曼编码。常用于压缩。