代码改变世界

树(Trees)

2012-11-12 14:56  Justany_WhiteSnow  阅读(869)  评论(0编辑  收藏  举报

 

有序树

Definition: an ordered tree consists of
A. A node, which may contain a piece of data known as a label. Depending on the application, a node may stand for any number of things and the data labeling it may be arbitrarily elaborate. The node part of a tree is known as its root node or root.
B. A sequence of 0 or more trees, whose root nodes are known as the children of the root. Each node in a tree is the child of at most one node—its parent. The children of any node are siblings of each other.

定义:有序树

A. 节点可能包含一些被称为标签的数据。根据不同的应用,一个节点可能代表一些事物并且数据表前可能是任意复杂的。节点树的一部分被称为根节点或根。

B. 一个0个或更多树的序列,他们的根节点被称为是子节点的根。每一个树上的节点都是他们是父级的子节点。任何一个节点的子节点们是兄弟姐妹们。

有序树:树中任意节点的子结点之间有顺序关系,这种树称为有序树。

 

位置树

Definition: A positional tree is either
A. Empty (missing), or
B. A node (generally labeled) and, for every non-negative integer, j, a positional tree—the jth child. 

定义:位置树

A. 空的,或者

B. 对于任意非负整数j,一个位置树有j个子节点。

最常见的位置树是二叉树(binary tree)。

 

树的三种基本的枚举方式

Three basic orders for enumeration (+ variations):
– Preorder: visit node, traverse its children.
– Postorder: traverse children, visit node.
– Inorder: traverse first child, visit node, traverse second child
(binary trees only).

三种基本的枚举方式:

— 前序遍历:先访问根节点,然后再访问所有的子树;

— 后序遍历:先访问子树,然后再访问根节点;

— 中序遍历:二叉树专用,先访问左子树,然后是根节点,最后是右子树。

 

前序遍历与前缀表达式

static String toLisp (Tree<String> T) {
    if (T == null)
        return "";
    else if (T.degree () == 0)
        return T.label ();
    else {
        String R; R = "";
        for (int i = 0; i < T.numChildren (); i += 1)
            R += " " + toLisp (T.child (i));
        return String.format ("(%s%s)", T.label (), R);
    }
}

 

中序遍历和中缀表达式

static String toInfix (Tree<String> T) {
    if (T == null)
        return "";
    if (T.degree () == 0)
        return T.label ();
    else {
        return String.format ("(%s%s%s)", toInfix (T.left ()), T.label (), toInfix (T.right ())
    }
}

怎样能够避免使用括号呢?

 

后续遍历和后缀表达式

static String toPolish (Tree<String> T) {
    if (T == null)
        return "";
    else {
        String R; R = "";
        for (int i = 0; i < T.numChildren (); i += 1)
            R += toPolish (T.child (i)) + " ";
        return String.format ("%s%s:%d", R, T.label (), T.degree ());
    }
}

逆波兰表示法Reverse Polish notationRPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。

逆波兰记法中,操作符置于操作数的后面。例如表达“三加四”时,写作“3 4 +”,而不是“3 + 4”。如果有多个操作符,操作符置于第二个操作数的后面,所以常规中缀记法的“3 - 4 + 5”在逆波兰记法中写作“3 4 - 5 +”:先3减去4,再加上5。使用逆波兰记法的一个好处是不需要使用括号。例如中缀记法中“3 - 4 * 5”与“(3 - 4)*5”不相同,但后缀记法中前者写做“3 4 5 * -”,无歧义地表示“3 (4 5 *) −”;后者写做“3 4 - 5 *”。

 

通用遍历:访问者模式

访问者模式是一种将算法与对象结构分离的软件设计模式

这个模式的基本想法如下:首先我们拥有一个由许多对象构成的对象结构,这些对象的都拥有一个accept方法用来接受访问者对象;访问者是一个接口,它拥有一个visit方法,这个方法对访问到的对象结构中不同类型的元素作出不同的反应;在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中回调访问者的visit方法,从而使访问者得以处理对象结构的每一个元素。我们可以针对对象结构设计不同的实在的访问者类来完成不同的操作。

void preorderTraverse (Tree<Label> T, Action<Label> whatToDo)
{
    if (T != null) {
        whatToDo.action (T);
        for (int i = 0; i < T.numChildren (); i += 1)
            preorderTraverse (T.child (i), whatToDo);
    }
}

Action是什么?

定义:

interface Action<Label> {
    void action (Tree<Label> T);
}
class Print implements Action<String> {
    void action (Tree<String> T) {
        System.out.print (T.label ());
    }
}

运行:

preorderTraverse (myTree, new Print ());

 

基于指针的由根节点向下二叉树

类似于双向链表:

T L; /* 数据 */
BinaryTree<T> left, right; /* 左右两个子节点 */
BinaryTree<T> parent; // 父节点

 

基于指针的由枝叶向上有序树

有些应用,父级是非常重要的,而子级不是,则可以用下面的表示方法:

T label;
Tree<T> parent; /* 父节点 */

 

用数组表示整个树

class BinaryTree2<T> {
    protected T[] label;
    protected int size;

    public BinaryTree2(int N) {
        label = (T[]) new Object[N]; size = 0;
    }
    public int currentSize() { return size; }
    public int maxSize() { return label.length; }

    public T label(int k) { return label[k]; }

    public void setLabel(int k, T val) { label[k] = val; }
    public int left(int k) { return 2*k+1; }
    public int right(int k) { return 2*k+2; }
    public int parent(int k) { return (k-1)/2; }
    public void extend(T label) {
        this.label[size] = label; size += 1;
    }
}

用数组表示二叉树。

 

空树表示

目前为止我们没有表示一个空树,而只是使用Null空指针来表示空树。这样子并不是一个好方法,我们应当建立一个空树的表示方法。

class Tree<T> {
...
    public final Tree<T> EMPTY = new EmptyTree<T> ();
    /** 当且仅当为空树时返回true */
    public boolean isEmpty () { return false; }
    private static class EmptyTree<T> extends Tree<T> {
    /** 空树 */
        private EmptyTree () { }
        public boolean isEmpty () { return true; }
        public int degree() { return 0; }
        public int numChildren() { return 0; }
        /** 第k个子节点总是抛出一个错误 */
        public Tree<T> child(int k) {
            throw new IndexOutOfBoundsException ();
        }
        /** label函数总是抛出一个错误 */
        public T label () {
            throw new IllegalStateException ();
        }
    }
}

 

鸟瞰图

 

二叉查找树

是一棵空树,或者是具有下列性质的二叉树

  1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 它的左、右子树也分别为二叉排序树。

在二元排序树b中查找x的过程为:

  1. 若b是空树,则搜索失败,否则:
  2. 若x等于b的根节点的数据域之值,则查找成功;否则:
  3. 若x小于b的根节点的数据域之值,则搜索左子树;否则:
  4. 查找右子树。

查找代码:

public static BST find(BST T, int L)
{
    if (T == null || L == T.label)
        return T;
    else if (L < T.label)
        return find(T.left, L);
    else return find(T.right, L);
}

 

二叉查找树插入数字

/** 一个二叉查找树 */
class BST {
    protected int label;
    protected BST left, right;

    public BST(int label) { this(label, null, null); }

    public int label();

    public BST left() ...
    public BST right() ...

    public static BST find(BST T, int L) ...

    public static boolean isIn(BST T, int L)
        { return find (T, L) != null; }

    public static BST insert(BST T, int L) ...

    public static BST remove(BST T, int L) ...

    private BST(int label, BST left, BST right) {
        this.label = label; this.left = left; this.right = right;
    }
}

插入数字:

/* 向二叉树插入数字 */
static BST insert(BST T, int L)
{
    if (T == null)
        return new BST (L, null, null);
    if (L < T.label)
        T.left = insert(T.left, L);
    else
        T.right = insert(T.right, L);
    return this;
}

 

四叉树(Quadtree)

四元树又称四叉树是一种树状数据结构,在每一个节点上会有四个子区块。四元树常应用于二维空间数据的分析与分类。 它将数据区分成为四个象限。数据范围可以是方形或矩形或其他任意形状。这种数据结构是由 拉斐尔·芬科尔(Raphael Finkel) 与 J. L. Bentley 在1974年发展出来 。 类似的数据分区方法也称为 Q-tree。 所有的四元树法有共同之特点:

  • 可分解成为各自的区块
  • 每一个区块可持续分解直到数据无法分解为止
  • 树状数据结构依造四元树法加以区分

Idea: divide (2D) space into four quadrants, and store items in the appropriate quadrant. Repeat this recursively with each quadrant that contains more than one item.

想法:将2D空间分割成四个象限,并存储相应的数据项,递归重复每个象限使其存相应的数据项。

Original definition: a quadtree is either
– Empty, or
– An item at some position (x, y), called the root, plus
– four quadtrees, each containing only items that are northwest, northeast, southwest, and southeast of (x, y).

原始定义:一个四叉树是

— 空,或者

— 一个在(x, y)的项,称为根,并包含

— 四个四叉树,每一个包含一个(x, y)项,分别表示:西北,东北,西南和东南四个方向

也可以将这一个想法变一下,把位置(x, y)改成像素点(x, y),这样子就可以用一个深度为n的四叉树表示一个2n * 2n 的像素块了。

 

相关资料

逆波兰表示法 . Wiki

访问者模式 . Wiki

树(数据结构) . Wiki

四叉树 . Wiki

Data Structures (Into Java) . Paul N. Hilfinger