数据结构与算法分析之树-Java语言描述(三)

本文内容基于《数据结构与算法分析 Java语言描述》第三版,冯舜玺等译。


1. 预备知识

1.1 树的实现

1.2 树的遍历及应用

2. 二叉树

2.1 实现

2.2 例子:表达式树

3. 查找树ADT-二叉查找树

4. 标准库中的集合与映射

4.1 Set接口

4.2 Map接口

4.3 TreeSet类和TreeMap类的实现


1. 预备知识

树可以用几种方式定义。定义树的一种自然的方式是递归。一棵树是一些节点的集合。这个集合可以是空集;若不是空集,则树由称作根的节点r以及0个或多个非空的子树组成,这些子树中每一颗的根都被来自根r的一条有向边所连接。

每一颗子树的根叫做根r的儿子,而r是每一颗子树的根的父亲。

一棵树是N个节点和N-1条边的集合,其中的一个节点叫做根。

没有儿子的节点称为树叶。

具有相同父亲的节点为兄弟。

对于图中的树,E的深度为1高为2;F的深度为1高为1;该树的高度为3。

1.1 树的实现

实现树的一种方法可以是在每一个节点除数据外还要有一些链,使得该节点的每一个儿子都有一个链指向它。

树节点的声明:

public class TreeNode {

    private Object element;

    private TreeNode firstChild;

    private TreeNode nextSibling;
}

1.2 树的遍历及应用

流行的用法之一是包括UNIX和DOS在内的许多常用操作系统中的目录结构。

1.2.1 先序遍历

对节点的处理工作是在它的诸儿子节点被处理之前进行的。

public class Test {

    public static void main(String[] args) {
        TreeNode node1 = new TreeNode("F:\\Projects\\Java\\review\\files");
        TreeNode node2 = new TreeNode("F:\\Projects\\Java\\review\\files\\csv1.csv");
        TreeNode node3 = new TreeNode("F:\\Projects\\Java\\review\\files\\directory11");
        TreeNode node4 = new TreeNode("F:\\Projects\\Java\\review\\files\\directory11\\file1.txt");
        TreeNode node5 = new TreeNode("F:\\Projects\\Java\\review\\files\\file11.txt");
        TreeNode node6 = new TreeNode("F:\\Projects\\Java\\review\\files\\file2.txt");
        TreeNode node7 = new TreeNode("F:\\Projects\\Java\\review\\files\\html1.html");

        node1.setFirstChild(node2);
        node2.setNextSibling(node3);
        node3.setFirstChild(node4);
        node3.setNextSibling(node5);
        node5.setNextSibling(node6);
        node6.setNextSibling(node7);

        preOrderTraversal(node1);
    }

    public static void preOrderTraversal(TreeNode node) {
        System.out.println(node.getElement());

        if (node.getFirstChild() != null) {
            preOrderTraversal(node.getFirstChild());
        }

        if (node.getNextSibling() != null) {
            preOrderTraversal(node.getNextSibling());
        }
    }
}

1.2.2 后序遍历

一个节点的工作是在它的诸儿子节点被计算后进行的。

public class Test {

    public static void main(String[] args) {
        TreeNode node1 = new TreeNode("F:\\Projects\\Java\\review\\files");
        TreeNode node2 = new TreeNode("F:\\Projects\\Java\\review\\files\\csv1.csv");
        TreeNode node3 = new TreeNode("F:\\Projects\\Java\\review\\files\\directory11");
        TreeNode node4 = new TreeNode("F:\\Projects\\Java\\review\\files\\directory11\\file1.txt");
        TreeNode node5 = new TreeNode("F:\\Projects\\Java\\review\\files\\file11.txt");
        TreeNode node6 = new TreeNode("F:\\Projects\\Java\\review\\files\\file2.txt");
        TreeNode node7 = new TreeNode("F:\\Projects\\Java\\review\\files\\html1.html");

        node1.setFirstChild(node2);
        node2.setNextSibling(node3);
        node3.setFirstChild(node4);
        node3.setNextSibling(node5);
        node5.setNextSibling(node6);
        node6.setNextSibling(node7);

        postOrderTraversal(node1);
        System.out.println(node1.getElement());
    }

    public static void postOrderTraversal(TreeNode node) {
        for (node = node.getFirstChild(); node != null; node = node.getNextSibling()) {
            System.out.println(node.getElement());
            postOrderTraversal(node);
        }
    }
}

2. 二叉树

二叉树是一棵树,其中每个节点都不能有多于两个的儿子。

二叉树的一个性质是一颗平均二叉树的深度要比节点个数N小得多,其平均深度为,而对于特殊类型的二叉树,即二叉查找树,其深度的平均值是

2.1 实现

因为一个二叉树节点最多有两个子节点,所以可以保存直接链接到他们的链。

二叉树节点类:

class BinaryNode{
    Object element;
    BinaryNode left;
    BinaryNode right;
}

2.2 例子:表达式树

表达式树的树叶是操作数,如常量或变量名,其他节点为操作符。

上图中的树表示(a + (b * c)) + (((d * e) + f) * g) 。

可以通过递归地产生一个带括号的左表达式,然后打印出在根处的运算符,最后递归地产生一个带括号的右表达式,最终得到一个中缀表达式(左,节点,右)。称为中序遍历。

递归地打印出左子树、右子树、然后打印运算符。上图中的树将输出 abc*+de*f+g*+ 这个后缀表达式(左,右,节点)。称为后序遍历。

先打印出运算符,然后递归地打印出左子树和右子树。上图中的树将输出 ++a*bc*+*defg 这个前缀表达式(节点,左,右)。称为先序遍历。

2.2.1 将后缀表达式转变成表达式树

例如:ab+cde+**

public class BinaryTree {

    public static void main(String[] args) {
        String expression = "ab+cde+**";

        Stack<BinaryNode> stack = new Stack<>();

        for (int i = 0; i < expression.length(); i++) {
            char x = expression.charAt(i);
            if (!judgeOperand(x)) {
                stack.push(new BinaryNode(x));
            } else {
                BinaryNode newNode = new BinaryNode(x);
                BinaryNode rightNode = stack.pop();
                newNode.right = rightNode;
                BinaryNode leftNode = stack.pop();
                newNode.left = leftNode;

                stack.push(newNode);
            }
        }

        BinaryNode t = stack.pop();
        printTree(t);
    }

    public static boolean judgeOperand(char x) {
        if ('+' == x || '-' == x || '*' == x || '/' == x) {
            return true;
        }
        return false;
    }

    public static void printTree(BinaryNode t) {
        if (t == null) {
            return;
        }

        printTree(t.left);

        printTree(t.right);

        System.out.print(t.element);
    }
}

class BinaryNode {
    Object element;
    BinaryNode left;
    BinaryNode right;

    public BinaryNode(Object element) {
        this.element = element;
    }
}

3. 查找树ADT-二叉查找树

使得二叉树称为二叉查找树的性质是,对于树中的每个节点X,它的左子树中所有项的值小于X中的项,而它的右子树中所有项的值大于X中的项。这意味着该树所有的元素可以用某种一直的方式排序。

/**
 * 二叉查找树
 * 树中的每个节点X,它的左子树中所有项的值都小于X中的值,它的右子树中所有项的值都大于X中的值
 * @Author YETA
 * @Date 2019-05-09 14:07
 */
public class BinarySearchTree<AnyType extends Comparable<? super AnyType>> {

    /**
     * 二叉树节点类
     * @param <AnyType>
     */
    private static class BinaryNode<AnyType> {

        public BinaryNode(AnyType theElement) {
            this(theElement, null, null);
        }

        public BinaryNode(AnyType element, BinaryNode<AnyType> left, BinaryNode<AnyType> right) {
            this.element = element;
            this.left = left;
            this.right = right;
        }

        AnyType element;
        BinaryNode<AnyType> left;
        BinaryNode<AnyType> right;
    }

    private BinaryNode<AnyType> root;

    public BinarySearchTree() {
        this.root = null;
    }

    public void makeEmpty() {
        root = null;
    }

    public boolean isEmpty() {
        return root == null;
    }

    public boolean contains(AnyType x) {
        return contains(x, root);
    }

    /**
     * 判断树t中是否包含节点x
     * @param x
     * @param t
     * @return
     */
    private boolean contains(AnyType x, BinaryNode<AnyType> t) {
        if (t == null) {
            return false;
        }

        int compareResult = x.compareTo(t.element);

        if (compareResult < 0) {
            return contains(x, t.left);
        } else if (compareResult > 0) {
            return contains(x, t.right);
        } else {
            return true;
        }
    }

    public AnyType findMin() {
        if (isEmpty()) {
            System.out.println("is empty!");
        }

        return findMin(root).element;
    }

    /**
     * 返回树中包含最小元的节点的引用
     * 递归实现
     * @param t
     * @return
     */
    private BinaryNode<AnyType> findMin(BinaryNode<AnyType> t) {
        if (t == null) {
            return null;
        } else if (t.left == null) {
            return t;
        }

        return findMin(t.left);
    }

    public AnyType findMax() {
        if (isEmpty()) {
            System.out.println("is empty!");
        }

        return findMax(root).element;
    }

    /**
     * 返回树中包含最大元的节点的引用
     * 非递归实现
     * @param t
     * @return
     */
    private BinaryNode<AnyType> findMax(BinaryNode<AnyType> t) {
       if (t != null) {
           while (t.right != null) {
               t = t.right;
           }
       }

       return t;
    }

    public void insert(AnyType x) {
        root = insert(x, root);
    }

    /**
     * 将节点x插入到树t中
     * @param x
     * @param t
     * @return
     */
    private BinaryNode<AnyType> insert(AnyType x, BinaryNode<AnyType> t) {
        if (t == null) {
            return new BinaryNode<>(x, null, null);
        }

        int compareResult = x.compareTo(t.element);

        if (compareResult < 0) {
            t.left = insert(x, t.left);
        } else if (compareResult > 0) {
            t.right = insert(x, t.right);
        } else {
            //duplicate, do nothing
        }

        return t;
    }

    public void remove(AnyType x) {
        root = remove(x, root);
    }

    /**
     * 将节点x从树t中移除
     * @param x
     * @param t
     * @return
     */
    private BinaryNode<AnyType> remove(AnyType x, BinaryNode<AnyType> t) {
        if (t == null) {
            return t;
        }

        int compareResult = x.compareTo(t.element);

        if (compareResult < 0) {
            //节点x的值比当前节点的值小,所以向左遍历
            t.left = remove(x, t.left);
        } else if (compareResult > 0) {
            //节点x的值比当前节点的值大,所以向右遍历
            t.right = remove(x, t.right);
        } else if (t.left != null && t.right != null) {
            //通过上面两个if的遍历,程序运行到这里说明当前节点就是要移除的节点
            //当前节点有两个儿子的情况,用右儿子中最小的数据来替换当前节点,并删除那个最小数据的节点
            t.element = findMin(t.right).element;
            t.right = remove(t.element, t.right);
        } else {
            //通过上面两个if的遍历,程序运行到这里说明当前节点就是要移除的节点
            //当前节点只有一个儿子的情况,直接用儿子节点替换当前节点
            t = t.left != null ? t.left : t.right;
        }

        return t;
    }

    public void printTree() {
        if (isEmpty()) {
            System.out.println("is empty!");
        } else {
            printTree(root);
        }
    }

    /**
     * 中序遍历打印
     * @param t
     */
    private void printTree(BinaryNode<AnyType> t) {
        if (t != null) {
            printTree(t.left);
            System.out.println(t.element);
            printTree(t.right);
        }
    }
}

4. 标准库中的集合与映射

4.1 Set接口

Set接口代表不允许重复元的Collection。由接口SortedSet给出的一种特殊类型的Set保证其中的各项处于有序的状态,一个实现是TreeSet。

4.2 Map接口

Map是一个接口,代表由关键字以及它们的值组成的一些项的集合。关键字必须是唯一的,但是若干关键字可以映射到一些相同的值,因此值不是唯一的。在SortedMap接口中,映射中的关键字保持逻辑上有序状态,一种实现是TreeMap类。

Map不提供迭代器,而是提供3种方法:

4.3 TreeSet类和TreeMap类的实现

Java要求TreeSet和TreeMap支持基本的add、remove和contains操作以及以对数最坏情形时间完成。因此,基本的实现方法就是平衡二叉查找树,一般来说,并不适用AVL树,而是经常使用一些自顶向下的红黑树。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值