Java中的action函数干什么的_Java中的设计模式之迭代器

本节课我们介绍一下迭代器模式,然后在二叉树里实现一下迭代器。

迭代器模式

在容器里存放了大量的同类型对象,我们如果想逐个访问容器中的对象,就必须要知道容器的结构。比如,ArrayList 和 LinkedList 的遍历方法一定是不一样的,如果再加上HashMap, TreeMap,以及我们现在正在研究的BinarySearchTree,对于容器的使用者而言,肯定是一个巨大的负担。作为容器的使用者,我肯定希望所有的容器能提供一个统一的接口,这个接口可以遍历容器中的所有内容,又可以把容器的细节屏蔽掉。

幸运的是,确实存在这样的一个统一的接口,这个接口就是 java.util.Iterator。先看类图,下面的这幅图所表示的意思就是,在一个容器类上实现createIterator方法,这个方法可以创建一个迭代器,使用这个迭代器就可以遍历容器中的所有元素了。

227fe2f2841e885b2700bd8c2c302f2b.png

Iterator上定义了各种方法,用于遍历。图中只列出了一小部分。我们可以看一下JDK中是怎么定义的:

public 

有两个default方法。default 这个关键字,我们以前没接触过。这是从1.8引入的,为了和以前的接口兼容,这个我们先不去管它,还有 forEachRemaing是一个高阶函数,它可以接受一个lambda表达式,这个我们会在后面的 Java 函数式编程中慢慢介绍,也先不去管它。所以 Iterator中也就只有两个方法,一个是hasNext,告诉使用者,还有没有下一个元素,如果为false,说明遍历已经完成了。还有一个是next,目的是取下一个元素。

我们先来体验一下。

List中的迭代器

我们先使用这个迭代器遍历一下List,例如:

public 

请大家再试一下ArrayList,体会一下 Iterator 的用法。可以看到,我们根本就不需要关心后面具体的数据结构,只需要使用 Iterator,不管后面是数组还是链表,都可以很方便地遍历整个容器。

大家想一下,如果要让自己来实现这样的一个Iterator,你会怎么写?这里给出ArrayList的源代码,可以参考一下:

    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
    }

我把 remove 等方法删掉了,这个方法是在 ListIterator定义的,我们先不去管它。这里只留下了 hasNext 和 next 方法。

可以看到,ArrayList 的 Iterator 就是在数组中逐个前进,逻辑非常简单。

二叉树的Iterator

我们前边几节,实现了一个自己的容器,BinarySearchTree,如果想给这个容器加上迭代器,那应该怎么办呢?

把问题转化一下,如果已经停在一个结点上,next 方法其实就是要寻找二叉树的中序后继。如果某结点有右孩子的话,如图2.2(a),(这是我从以前的讲议里截的图,章节序号都是讲义里的,大家可以忽略这个章节号)

281450e7b38a9d1b82144676e6b9234c.png

蓝色结点的中序后继就是右孩子上的最小值,即红色结点。如果这个结点没有右孩子,就要去找他的父结点,如果该结点是父亲的左孩子,如图2.2(b),那么父亲结点就是该结点的中序后继,最复杂的情况是如果该结点是父亲的右孩子,那么就继续往祖先结点找,直到找到该结点是在某个祖先的左孩子上,如图2.2(c),如果一直到根结点都未找到,那么可以肯定的是这个结点是二叉树中的最大值结点,无中序后继。

可以看到,我们设计的这个算法中,经常要访问节点的父亲结点,由子结点查找父亲结点是一个大问题。可以这样解决,在Node的定义中,再加一个父结点的引用就可以了。这样,我们就可以通过子节点方便地找到父结点了:

class 

Node发生了变化,BST中的 insert 也会相应地发生变化,请读者自行补充。

我们再回到寻找中序后继这个问题上来,根据前边分析三种情况,我们可以分别进行处理。下面是在BST中查找中序后继的方法。

    public Node<T> successor(Node<T> n) {
        if (n == null)
            return null;

        Node<T> p;
        if (n.right != null) {
            p = n.right;
            while (p.left != null) {
                p = p.left;
            }
            return p;
        }
        else {
            p = n.parent;

            while (p != null && p.left != n) {
                n = p;
                p = n.parent;
            }
            return p;
        }
    }

好了,今天的课程就到这里了。作业:

1. 自己看一下ArrayList的 iterator的实现,LinkedList, ArrayDeque, ListIterator也一起看一下。

2. 使用文章中提供的 successor 方法,实现BST的 iterator 方法,并且向BST中插入几个数字,再使用 iterator 进行遍历,查看遍历结果是不是从小到大有序的。(这个结果与中序遍历的结果相同)

完整的BinarySearchTree我已经放在小密圈里了。圈子里的朋友尽量自己做,做不出来再看答案。

上一节课:数据结构(六):二叉树的遍历

下一节课:二叉树的删除和应用举例

目录:课程目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值