数据结构之树(一)

定义

树(Tree)是n(n≥0)个结点的有限集合,n=0时称为空树。在任意一个非空树中:①有且只有一个特定的称为根(root)的结点;②当n>1时,其余结点可以分为m(m>0)个互不相交的有限集T₁,T₂,T₃....Tп,其中每个集合本身又是一棵树,并且称为根的子树(subTree)

树的定义用到了递归,也就是在树的的定义中还用到了树的概念,如上图中T₁,T₂的树就是结点A的子树,DGHI组成的树是结点B的子树,EJ组成的树是结点C的子树。当n>0时根节点时唯一的,当m>0,子树个数没有限制,但一定不能相交。

结点分类

树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶结点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。如下图,这棵树的度的值为最大结点D的度,也就是3

 结点关系

结点的子树的根称为该结点的孩子(Child),相应的该结点称为孩子的双亲(Parent),同一个双亲的孩子之间互称兄弟(Sibling),结点的祖先是从根到该结点所经分支上的所有结点,以某结点为根的的子树任意一个结点都是该结点的子孙。如下图:对于H来说,D,B,A都是它的祖先,而B的子孙有D,G,H,I

其他概念

结点的层次(Level)从根开始定义,根为第一层,根的子节点称为第二层,其双亲结点在同一层的结点互为堂兄弟,如下图:D,E,F是堂兄弟,G,H,I,J也是。树中结点的最大层次称为树的深度(Depth)或高度,当前树高度为4。

如果我们将树中的各个子树看成从左至右依次有顺序的,不能互换,那么该树为有序树,否则就是无序树。

森林(Forest)是m(m≥0)棵互不相交的树的集合,对于树中的每个结点而言,其子树的集合即为森林。

线性结构与树结构的对比:

树的存储结构

对于存储结构,可能会联想到前面的顺序存储和链式存储结构。但是对于数这种可能会有很多孩子的特殊数据结构,只用顺序存储结构或者链式存储结构很那实现,那么可以将这两者结合,产生主要的三种存储结构表示法:双亲表示法、孩子表示法、孩子兄弟表示法。

双亲表示法:

假设以一组连续空间存储数的结点,同时在每个结点中,附设一个指示器指示其双亲结点到链表中的位置

data(数据域)parent(指针域)
存储结点的数据信息存储该结点的双亲所在数组中的下标

双亲表示法的特点

  • 由于根结点是没有双亲的,约定根结点的位置位置域为-1.
  • 根据结点的parent指针很容易找到它的双亲结点。所用时间复杂度为O(1),直到parent为-1时,表示找到了树结点的根。
  • 缺点:如果要找到孩子结点,需要遍历整个结构才行

双亲表示法存储结构代码实现:

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;
        }

        public String toString() {
            return "TreeParent$Node[data=" + data + ", parent=" + parent + "]";
        }

    }
    private final int DEFAULT_TREE_SIZE = 100;
    private int treeSize = 0;
    // 使用一个Node[]数组来记录该树里的所有节点
    private Node<E>[] nodes;
    // 记录树的节点数
    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++;
    }
}

孩子表示法:

把每个结点的孩子结点排列起来,以单链表作为存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。

孩子表示法要设计两种结点结构:孩子链表的孩子结点表头数组的表头结点

  • 孩子链表的孩子结点
child(数据域)next(指针域)
存储某个结点在表头数组中的下标存储指向某结点的下一个孩子结点的指针
  • 表头数组的表头结点
data(数据域)firstchild(头指针域)
存储某个结点的数据信息存储该结点的孩子链表的头指针

 孩子表示法的存储结构:


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;
		}


	// 默认树的节点数
	private final int DEFAULT_SIZE = 100;
	// 树的节点数
	private int treeSize = 0;
	// 存储树节点的节点数组
	private Node<E>[] nodes;
	// 记录的节点数
	private int nodeNum = 0;
 
	// 指定根节点创建树
	public TreeChild(E element) {
		treeSize = DEFAULT_SIZE;
		nodes = new Node[treeSize];
		nodes[0] = new Node<E>(element);
		nodeNum++;
        }
}

对于孩子表示法,查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表即可。但是当要寻找某个结点的双亲时,就不是那么方便了。所以可以将双亲表示法和孩子表示法结合,就是在表头数组再添加一列存放双亲的下标,形成双亲孩子表示法

 孩子兄弟表示法:

任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟存在也是唯一的。因此,设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。

data(数据域)firstchild(指针域)rightsib(指针域)
存储结点的数据信息存储该结点的第一个孩子的存储地址存储该结点的右兄弟结点的存储地址

 

孩子兄弟数据结构:

public class TreeNode {
    private TreeNode sonNode;//这里存储的是最左的儿子节点
    private TreeNode brotherNode;//这里存储的是自己的兄弟节点
    private int value;
    public TreeNode(TreeNode sonNode,TreeNode brotherNode,int value){
    this.sonNode = sonNode;
    this.brotherNode = brotherNode;
    this.value = value;
}
public TreeNode getSonNode(){
    return this.sonNode;
}
public TreeNode getBrotherNode() {
    return brotherNode;
}
public int getValue(){
    return value;
}
 
public void setSonNode(TreeNode sonNode){
    this.sonNode = sonNode;
}
 
public void setBrotherNode(TreeNode brotherNode){
    this.brotherNode = brotherNode;
}

 这种表示方法,只要通过fistchild找到此结点的长子,在通过长子结点的rightsib找到它的二弟,然后一直下去,直到找到自己的孩子,这种表示法最大的好处就是把一个复杂的树变成了一棵二叉树,上图可以变为下图的样子:

这样就能利用二叉树的特性和算法来处理该树了。

原文地址:https://www.cnblogs.com/binbinshan/p/14156582.html