package com.yc.tree;
/**
*
* @author wb
* @param <E>
*
* _____________________
* │ │ │
* │ ● │ data │ ● │
* │____│______│____│
* │ │
* ↓left ↓right
*
* 二叉链表存储的思想是让每个节点都能“记住”它的左、右两个子节点。为每个节点增加left、right两个指针,分别引用该节点的左、右两个子节点。
* 因此二叉链表存储的每个节点有如下图所示的结构:
*
* 二叉链表存储的二叉树节点有如下定义
* class Node{
* Object data;
* Node left;
* Node right;
* }
* 对于这种二叉链表存储的二叉树,如果程序需要,为指定节点添加子节点也非常容易,让父节点的left或right引用指向新节点即可。
*
* 下面程序加粗的字体代码就是二叉链表存储的关键,二叉树中每个节点保留left、right两个引用,分别指向该节点的左右两个子节点。
* 通过这种方式即可建立二叉树各个节点之间的父子关系。
*
* 下面是程序实现:
*/
public class TwoLinkBinTree <E> {
public static class Node{
Object data;
Node left;
Node right;
public Node(){
}
public Node(Object data){
this.data = data;
}
public Node(Object data, Node left, Node right){
this.data = data;
this.left = left;
this.right = right;
}
}
private Node root;
public TwoLinkBinTree(){
this.root = new Node();
}
public TwoLinkBinTree(Object data){
this.root = new Node(data);
}
//根据根节点中的data判断是否为空树
public boolean isEmpty(){
return root.data == null;
}
//返回根节点
public Node root(){
exceptionForRoot();
return root;
}
/**
* 为指定节点添加子节点
* @param parent :指定节点
* @param data :数据元素
* @param isLeft :是否是左子节点
*/
public void add(Node parent, Object data, boolean isLeft){
exceptionForAdd(parent, isLeft);
Node newNode = new Node(data);
if(isLeft){
parent.left = newNode;
}else{
parent.right = newNode;
}
}
/*
* 向指定节点添加子节点并返回该子节点
*/
public Node addAndReturn(Node parent, Object data, boolean isLeft){
exceptionForAdd(parent, isLeft);
Node newNode = new Node(data);
if(isLeft){
parent.left = newNode;
}else{
parent.right = newNode;
}
return newNode;
}
//返回非根节点的父节点
public Node parent(Node node){
exceptionForParent(node);
//采用二叉链表存储的树要返回某节点的父节点需遍历整个二叉树,但由于它是链式的,所以遍历起来非常不方便。
//对于链式存储的树的遍历在后面会讲。
return null;
}
//返回左子节点
@SuppressWarnings("unchecked")
public E leftChild(Node node){
exceptionForLeftChild(node);
return node.left == null ? null : (E)node.left.data;
}
//返回右子节点
@SuppressWarnings("unchecked")
public E rightChild(Node node){
exceptionForRightChild(node);
return node.right == null ? null : (E)node.right.data;
}
//返回该树的深度
public int deep(){
return deep(root);
}
private int deep(Node node) {
if(node == null){
return 0;
}
if(node.left == null && node.right == null){
return 1;
}else{
int deepRight = deep(node.right);
int deepLeft = deep(node.left);
return deepLeft > deepRight ? deepLeft + 1 : deepRight + 1;
}
}
private void exceptionForAdd(Node parent, boolean isLeft){
if(parent == null){
throw new RuntimeException("指定节点在二叉链表中不存在,无法为其添加子节点");
}
if(isLeft && parent.left != null){
throw new RuntimeException("指定节点在二叉链表中已存在左子节点");
}
if(!isLeft && parent.right != null){
throw new RuntimeException("指定节点在二叉链表中已存在右子节点");
}
}
private void exceptionForRoot(){
if(isEmpty()){
throw new RuntimeException("该树为空树,无根节点");
}
}
private void exceptionForParent(Node node){
if(node == root){
throw new RuntimeException("该节点为根节点,无父节点");
}
if(node == null){
throw new RuntimeException("该节点为null,无父节点");
}
}
private void exceptionForLeftChild(Node node){
if(node == null){
throw new RuntimeException("该节点为null,无左子节点");
}
}
private void exceptionForRightChild(Node node) {
if(node == null){
throw new RuntimeException("该节点为null,无右子节点");
}
}
}
测试代码如下:
/**
* 对于这种二叉链表的二叉树,因为程序采用链表来记录树中所有节点,所以添加节点没有限制,而且不会像顺序存储那样产生大量的空间浪费。
* 当然,这种二叉链表的存储方式在遍历树节点时的效率不高,指定节点访问其父节点是也是比较苦难,程序必须采用遍历二叉树的方式来搜索其父节点。
* 为了克服二叉链表存储方式中访问父节点不方便的问题,可以将二叉链表扩展成三叉链表。
*
* **/
public class TwoLinkBinTreeTest {
@SuppressWarnings("unused")
public static void main(String[] args) {
TwoLinkBinTree<String> tree = new TwoLinkBinTree<String>("根节点");
//依次添加节点
Node n2_l = tree.addAndReturn(tree.root(), "第二层左节点", true);
Node n2_r = tree.addAndReturn(tree.root(), "第二层右节点", false);
Node n3_l = tree.addAndReturn(n2_r, "第三层左节点", true);
Node n3_r = tree.addAndReturn(n2_r, "第三层右节点", false);
Node n4_r = tree.addAndReturn(n3_r, "第四层右节点", false);
System.out.println("n2_r的左子节点为:" + tree.leftChild(n2_r));
System.out.println("n2_r的右子节点为:" + tree.rightChild(n2_r));
System.out.println( "该树此时的深度为:" + tree.deep());
}
}
测试结果如下:
n2_r的左子节点为:第三层左节点
n2_r的右子节点为:第三层右节点
该树此时的深度为:4