普通树简介以及Java代码实现

时间:2022-06-10
本文章向大家介绍普通树简介以及Java代码实现,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1. 树相关的概念

  • 根节点:没有父节点的节点(图中A、1)
  • 叶子节点:没有子节点的节点(图中B、D、3、5)
  • 普通节点:有子节点的节点(图中C、2、4)
  • 节点的度(degree):节点拥有的子树的个数称为该节点的度(例如,C节点的度为2,2节点的度为3)
  • 树的度:树中所有节点的度的最大值(例如,左边的树的度为3)
  • 节点的层次(level):节点的层次从根开始算起,根的层次为1,其余节点的层次值为其父节点的层次值加1
  • 数的深度(depth):树中节点的最大层次值称为树的深度(例如,左边的树的深度为3,右边的树的深度为4)
  • 森林:两颗或两颗以上互不相交的树的集合

2. 用代码实现表示一棵树的方法

范例树

(1) 父节点表示法

数组索引

data

parent

0

A

-1

1

B

0

2

C

0

3

D

0

4

E

1

5

F

3

6

G

3

7

H

4

8

I

4

9

J

4

10

K

6

以下程序采用父节点表示法实现了一棵树:

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

/**
 * @Description: 使用父节点表示法实现一棵树
 * @author Jed
 * @date 2018年1月31日
 * @param <E>
 */
public class TreeParent<E> {
    
    public static class Node<T> {
        
        T data; // 节点中存放的数据
        int parent; // 节点的父节点的索引
        
        public Node() {}
        
        public Node(T data) {
            this.data = data;
        }
        
        public Node(T data, int parent) {
            this.data = data;
            this.parent = parent;
        }
        
        @Override
        public String toString() {
            return "TreeParent$Node [data=" + data + ", parent=" + parent + "]";
        }
    }
    
    private final int DEFAULT_TREE_SIZE = 100; // 默认的树的大小
    private int treeSize = 0;
    private Node<E>[] nodes; // 使用一个Node[]数组来记录该树中的所有节点
    private int nodeNums; // 记录节点数
    
    /**
     * 指定根节点创建树
     */
    public TreeParent(E data) {
        treeSize = DEFAULT_TREE_SIZE;
        nodes = new Node[treeSize];
        nodes[0] = new Node<E>(data, -1);
        nodeNums++;
    }
    
    /**
     * 指定根节点、指定treeSize创建树
     */
    public TreeParent(E data, int treeSize) {
        this.treeSize = treeSize;
        nodes = new Node[treeSize];
        nodes[0] = new Node<E>(data, -1);
        nodeNums++;
    }
    
    /**
     * 获取某个节点的索引值
     */
    public int getNodeIndex(Node node) {
        for(int i = 0; i < treeSize; i++) {
            // 找到指定节点
            if(nodes[i] == node) {
                return i;
            }
        }
        return -1;
    }
    
    /**
     * 为指定节点添加子节点
     */
    public void addNode(E data, Node parent) {
        for(int i = 0; i < treeSize; i++) {
            if(nodes[i] == null) {
                // 创建新节点,并用指定的数组元素保存它
                nodes[i] = new Node(data, getNodeIndex(parent));
                nodeNums++;
                return;
            }
        }
        throw new RuntimeException("该树已满,无法添加新节点");
    }
    
    /**
     * 判断树是否为空
     */
    public boolean isEmpty() {
        return nodes[0] == null;
    }
    
    /**
     * 返回根节点
     */
    public Node<E> getRoot() {
        return nodes[0];
    }
    
    /**
     * 返回指定节点(非根节点)的父节点
     */
    public Node<E> getParent(Node node) {
        // 每个节点的parent记录了其父节点的索引
        return nodes[node.parent];
    }
    
    public Node<E> getNodeByData(E data) {
        for(int i = 0; i < treeSize; i++) {
            if(nodes[i].data == data) {
                return nodes[i];
            }
        }
        System.out.println("树中不存在包含该数据的节点");
        return null;
    }
    
    /**
     * 返回指定节点(非叶子节点)的所有子节点
     */
    public List<Node<E>> getChildren(Node parent) {
        List<Node<E>> list = new ArrayList<>();
        for(int i = 0; i < treeSize; i++) {
            if(nodes[i] != null && nodes[i].parent == getNodeIndex(parent)) {
                list.add(nodes[i]);
            }
        }
        return list;
    }
    
    /**
     * 返回树的深度
     */
    public int getDeep() {
        int max = 0; // 用于记录节点的最大层次值
        for(int i = 0; i < treeSize && nodes[i] != null; i++) {
            int def = 1; // 初始化本节点的深度
            int m = nodes[i].parent; // 当前节点的父节点的索引
            // 如果其父节点存在
            while(m != -1 && nodes[m] != null) {
                // 继续向上搜索父节点
                m = nodes[m].parent;
                def ++;
            }
            if(max < def) {
                max = def;
            }
        }
        return max;
    }
    
}
import java.util.List;

public class TreeParentTest {
    
    public static void main(String[] args) {
        TreeParent<String> tree = new TreeParent<String>("root");
        TreeParent.Node<String> root = tree.getRoot();
        System.out.println(root);
        tree.addNode("A", root);
        tree.addNode("B", root);
        TreeParent.Node<String> A = tree.getNodeByData("A");
        TreeParent.Node<String> B = tree.getNodeByData("B");
        tree.addNode("C", A);
        tree.addNode("D", A);
        tree.addNode("E", B);
        tree.addNode("F", B);
        System.out.println("树的深度为:" + tree.getDeep());
        List<TreeParent.Node<String>> list = tree.getChildren(A);
        System.out.println("A节点的子节点为:");
        for(TreeParent.Node<String> node : list) {
            System.out.println(node);
        }
        
    }

}

结果:
TreeParent$Node [data=root, parent=-1]
树的深度为:3
A节点的子节点为:
TreeParent$Node [data=C, parent=1]
TreeParent$Node [data=D, parent=1]

父节点表示法的特点:

  • 每个节点可以快速找到父节点
  • 找某个节点的子节点需要遍历整颗树

(2) 子节点链表示法

数组索引

data

child

0

A

1 -> 2 -> 3

1

B

4

2

C

3

D

5 -> 6

4

E

7 -> 8 -> 9

5

F

6

G

10

7

H

8

I

9

J

10

K

以下程序采用子节点链表示法实现了一棵树:

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

/**
 * @Description: 使用子节点链表示法实现一棵树
 * @author Jed
 * @date 2018年1月31日
 */
public class TreeChild<E> {
    
    private static class SonNode {
        
        private int pos; // 记录当前节点的位置
        private SonNode next;
        
        public SonNode(int pos, SonNode next) {
            this.pos = pos;
            this.next = next;
        }
    }
    
    public static class Node<T> {
        
        T data;
        SonNode first; // 记录第一个子节点
        
        public Node(T data) {
            this.data = data;
            this.first = null;
        }
        
        @Override
        public String toString() {
            if(first != null) {
                return "TreeChild$Node [data=" + data + ", first=" + first.pos + "]";
            }else {
                return "TreeChild$Node [data=" + data + ", first=-1]";
            }
        }
    }
    
    private final int DEFAULT_TREE_SIZE = 100; // 默认的树的大小
    private int treeSize = 0;
    private Node<E>[] nodes; // 使用一个Node[]数组来记录该树中的所有节点
    private int nodeNums; // 记录节点数
    
    /**
     * 指定根节点创建树
     */
    public TreeChild(E data) {
        treeSize = DEFAULT_TREE_SIZE;
        nodes = new Node[treeSize];
        nodes[0] = new Node<E>(data);
        nodeNums++;
    }
    
    /**
     * 指定根节点、指定treeSize创建树
     */
    public TreeChild(E data, int treeSize) {
        this.treeSize = treeSize;
        nodes = new Node[treeSize];
        nodes[0] = new Node<E>(data);
        nodeNums++;
    }
    
    /**
     * 获取某个节点的索引值
     */
    public int getNodeIndex(Node node) {
        for(int i = 0; i < treeSize; i++) {
            // 找到指定节点
            if(nodes[i] == node) {
                return i;
            }
        }
        return -1;
    }
    
    /**
     * 为指定节点添加子节点
     */
    public void addNode(E data, Node parent) {
        for(int i = 0; i < treeSize; i++) {
            // 找到数组中第一个为null的元素,该元素保存新节点
            if(nodes[i] == null) {
                // 创建新节点,并用指定的数组元素保存它
                nodes[i] = new Node(data);
                if(parent.first == null) {
                    parent.first = new SonNode(i, null);
                }else {
                    SonNode next = parent.first;
                    while(next.next != null) {
                        next = next.next;
                    }
                    next.next = new SonNode(i, null);
                }
                nodeNums++;
                return;
            }
        }
        throw new RuntimeException("该树已满,无法添加新节点");
    }
    
    /**
     * 判断树是否为空
     */
    public boolean isEmpty() {
        return nodes[0] == null;
    }
    
    /**
     * 返回根节点
     */
    public Node<E> getRoot() {
        return nodes[0];
    }
    
    /**
     * 返回指定节点(非叶子节点)的所有子节点
     */
    public List<Node<E>> getChildren(Node parent) {
        List<Node<E>> list = new ArrayList<>();
        // 获取parent节点的第一个子节点
        SonNode next = parent.first;
        // 沿着孩子链不断搜索下一个孩子节点
        while(next != null) {
            // 添加孩子链中的节点
            list.add(nodes[next.pos]);
            next = next.next;
        }
        return list;
    }
    
    /**
     * 返回指定节点(非叶子节点)的第index个子节点
     */
    public Node<E> getChild(Node parent, int index) {
        // 获取parent节点的第一个子节点
        SonNode next = parent.first;
        // 沿着孩子链不断搜索下一个孩子节点
        for(int i = 0; next != null; i++) {
            if(index == i) {
                return nodes[next.pos];
            }
            next = next.next;
        }
        return null;
    }
    
    // 这是一个递归方法,每颗子树的深度为其所有子树的深度 +1
    private int deep(Node node) {
        if(node.first == null) {
            return 1;
        }else {
            // 记录其所有子树的最大深度
            int max = 0;
            SonNode next = node.first;
            // 沿着孩子链不断搜索下一个孩子节点
            while(next != null) {
                // 获取以其子节点为根的子树的深度
                int tmp = deep(nodes[next.pos]);
                if(tmp > max) {
                    max = tmp;
                }
                next = next.next;
            }
            return max + 1;
        }
    }
    
    /**
     * 返回该树的深度
     */
    public int getDeep() {
        return deep(getRoot());
    }

}
import java.util.List;

public class TreeChildTest {
    
    public static void main(String[] args) {
        TreeChild<String> tc = new TreeChild<String>("root");
        TreeChild.Node<String> root = tc.getRoot();
        System.out.println(root);
        tc.addNode("A", root);
        tc.addNode("B", root);
        TreeChild.Node<String> A = tc.getChild(root, 0);
        TreeChild.Node<String> B = tc.getChild(root, 1);
        tc.addNode("C", A);
        tc.addNode("D", A);
        tc.addNode("E", B);
        tc.addNode("F", B);
        System.out.println("树的深度为:" + tc.getDeep());
        List<TreeChild.Node<String>> list = tc.getChildren(A);
        System.out.println("A节点的子节点为:");
        for(TreeChild.Node<String> node : list) {
            System.out.println(node);
        }
        System.out.println(A);
        System.out.println(B);
        
    }
    
}

结果:
TreeChild$Node [data=root, first=-1]
树的深度为:3
A节点的子节点为:
TreeChild$Node [data=C, first=-1]
TreeChild$Node [data=D, first=-1]
TreeChild$Node [data=A, first=3]
TreeChild$Node [data=B, first=5]

子节点链表示法的特点:

  • 每个节点都可以快速找到它的所有子节点
  • 但要找到某个节点的父节点则需要遍历整颗树