Java基础 - 树的实现(二)子节点链表示法

本文介绍了子节点链表示法的基本概念及实现方法,详细解释了如何通过子节点链表示法来存储树形结构的数据,并提供了具体的代码示例。
子节点链表示法:

父节点表示法的思想是让每个节点“记住”它的父节点的索引,父节点表示法是从子节点入手的;反过来,还有另外一种方式:让父节点“记住”它的所有子节点。在这种方式下,由于每个父节点需要记住多个子节点,因此必须采用”子节点链“表示法。在这种表示法下,对于下面的树的采用的保存结构如下图所示:


从上面的图可以看出,采用子节点链表示法来记录树时,需要为每个节点维护一个子节点链,通过该子节点链来记录该节点的所有子节点。下面程序实现了树的子节点链表示法:

import java.util.ArrayList;
import java.util.List;

/**
 * 子节点链表示法
 * @author WB
 *
 * @param <E>
 */
public class TreeChild <E>{
	//用于保存子节点链
	private class SonNode{
		//记录自己的索引位置
		private int pos;
		private SonNode next;
		public SonNode(int pos, SonNode next){
			this.pos = pos;
			this.next = next;
		}
	}
	//用于保存节点
	public class Node <T>{
		T data;
		//每个节点都有可能有子节点,指向第一个可能的子节点
		SonNode first;
		public Node(){
		}
		public Node(T data, SonNode first){
			this.data = data;
			this.first = first;
		}
		public String toString(){
			return first != null ? "TreeChild$Node[data = " + data + ", first = " + first.pos + "]" :
				"TreeChild$Node[data = " + data + ", first = -1]";
		}
	}
	
	private final int DEFAULT_TREE_SIZE = 100;
	//容量
	private int treeSize = 0;
	//底层数组
	private Node<E>[] nodes;
	
	private int nodeNums;
	
	@SuppressWarnings("unchecked")
	public TreeChild(){
		treeSize = DEFAULT_TREE_SIZE;
		nodes = new Node[treeSize];
	}
	public TreeChild(E data){
		this();
		nodes[0] = new Node<E>(data, null);
		nodeNums ++;
	}
	@SuppressWarnings("unchecked")
	public TreeChild(E data, int initSize){
		treeSize = initSize;
		nodes = new Node[treeSize];
		nodes[0] = new Node<E>(data, null);
		nodeNums ++;
	}
	
	//判断是否为空
	public boolean isEmpty(){
		return nodes[0] == null;
	}
	
	//返回根节点
	public Node<E> root(){ 
		return nodes[0];
	}
	
	//为指定节点添加子节点
	public void add(E data, Node<?> parent){
		if(pos(parent) == -1){
			throw new RuntimeException("无法找到该节点。");
		}
		else{
			for(int i = 0;i < treeSize; i ++){
				//找到第一个为空的位置存放新添加的子节点
				if(nodes[i] == null){
					Node<E> newNode = new Node<E>(data, null);
					nodes[i] = newNode;
					
					if(parent.first == null){
						SonNode sonNode = new SonNode(i, null);
						parent.first = sonNode;
					}
					else{//有子节点链
						SonNode next = parent.first;
						while(next.next != null){
							next = next.next;
						}
						next.next = new SonNode(i, null);
						
					}
					
					nodeNums ++;
					return;
						
				}
			}
			
			throw new RuntimeException("该树已满,无法添加新节点!");
		}
	}
	
	//获得指定节点(非叶子节点)的所有子节点
	public List<Node<E>> children(Node<?> parent){
		List<Node<E>> list = new ArrayList<Node<E>>();
		
		SonNode next = parent.first;
		
		while(next != null){
			list.add(nodes[next.pos]);
			next = next.next;
		}
		
		return list;
	}
	
	//获取指定节点(非叶子节点)的第 i 个子节点
	public Node<E> child(int index, Node<?> parent){ 
		SonNode next = parent.first;
		
		for(int i = 1; next != null; i ++){
			if(i == index){
				return nodes[next.pos];
			}
			next = next.next;
		}
		return null;
	}
	
	//返回树的深度
	public int deep(){
		return deepHelp( root() );
	}
	
	//这是一个递归方法:每柯子树的深度是其所有子树的最大深度 +1
	private int deepHelp(Node<?> parent){
		if(parent.first == null){
			return 1;
		}
		else{
			int max = 0;
			SonNode next = parent.first;
			
			while(next != null){
				int tmp = deepHelp(nodes[next.pos]);
				
				if(tmp > max){
					max = tmp;
				}
				
				next = next.next;
			}
			
			return max + 1;
		}
	}
	
	private int pos(Node<?> parent){
		for(int i = 0; i < treeSize; i ++){
			if(nodes[i] == parent){
				return i;
			}
		}
		return -1;
	}
}

从上面的程序来看,程序定义树节点时添加了一个first域。该first域用于保存该节点的子节点链的引用。通过这种方法即可保存树中的节点之间的父子关系。使用这种子节点链表示法来存储树时,添加节点时只需找到指定父节点的子节点链的最后节点,并让他指向新增的节点即可。

下面是测试代码:

import java.util.List;

import com.yc.tree.TreeChild;

public class TreeChildTest {
	public static void main(String[] args) {
		 TreeChild<String> tree = new TreeChild<String>("root");
		 System.out.println("根节点:" + tree.root());
		 
		 
		 tree.add("节点1", tree.root());
		 tree.add("节点2", tree.root());
		 tree.add("节点3", tree.root());
		 System.out.println( "添加子节点后的根节点为:" + tree.root());
		 System.out.println( "此时树的深度为:" + tree.deep());
		 
		 //获取根节点下的所有子节点
		 List<TreeChild<String>.Node<String>> list = tree.children(tree.root());
		 System.out.println( "根节点的第一个子节点为:" + list.get(0));
		 System.out.println( "或者用child()方法得到根节点的第一个子节点为:" + tree.child(1, tree.root()));
		 
		 //为根节点的第一个子节点添加新的子节点
		 tree.add("节点4", list.get(0));
		 System.out.println( " 此时树的深度为:" + tree.deep());
		 System.out.println( " 根节点的第一个子节点的第一个字节点为:" + tree.child(1, tree.child(1, tree.root())));
		 
	}
}

测试结果如下:


通过上面介绍可知,子节点链表示法的特点是:每个节点都可以快速的找到它的所有的子节点。但如果找找到某个节点的父节点则比较麻烦,程序要遍历整个节点数组。

/* * 基于实现结构 */ package dsa; public class TreeLinkedList implements Tree { private Object element;//根节点 private TreeLinkedList parent, firstChild, nextSibling;//父亲、长子及最大的弟弟 //(单节点)构造方法 public TreeLinkedList() { this(null, null, null, null); } //构造方法 public TreeLinkedList(Object e, TreeLinkedList p, TreeLinkedList c, TreeLinkedList s) { element = e; parent = p; firstChild = c; nextSibling = s; } /*---------- Tree接口中各方法的实现 ----------*/ //返回当前节点中存放的对象 public Object getElem() { return element; } //将对象obj存入当前节点,并返回此前的内容 public Object setElem(Object obj) { Object bak = element; element = obj; return bak; } //返回当前节点的父节点;对于根节点,返回null public TreeLinkedList getParent() { return parent; } //返回当前节点的长子;若没有孩子,则返回null public TreeLinkedList getFirstChild() { return firstChild; } //返回当前节点的最大弟弟;若没有弟弟,则返回null public TreeLinkedList getNextSibling() { return nextSibling; } //返回当前节点后代元素的数目,即以当前节点为根的子的规模 public int getSize() { int size = 1;//当前节点也是自己的后代 TreeLinkedList subtree = firstChild;//从长子开始 while (null != subtree) {//依次 size += subtree.getSize();//累加 subtree = subtree.getNextSibling();//所有孩子的后代数目 } return size;//即可得到当前节点的后代总数 } //返回当前节点的高度 public int getHeight() { int height = -1; TreeLinkedList subtree = firstChild;//从长子开始 while (null != subtree) {//依次 height = Math.max(height, subtree.getHeight());//在所有孩子中取最大高度 subtree = subtree.getNextSibling(); } return height+1;//即可得到当前节点的高度 } //返回当前节点的深度 public int getDepth() { int depth = 0; TreeLinkedList p = parent;//从父亲开始 while (null != p) {//依次 depth++; p = p.getParent();//访问各个真祖先 } return depth;//真祖先的数目,即为当前节点的深度 } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值