树我觉得是数据结果中一个比较繁琐的内容了,树的表达方式有很多种,什么双亲表示法,什么双亲孩子表示法啥的,树的遍历方式也多种多样,前序,中序,后序,层序等等,今天介绍的就是树的两种表示方法。
这个学期学了数据结构这本书,所以我打算用Java实现其中表,队,栈,树。
今天主要介绍的如何实现两种树表现方式,双亲表示法和孩子链表表示法。
双亲表示法
顾名思义就是树中每个除根节点以外的每个节点都有父节点,当我们为每个节点添加一个父节点记录父节点时,这就是孩子的双亲表示法。说白了就是为每个节点添加一个父节点属性,以此来记录父节点,方便查找,当然除了根节点以外,毕竟根节点没有父节点。
下面就是具体步骤了:
创建节点类Node:
class Node<T>{
T data; //节点元素
int parent; //父节点位置
public Node() { }
public Node(T data,int parent){
this.data=data;
this.parent=parent;
}
@Override
public String toString() {
return this.data+"";
}
}
然后创建ParentTree类,并实现一系列方法,就OK了:
//孩子的双亲表示法
public class ParentTree<T>{
private int treeSize=0; //自己指定的树大小
private Node<T>[] nodes; //用一个Node数组记录树里面所有节点
private int nodeNums; //记录节点数
public ParentTree() {}
//以指定的根节点和大小创建树
public ParentTree(T data,int treeSize){
this.treeSize=treeSize;
nodes=new Node[treeSize];
nodes[0]=new Node(data, -1);
nodeNums++;
}
//添加节点
public void addNode(T data,Node parent){
if(nodeNums==treeSize){
try {
throw new Exception("树已满");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int i=0;i<treeSize;i++)
{
//第一个为null的地方添加节点
if(nodes[i]==null){
nodes[i]=new Node(data, getNodeIndex(parent));
nodeNums++;
break; //添加后就结束循环
}
}
}
//找到指定节点对应的序号
public int getNodeIndex(Node node){
for(int i=0;i<treeSize;i++)
{
if(nodes[i]==node){
return i;
}
}
return -1;
}
//判断树是否为空
public boolean isEmpty(){
//判断根节点是否为空
return nodes[0]==null;
}
//获得指定节点的子节点
public List<Node<T>> getAllChildren(Node parent){
List<Node<T>> list=new ArrayList<>();
for(int i=0;i<treeSize;i++)
{
if(nodes[i].parent!=-1&&nodes[i].parent==getNodeIndex(parent)){
list.add(nodes[i]);
}
}
return list;
}
//获得节点深度,通过获取当前节点,并初始化深度等于1,再向上找父节点,每找到一个父节点深度加1
public int getDeep(){
int maxDeep=0; //最大深度
for(int i=0;i<treeSize&&nodes[i]!=null;i++)
{
//初始化当前结点深度
int index=1;
//获得其父节点位置
int m=nodes[i].parent;
while(m!=-1&&nodes[m]!=null){
m=nodes[m].parent; //继续向上搜索父节点
index++;
}
if(maxDeep<index){
maxDeep=index;
}
}
return maxDeep;
}
//获得根节点
public Node getRoot(){
return nodes[0];
}
}
这样一颗树就可以轻松表示出来了,但从代码可以看出,这种结构的特点:
这种结构却可以轻松找出指定节点的父节点,但当要找出某节点的所有子节点需要遍历整个树,时间花费长。
孩子链表表示法
孩子链表表示法,是一种用多个单链表表示树的方法,即把每个节点孩子排列起来,看成是一个线性表,且以单链表存储。如下图所示:
在孩子链表表示法中存在两类节点:孩子节点和表头节点。右边的所有节点,比如3->5即子节点链表,子节点链表中包含的所有子节点都是孩子节点,需要记录数组下标和下一个节点是什么。
两类节点实现如下:
class SonNode<T>{
//记录当前节点位置,即上图中数组下标
public int pos;
public SonNode next; //孩子节点的下一个
public SonNode(int pos,SonNode next){
this.pos=pos;
this.next=next;
}
}
//表头节点
class Node<T>{
T data;
SonNode first; //子节点链表的头节点
public Node(T data, SonNode sonNode) {
super();
this.data = data;
this.first = sonNode;
}
@Override
public String toString() {
return this.data+"";
}
}
创建树并实现所有方法:
//孩子链表表示法
public class ChildTree<T> {
private int treeSize=0;//树大小
Node[] nodes; //一个数组存储所有表节点
private int nums=0;
//以指定元素和大小创建一棵树
public ChildTree(int treeSize,T data){
this.treeSize=treeSize;
nodes=new Node[treeSize];
Node node=new Node(data, null);
nodes[0]=node;
nums++;
}
//添加节点
public void addNode(T data,Node parent){
//判断树是否满
if(nums==treeSize){
try {
throw new Exception("树已满");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果父节点不为空
if(parent!=null){
for(int i=0;i<treeSize;i++)
{
//查找到第一个为null的位置,再添加节点
if(nodes[i]==null)
{
Node node=new Node(data, null);
SonNode sonNode=new SonNode(i, null);
if(parent.first==null){
//如果指定节点没有子节点
parent.first=sonNode;
}else{
//如果有子节点则一直遍历到子节点为空的地方,添加子节点
SonNode sNode=parent.first;
while(sNode.next!=null){
sNode=sNode.next;
}
sNode.next=sonNode;
}
nodes[i]=node;
nums++;
break; //一定要结束循环,不然会一直下去
}
}
}
}
//返回指定节点的所有子节点
public List<Node> getALLChildNodes(Node parent){
List<Node> list=new ArrayList<>();
if(parent.first==null){
return null;
}else{
SonNode sonNode=parent.first;
while(sonNode!=null){
list.add(nodes[sonNode.pos]);
sonNode=sonNode.next;
}
}
return list;
}
//获得节点总数
public int getNodesNums(){
return nums;
}
//判断是否为空
public boolean isEmpty(){
return nodes[0]==null;
}
//递归获取深度
private int deep(Node node){
if(node.first==null){
return 1;
}else{
int max=0; //最大的深度
SonNode next=node.first;
while(next!=null){
//递归遍历
int def=deep(nodes[next.pos]);
if(def>max){
max=def;
}
next=next.next;
}
//因为获得的是节点的子树深度,所以还要加1
return max+1;
}
}
//获得树的深度
public int getDeep(){
return deep(nodes[0]);
}
}
通过上面代码可以看出(如果自己实现了),孩子链表表示法特点是:每个节点都可以快速找到它的子节点,但是找某节点父节点就需要遍历整个节点数组。
建议小伙伴认真实现下上面两种操作,当自己能亲手实现后才真正了解了这两种表达方式的特点和实现方式。