1.基本概念
结点的度:结点拥有的子树的个数
2.树的存储结构
树的存储结构在C++中有:双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法
但自己当转化为java程序时,却发现这些表示法在java中没有区别,不知道是不是自己理解错了
以下给出结点的程序:
package tree;
public class PTNode<T extends Comparable<T>> {
T key; //关键字(键值)
PTNode<T> parent; //双亲域,根节点没有双亲,根节点的双亲域为None
PTNode<T> firstchild; //长子域(最左边的孩子)
PTNode<T> rightchild; //右兄弟域
public PTNode(T key, PTNode<T> parent, PTNode<T> firstchild, PTNode<T> rightchild){
this.key = key;
this.parent = parent;
this.firstchild = firstchild;
this.rightchild = rightchild;
}
public T getKey(){
return key;
}
}
3.二叉树
特殊二叉树:
①斜树:所有结点只有左子树的二叉树称为左斜树、所有结点只有右子树的二叉树称为右斜树。
②满二叉树:所有分支结点都存在左子树和右子树,所有叶子节点在同一层上。
③完全二叉树:对一个具有n个节点的二叉树按层序编号,如果编号为i的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
完全二叉树的判别特点:
- 叶子结点只能出现在最下两层
- 最下层的叶子结点一定集中在左部连续位置
- 导数第二层,若有叶子结点,一定都在右部连续位置
- 如果结点度为1,则该节点只有左孩子,即不存在只有右子树的情况
- 同样结点的二叉树,完全二叉树的深度最小
二叉树的性质:(懒的打了,待完成)
- 在二叉树的第i层上至多有个节点()
- 深度为k的二叉树至多有个节点
- 二叉树的终端节点数等于度为2的节点数加1:
- 具有n个结点的完全二叉树的深度为
4.线索二叉树
解释:将叶子结点的左右孩子指向中序遍历中该结点的前驱和后继的一种特殊 二叉树。
使用情况:所用的二叉树需经常遍历或查找节点时需要魔宗遍历序列中的前驱和后继。(感觉应该很少需要用到)
下面给出程序如下:
package tree;
/*
说明:线索二叉树程序
包含:加头结点的线索二叉树和不加头结点的线索二叉树两种遍历方式(只包含中序遍历方式)
包含函数:
createBinaryTree 通过数组构造一个二叉树(完全二叉树)
inThreadOrder 中序遍历线索化二叉树(博客上的程序)
inThreadList 中序遍历线索二叉树,按照后继方式遍历
InThreading 中序遍历线索化二叉树(依据书来写的程序,与inThreadOrder本质上没有区别)
AddHeadNode 添加头结点程序
InOrderTraverse_Thr 线索化后的中序遍历(带头结点)
main 测试程序
by Miles Wen
时间:2018年11月14日
参考:https://blog.csdn.net/UncleMing5371/article/details/54176252
《大话数据结构》
*/
public class BiThrTree {
private BiThrNode preNode; //线索化时记录前一个结点
private BiThrNode root;
private static final boolean link =false; //定义左右孩子、前驱后继标志
private static final boolean thread = true;
//通过数组构造一个二叉树(完全二叉树)
static BiThrNode createBinaryTree(String[] array, int index){
BiThrNode node = null;
if(index < array.length){
node = new BiThrNode(array[index]);
node.lchild = createBinaryTree(array, index*2+1);
node.rchild = createBinaryTree(array,index*2+2);
}
return node;
}
//中序遍历线索化二叉树
void inThreadOrder(BiThrNode node){
// BiThrNode preNode = null;
if(node == null) {
return;
}
//处理左子树
inThreadOrder(node.lchild);
//左指针为空,将左指针指向前驱节点
if(node.lchild == null) {
node.lchild = preNode;
node.ltag = true;
}
//前一个节点的后继节点指向当前节点
if(preNode != null && preNode.rchild == null) {
preNode.rchild = node;
preNode.rtag = true;
}
preNode = node;
//处理右子树
inThreadOrder(node.rchild);
}
//中序遍历线索二叉树,按照后继方式遍历(思路:找到最左子节点开始)
void inThreadList(BiThrNode node){
//找中序遍历方式开始的结点
while(node != null && !node.ltag){
node = node.lchild;
}
while(node != null){
System.out.print(node.key + ", ");
//如果右指针为线索
if(node.rtag){
node = node.rchild;
}else{ //如果佑指针不是线索
node = node.rchild;
while(node != null && !node.ltag){
node = node.lchild;
}
}
}
}
/***************************************************************
带头结点的线索二叉树
正确使用顺序:建树 -> 线索化 -> 添加头结点 -> 带头结点的中序遍历
****************************************************************/
//中序遍历线索化
public void InThreading(BiThrNode root){
if(root != null){
InThreading(root.lchild); //递归左子树线索化
if(root.lchild == null){ //没有左孩子
root.lchild = preNode; //左孩子指向前驱
root.ltag = thread; //前驱线索
}
/*注意下面这个if里面的条件总是报错,必须要有preNode!= null的判断,而且要在
* preNode.rchild == null的前面,因为如果preNode是null,那么去调用preNode的属
* 性就会出错
* 在书中C++用的是!preNode.rchild,猜测可能是C++与java的区别,因为!在java
* 中不能用于一个类对象,而C++是用指针实现的,而指针实质上是unsigned long int
* 记得好像是,可以用!操作符*/
if(preNode!= null && preNode.rchild == null){ //前驱没有右孩子
preNode.rchild = root; //前驱右孩子指向后继
preNode.rtag = thread; //后继线索
}
preNode = root; //保持pre为root前驱
InThreading(root.rchild); //递归右子树线索化
}
}
//添加头结点程序
public BiThrNode AddHeadNode(BiThrNode root){
BiThrNode head = new BiThrNode(null);
head.lchild = root;
head.ltag = link;
//找到中序遍历的第一个结点
BiThrNode Node = root;
while(Node.lchild != null){
Node = Node.lchild;
}
Node.lchild = head;
//找到中序遍历的最后一个结点
Node = root;
while(Node.rchild != null){
Node = Node.rchild;
}
head.rchild = Node;
head.rtag = thread;
Node.rchild = head;
return head;
}
//线索化后的中序遍历(带头结点)
public boolean InOrderTraverse_Thr(BiThrNode T){
BiThrNode p;
p = T.lchild; //T是头指针,所以T.lchild应该是根节点
while(p != T){ //空树或遍历结束时p==T
while(p.ltag == link) { //当ltag = 0时循环到中序序列第一个结点
p = p.lchild;
}
System.out.print(p.key+" "); //对结点的操作,这里暂时写为打印数据
while(p.rtag == thread && p.rchild != T){
p = p.rchild;
System.out.print(p.key+" ");
}
p = p.rchild; //p进至其右子树根
}
return true;
}
//测试程序
public static void main(String[] args){
String[] array = {"A","B","C","D","E","F","G","H"};
BiThrNode root = createBinaryTree(array, 0);
BiThrTree tree = new BiThrTree();
//tree.inThreadOrder(root);
tree.InThreading(root);
System.out.println("中序按后继结点遍历线索二叉树结果:");
tree.inThreadList(root);
System.out.println("\n加入头结点后:");
BiThrNode head = tree.AddHeadNode(root);
tree.InOrderTraverse_Thr(head);
}
}
节点定义如下:
package tree;
/*线索二叉树节点*/
public class BiThrNode<T extends Comparable<T>> {
T key;
BiThrNode<T> lchild; //左孩子
BiThrNode<T> rchild; //右孩子
boolean ltag = false;
boolean rtag = false;
public BiThrNode(T key, BiThrNode<T> lchild, BiThrNode<T> rchild,boolean ltag, boolean rtag){
this.key = key;
this.lchild = lchild;
this.rchild = rchild;
this.ltag = ltag;
this.rtag = rtag;
}
public BiThrNode(T key){
this.key = key;
}
}
与传统二叉树的区别是,添加了标记属性用于区分该节点左右孩子属性为孩子还是前驱、后继。