1.3 背包、队列与栈

背包、队列与栈

1.API

每个API都含有一个无参数的构造函数、一个向集合中添加单个元素的方法、一个测试集合是否为空的方法和一个返回集合大小的方法。Stack和Queue都含有一个能够删除集合中特定元素的方法。三个API如下:

1.3 背包、队列与栈_第1张图片

2.泛型

泛型可以存储任意类型的数据,在上面的API中,记号将Item定义为一个类型参数,表示一个占位符,指某种具体数据类型。如用栈处理String对象:

Stack stack = new Stack<>();

如果向stack变量中添加一个Date对象,将得到编译错误。

3.自动装箱

自动将一个原始数据类型转换为一个封装类型(包装类)称为自动装箱,自动将一个封装类型转换为原始数据类型称为自动拆箱。如:

Stack stack = new Stack<>();
stack.push(17);        //自动装箱(int->Integer)
int i = stack.pop();   //自动拆箱(Integer->int)

4.可迭代的集合类型

即迭代访问集合中的所有元素,如:

Queue collection = new Queue<>();
for(Transaction t : collection)
{
    StdOut.println(t);
}

这种语法叫做foreach语句,逐个处理集合中的元素。

5.背包、FIFO和下压栈(LIFO)

在有了上面的一些知识后,开始介绍主角:背包、队列和栈。先来看背包:

背包是一种不支持从中删除元素的集合数据类型。功能是帮助用例收集元素并迭代遍历所有收集到的元素。迭代的顺序不确定。

后面我们将看到背包的具体实现代码。

FIFO(先进先出队列):用集合保存元素的同时保存它们的相对顺序,入列顺序与出列顺序相同。

下压栈(LIFO):后进先出的集合类型。

下面是一个Dijkstra的双栈算术表达式求值算法:

输入:( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )

输出:101.0

public class Evaluate {
    public static void main(String[] args) { 
        Stack ops  = new Stack();
        Stack vals = new Stack();

        while (!StdIn.isEmpty()) {
            String s = StdIn.readString();
            if      (s.equals("("))               ;
            else if (s.equals("+"))    ops.push(s);
            else if (s.equals("-"))    ops.push(s);
            else if (s.equals("*"))    ops.push(s);
            else if (s.equals("/"))    ops.push(s);
            else if (s.equals("sqrt")) ops.push(s);
            else if (s.equals(")")) {
                String op = ops.pop();
                double v = vals.pop();
                if      (op.equals("+"))    v = vals.pop() + v;
                else if (op.equals("-"))    v = vals.pop() - v;
                else if (op.equals("*"))    v = vals.pop() * v;
                else if (op.equals("/"))    v = vals.pop() / v;
                else if (op.equals("sqrt")) v = Math.sqrt(v);
                vals.push(v);
            }
            else vals.push(Double.parseDouble(s));
        }
        StdOut.println(vals.pop());
    }
}

6.集合类数据类型实现

通过定容栈的例子和改进算法理解集合类数据类型实现。

(1)定容栈

一种容量固定的字符串栈的抽象数据类型。它只能处理String值,且不支持迭代。下面是它的API:

1.3 背包、队列与栈_第2张图片

下面是实现代码:

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

public class FixedCapacityStackOfStrings implements Iterable {
    private String[] a;  // holds the items
    private int N;       // number of items in stack

    // create an empty stack with given capacity
    public FixedCapacityStackOfStrings(int capacity) {
        a = new String[capacity];
        N = 0;
    }

    public boolean isEmpty()            {  return N == 0;                    }
    public boolean isFull()             {  return N == a.length;             }
    public void push(String item)       {  a[N++] = item;                    }
    public String pop()                 {  return a[--N];                    }
    public String peek()                {  return a[N-1];                    }
    public Iterator iterator()  { return new ReverseArrayIterator(); }


    public class ReverseArrayIterator implements Iterator {
        private int i = N-1;

        public boolean hasNext() {
            return i >= 0;
        }

        public String next() { 
            if (!hasNext()) throw new NoSuchElementException();
            return a[i--];
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }


    public static void main(String[] args) {
        int max = Integer.parseInt(args[0]);
        FixedCapacityStackOfStrings stack = new FixedCapacityStackOfStrings(max);
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (!item.equals("-")) stack.push(item); 
            else if (stack.isEmpty())  StdOut.println("BAD INPUT"); 
            else                       StdOut.print(stack.pop() + " ");
        }
        StdOut.println();

        // print what's left on the stack
        StdOut.print("Left on stack: ");
        for (String s : stack) {
            StdOut.print(s + " ");
        }
        StdOut.println();
    } 
} 

其中的一些性质:

1)数组中的元素顺序和它们被插入的顺序相同;

2)当N为0时栈为空;

3)栈的顶部位于a[N-1](如果栈非空)

这是FixedCapacityStackOfStrings的测试用例轨迹:

1.3 背包、队列与栈_第3张图片

(2)泛型定容栈

下面我们将FixedCapacityStackOfStrings里的所有String都替换为Item,这样可以用栈处理任意数据类型,由于Java不允许创建泛型数组(原因:https://blog.csdn.net/qq_38379983/article/details/90146160),所以我们将构造函数更改一下:

//更改前
a = new Item[cap];
//更改后
a = (Item[]) new Object[cap];

下面是泛型定容栈类FixedCapacityStack的API:

1.3 背包、队列与栈_第4张图片

代码实现如下:

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

public class FixedCapacityStack implements Iterable {
    private Item[] a;    // holds the items
    private int N;       // number of items in stack

    // create an empty stack with given capacity
    public FixedCapacityStack(int capacity) {
        a = (Item[]) new Object[capacity];   // no generic array creation
        N = 0;
    }

    public boolean isEmpty()          {  return N == 0;                    }
    public void push(Item item)       {  a[N++] = item;                    }
    public Item pop()                 {  return a[--N];                    }
    public Iterator iterator()  { return new ReverseArrayIterator(); }


    public class ReverseArrayIterator implements Iterator {
        private int i = N-1;

        public boolean hasNext() {
            return i >= 0;
        }

        public Item next() {
            if (!hasNext()) throw new NoSuchElementException();
            return a[i--];
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }


    public static void main(String[] args) {
        int max = Integer.parseInt(args[0]);
        FixedCapacityStack stack = new FixedCapacityStack(max);
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (!item.equals("-")) stack.push(item); 
            else if (stack.isEmpty())  StdOut.println("BAD INPUT"); 
            else                       StdOut.print(stack.pop() + " ");
        }
        StdOut.println();

        // print what's left on the stack
        StdOut.print("Left on stack: ");
        for (String s : stack) {
            StdOut.print(s + " ");
        }
        StdOut.println();
    } 
} 

(3)调整数组大小和对象游离

选择用数组表示栈内容意味着用例必须预先估计栈的最大容量,在Java中,数组一旦创建,大小不能被修改,如果用例所用内存与数组容量偏差较大,会导致内存浪费。因此,我们修改了数组的实现,动态调整数组a[]的大小。策略是:

首先实现一个方法将栈移动到另一个大小不同的数组中;

其次,在push()时,检查数组大小,即栈大小N和数组大小a.length()是否相等来检查数组是否能够容纳新的元素。如果没有多余空间,将数组容量翻倍。

同时,在pop()时,如果栈大小小于数组的四分之一,则将数组长度减半。

这样,栈永远不会溢出,使用率也不会低于四分之一。实现代码见后面。

对象游离:Java的垃圾收集策略是回收所有无法被访问的对象内存。在pop()中,被弹出的元素的引用仍存在于数组中,但它不会再被访问,但垃圾回收机制没法知道这一点,出现游离现象。解决办法很简单,将弹出的数组元素值设为null即可。

(4)迭代和改进算法

上面已经提到的foreach语句,如:

Stack stack = new Stack<>();
...
for(String s : stack)
    Std.println(s);
...

foreach其实可以看作是while语句的简写方式,它等价于:

Iterator i = collection.iterator();
while(i.hasNext())
{
    String s = i.next();
    Std.println(s);
}

这段代码展示了我们在任意可迭代的集合数据类型中需要实现的东西:

1)集合数据类型必须实现一个iterator()方法并返回一个Iterator对象;

2)Iterator类必须包含两个方法:hasNext()(返回一个布尔值)和next()(返回集合中的泛型元素)。

Java已经为我们定义好了一个迭代器接口Iterator:

public interface Iterator{
    boolean hasNext();
    Item next();
    void remove();   //可忽略,无实际作用
}

下面是下压栈能够动态调整数组大小的算法实现:

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

public class ResizingArrayStack implements Iterable {
    private Item[] a;         // array of items
    private int n;            // number of elements on stack


    /**
     * Initializes an empty stack.
     */
    public ResizingArrayStack() {
        a = (Item[]) new Object[2];
        n = 0;
    }

    /**
     * Is this stack empty?
     * @return true if this stack is empty; false otherwise
     */
    public boolean isEmpty() {
        return n == 0;
    }

    /**
     * Returns the number of items in the stack.
     * @return the number of items in the stack
     */
    public int size() {
        return n;
    }


    // resize the underlying array holding the elements
    private void resize(int capacity) {
        assert capacity >= n;

        // textbook implementation
        Item[] temp = (Item[]) new Object[capacity];
        for (int i = 0; i < n; i++) {
            temp[i] = a[i];
        }
        a = temp;

       // alternative implementation
       // a = java.util.Arrays.copyOf(a, capacity);
    }



    /**
     * Adds the item to this stack.
     * @param item the item to add
     */
    public void push(Item item) {
        if (n == a.length) resize(2*a.length);    // double size of array if necessary
        a[n++] = item;                            // add item
    }

    /**
     * Removes and returns the item most recently added to this stack.
     * @return the item most recently added
     * @throws java.util.NoSuchElementException if this stack is empty
     */
    public Item pop() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        Item item = a[n-1];
        a[n-1] = null;                              // to avoid loitering
        n--;
        // shrink size of array if necessary
        if (n > 0 && n == a.length/4) resize(a.length/2);
        return item;
    }


    /**
     * Returns (but does not remove) the item most recently added to this stack.
     * @return the item most recently added to this stack
     * @throws java.util.NoSuchElementException if this stack is empty
     */
    public Item peek() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        return a[n-1];
    }

    /**
     * Returns an iterator to this stack that iterates through the items in LIFO order.
     * @return an iterator to this stack that iterates through the items in LIFO order.
     */
    public Iterator iterator() {
        return new ReverseArrayIterator();
    }

    // an iterator, doesn't implement remove() since it's optional
    private class ReverseArrayIterator implements Iterator {
        private int i;

        public ReverseArrayIterator() {
            i = n-1;
        }

        public boolean hasNext() {
            return i >= 0;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public Item next() {
            if (!hasNext()) throw new NoSuchElementException();
            return a[i--];
        }
    }


    /**
     * Unit tests the {@code Stack} data type.
     *
     * @param args the command-line arguments
     */
    public static void main(String[] args) {
        ResizingArrayStack stack = new ResizingArrayStack();
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            if (!item.equals("-")) stack.push(item);
            else if (!stack.isEmpty()) StdOut.print(stack.pop() + " ");
        }
        StdOut.println("(" + stack.size() + " left on stack)");
    }
}

其ResizingArrayQueue的测试用例轨迹如下:

1.3 背包、队列与栈_第5张图片

7.链表

(1)构造链表

定义结点的抽象数据类型:

private class Node {
   Item item;
   Node next;
}

包含两个实例变量,Item和Node,其中Item是一个占位符,表示任意数据类型。

下面是构造链表的图示:

1.3 背包、队列与栈_第6张图片

(2)头插法与尾插法

不名思意,一个是从头部添加元素,一个是从链表尾部不断添加元素,只是指针的链接不同罢了。

(3)插入与删除

除了在表头表尾进行插入和删除,还可以删除指定的结点以及在指定结点前插入一个结点,解决这种操作的最好做法是采用双向链表的数据结构,每个结点都有两个链接,指向不同的方向,这里不继续展开讨论。

(4)遍历

访问链表中的所有元素,将循环的索引变量x初始化为链表的头结点,然后通过x.item访问x相关联的元素,并一直执行x.next访问链表的下一个结点,直至为null。

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

有必要说明链表和数组两个重要的基础数据结构的优缺点:

(5)背包的链表实现

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

public class Bag implements Iterable {
    private Node first;    // beginning of bag
    private int n;               // number of elements in bag

    // helper linked list class
    private static class Node {
        private Item item;
        private Node next;
    }

    /**
     * Initializes an empty bag.
     */
    public Bag() {
        first = null;
        n = 0;
    }

    /**
     * Returns true if this bag is empty.
     *
     * @return {@code true} if this bag is empty;
     *         {@code false} otherwise
     */
    public boolean isEmpty() {
        return first == null;
    }

    /**
     * Returns the number of items in this bag.
     *
     * @return the number of items in this bag
     */
    public int size() {
        return n;
    }

    /**
     * Adds the item to this bag.
     *
     * @param  item the item to add to this bag
     */
    public void add(Item item) {
        Node oldfirst = first;
        first = new Node();
        first.item = item;
        first.next = oldfirst;
        n++;
    }


    /**
     * Returns an iterator that iterates over the items in this bag in arbitrary order.
     *
     * @return an iterator that iterates over the items in this bag in arbitrary order
     */
    public Iterator iterator()  {
        return new ListIterator(first);  
    }

    // an iterator, doesn't implement remove() since it's optional
    private class ListIterator implements Iterator {
        private Node current;

        public ListIterator(Node first) {
            current = first;
        }

        public boolean hasNext()  { return current != null;                     }
        public void remove()      { throw new UnsupportedOperationException();  }

        public Item next() {
            if (!hasNext()) throw new NoSuchElementException();
            Item item = current.item;
            current = current.next; 
            return item;
        }
    }

    /**
     * Unit tests the {@code Bag} data type.
     *
     * @param args the command-line arguments
     */
    public static void main(String[] args) {
        Bag bag = new Bag();
        while (!StdIn.isEmpty()) {
            String item = StdIn.readString();
            bag.add(item);
        }

        StdOut.println("size of bag = " + bag.size());
        for (String s : bag) {
            StdOut.println(s);
        }
    }

}

这里,Bag的访问顺序是后进先出,不过这不重要。


学习永不止步,继续加油~

 

 

你可能感兴趣的:(算法)