动态扩容栈(2)链表

107 阅读6分钟

链表

上一篇,我们用数组实现了一个动态扩容栈,这次我们来看一下链表怎么实现栈结构。
说到链表,来看这篇文章的人应该都不会陌生,在计算机数据结构中,同离不开数组一样,我们也离不开链表结构,它属于线性表中的一种,在物理存储结构上来说,并不是连续空间存储,可以通过内部的引用指针寻找其它节点。

假如有老师或者妹子问你,为了显得不明觉厉,要这样说:链表是一种递归的数据结构,它或者为null,或者指向一个节点(node)的引用,该节点含有一个泛型的元素和一个指向另一条指针

好吧,咱们说人话,打个比方,链表就像火车箱一样一节节相连,你跟朋友(数据)身处不同的车厢,那么你在内部只有通过火车的连接处才能找到下一节车厢,一节节找才能找到朋友(数据)的位置,这在链表中火车连接处保存的是下个结点(车厢)的地址,可以通过地址找到数据。

链表中的结点是一个可能含有任意程序数据的抽象实体,和递归程序一样,一开始令人费解,但其简洁性赋予了巨大的价值。

简单实现

在面向对象编程中,实现链表并不困难,首先用一个嵌套类来定义结点抽象数据类型:

private class Node{
    Item item;//数据
    Node next;//下个相连节点引用

}

现在我们只需要创建一个Node类型的变量就嫩好表示一条链表,只要保证他的元素值是null或者指向另一个Node对象,且该对象的next域指向来另一条链表即可。例如要构造 d、e、f,只需要:

Node d = new Node();
Node e = new Node();
Node f = new Node();

并且每个结点的item对象设为所需值,我们先假设item存储的是String。

d.item = "d";
e.item = "e";
f.item = "f"
//然后通过设置next引用来串联在一起
d.next = e;
e.next = f;
//此时c.next = null

接下来学习如何从链表中插入或移除数据,并且在追踪链表与其它链式结构时,我们采用可视化表示方法(画图):
1.用长方形表示对象。
2.把实例变量值写在长方形中。
3.用指向被引用对象的箭头表示引用关系。
这种表示方法可以明显表现链表的特性。方便起见,我们采用链接表示对结点的引用。

在表头插入节点

最容易做到这一点的就是在链表的开头,我们要在上面的“d,e,f”链表的开头插入“C”结点,首先要创建一个新结点命名为“C”,在 C结点的item内写入字符串“C”,并且将C的next域指向d. 如图:

image.png

从表头删除结点

假设要删除头部结点C,只需要将C.next指向C即可,意思是将C的下一个节点的引用覆盖了C结点的引用,此时C结点变成了游离元素等待被垃圾回收器回收。首结点first又变为了d。上图:

image.png

在表尾部插入节点

如何能在链表的尾部插入一个节点,我们需要一个指向链表最后一个节点的链接,因为只要将该节点的链接指向一个新的节点即可。上图:

image.png

其他位置的插入删除操作

我们现在可以通过first(首结点)链接访问头部结点,也可以通过last访问链表尾节点。表头的插入删除、表尾部的添加实现了,还剩:删除指定结点、在指定结点前插入一个新的节点。

比如我们想删除尾节点,那么last(g)结点就帮不上忙,因为我们需要的是它的前一个节点,让该节点的链接(next)指向null。唯一的解决办法就是遍历所有的节点,并找出指向last(g)的节点,这个方案并不理想,但确实能实现我们的效果,目前业内解决方案是采用双向链表,其中的每个节点都含有两个链接,分别指向不同的方向。

遍历

访问链表中的每一个元素也有对应的方式,将循环的索引变量初始化为链表的首结点,不断寻找next指向的下个节点,直到首结点为null时说明已经到了链表的结尾。

for(Node x = first; x!=null;x.next){
    //处理x.item......
}

栈的实现方式

有了上面写的这些预备知识,我们利用链表实现一个动态扩容栈的API应该不难了。
当push一个元素时,我们在该元素添加在链表的表头即首结点。
当我们pop一个元素,就从表头删除,另外实现size方法,来查看栈内元素数量,用size变量来标识实际元素数量,在压入元素时size+1,反之弹出元素size-1,再实现一个isEmpty()方法,来查看first是否为null,或者可以检查size是否为0.
该实现使用了泛型Item.

目前的设计达到了我们初期的目标:
1.它可以处理任意类型的数据
2.所需的空间总是和集合的大小成正比
3.操作所需的时间总是和集合的大小无关

上代码:


import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * 用链表实现下压栈
 * @author wangxinfu
 */
public class Stack<Item> implements Iterable<Item> {
    /**
     * 实际元素数量
     */
    private int size;

    /**
     * 首结点
     */
    private Node first;

    /**
     * 私有内部类构成栈结构
     *
     */
    private class Node {
        Item item;
        Node next;
    }

    /**
     * 压栈
     *
     * @param item element
     * @return true
     */
    public boolean push(Item item) {
        //在头结点前添加,无需判断first是否null
        Node oldFirst = first;
        first = new Node();
        first.item = item;
        first.next = oldFirst;
        size++;
        return true;
    }

    /**
     * 弹栈
     *
     * @return element
     */
    public Item pop() {
        if (isEmpty()) {
            //空栈则抛出异常
            throw new NoSuchElementException("Stack is Empty!");
        }
        //暂存
        Item item = first.item;
        Node next = first.next;
        //help GC
        first.item = null;
        first.next = null;
        first = next;
        size--;
        return item;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return first == null;
    }
    @Override
    public StackIterator iterator() {
        return new StackIterator();
    }
    //iterator用来实现遍历
    private class StackIterator implements Iterator<Item> {
        //当前结点
        private Node current = first;

        @Override
        public boolean hasNext() {
            return current != null;
        }

        @Override
        public Item next() {
            if (!hasNext()) {
                throw new NoSuchElementException("Stack is Empty !");
            }
            Item item = current.item;
            current = current.next;
            return item;
        }
    }
    //测试 也可以存取其它数据类型
    public static void main(String[]args){
        Stack<Integer> stack = new Stack<>();
        for (int i = 0;i < 100; i++){
            stack.push(i);
        }
        System.out.println("Stack size : "+ stack.size());

        System.out.println("================================");
        //iterator遍历
        Iterator<Integer> iterator = stack.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }


}

最好的学习方法,往往是实践加上教给他人,希望看到这篇博客的读者自己能够将代码自行敲一遍,否则没有两天就忘记了。

下一篇我们将学习下利用链表实现队列,不积跬步无以至千里,不积小流无以成江海,加油。