目录

栈和队列

栈和队列

许多基础数据类型都和对象的集合有关。具体来说,数据类型的值就是一组对象的集合,所有操作都是关于添加、删除或者是访问集合中的对象。这里主要讨论的是栈(stack)和队列(queue)的几种实现。

链表

**定义:**链表是一种递归的数据结构,它或者为空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型元素和一个指向另一条链表的引用。

**注意:**之所以在这里定义,是因为后续我们的栈和队列的实现都要使用链表来构造。在结构化存储数据集时,链表是数组的一种重要的代替方式

1
2
3
4
5
6
//定义Node,且将Node声明为私有嵌套类之后,我们可以将Node的方法和实例变量的访问范围限制在包含它的类中
private class Node
{
	Item item;
	Node next;
}
1
2
3
数据结构							优点								缺点
数组						通过索引可以直接访问任意元素				在初始化时就需要知道元素的数量
链表						使用的空间大小和元素数量成正比				需要通过引用访问任意元素

下压栈或简称栈,是一种基于后进先出(LIFO)策略的集合类型。


1
2
3
4
5
6
7
8
9
下压栈后进先出LIFO

public class Stack<Item> implements Iterable<Item>

Stack() //创建一个空栈
void push() //添加一个元素
Item pop()	//删除最近添加的元素
boolean isEmpty() //栈是否为空
int size()	//栈中元素的数量

下压(LIFO)栈(能够动态的调整数组大小)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.Iterator;
import java.util.Objects;
public class ResizingArrayStack<Item> implements Iterable<Item> {
    private Item[] a = (Item[]) new Objects[1];//栈元素
    private int N = 0;//元素数量
    public boolean isEmpty() {
        return N == 0;
    }
    public int size() {
        return N;
    }
    private void resize(int max) {
        //将栈移动到一个大小为max的新数组
        Item[] temp = (Item[]) new Objects[max];
        for (int i = 0; i < N; i++) {
            temp[i] = a[i];
        }
        a = temp;
    }
    public void push(Item item) {
        //将元素添加到栈顶
        if (N == a.length)
            resize(2 * a.length);
        a[N++] = item;
    }
    public Item pop() {
        //从栈顶删除元素
        Item item = a[--N];
        a[N] = null;//避免对象游离
        if (N > 0 && N == a.length / 4) resize(a.length / 2);
        return item;
    }
    @Override
    public Iterator<Item> iterator() {
        return (Iterator<Item>) new ReverseArrayIterator();
    }
    private class ReverseArrayIterator implements Iterable<Item> {
        //支持后进先出的迭代
        private int i = N;
        public boolean hasnext() {
            return i > 0;
        }
        public Item next() {
            return a[--i];
        }
        public void remove() {
        }
        @Override
        public Iterator<Item> iterator() {
            return null;
      }
    }
}

这份泛型的可迭代的Stack API的实现是所有集合类抽象数据类型实现的模板。它将所有元素保存在数组中,并动态调整数组的大小以保持数组大小和栈大小之比小于一个常数。

特别要注意的是

  • push()操作中,检查数组是否太小。具体来说,我们会通过检查栈大小 N 和数组大小a.length是否相等来检查数组是否能够容纳新的元素。如果没有多余的空间,我们会将数组的长度加倍。然后就可以像以前一样使用a[N++] = item插入新元素了。
  • 类似,pop()操作中,首先删除栈顶元素,然后如果数组太大我们就将它的长度减半。只要稍加思考,你就明白正确的检测条件是栈的大小是否小于数组的四分之一。在数组长度被减半之后,它的状态大约为半满,在下次需要改变数组大小之前 仍然能够进行多次push()pop()操作。
  • 在以上实现中,栈永远不会溢出,使用率也永远不会低于四分之一(除非栈为空,那种情况下数组的大小为 1)。

应用

  • 算数表达式求值

    • 将操作数压入操作数栈;
    • 将运算符压入运算符栈;
    • 忽略左括号;
    • 在遇到右括号时,弹出一个运算符,弹出所需数量的操作数,并将运算符和操作数的运算结果压入操作数栈。

    Dijkstra 的双栈算数表达式求值算法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    public class Evaluate {
        public static void main(String[] args) {
            Stack<String> ops = new Stack<String>();
            Stack<Double> vals = new Stack<Double>();
            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);
                }//如果字符既不是运算符也不是运算数,将它作为double压入栈
                else vals.push(Double.parseDouble(s));
            }
            StdOut.println(vals.pop());
        }
      }
    

下压堆栈(链表实现)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Stack<Item> implements Iterable<Item>{
	private Node first;//栈顶,最近添加的元素
	private int N;//元素的数量
	private class Node{
		//定义了结点的嵌套
		Item item;
		Node next;
	}
	public boolean isEmpty(){return first == null;}//或N == 0
	public int size(){return N;}
	public void push(Item item){
		Node oldfirst = first;//将栈顶元素变成第二个最近添加的元素
		first = new Node();//申请一个新的结点
		first.item = item;//新结点的item赋值
		first.next = oldfirst;//连接,此时新结点变成栈顶的元素
		N++;
	}
	public Item pop(){
		Item item = first.item;//保存栈顶元素的item值
		first = first.next;//修改栈顶元素,first的后一个元素变为栈顶元素
		N--;
		return item;//返回栈顶元素
	}
}

队列

先进先出队列或简称队列,是一种基于先进先出(FIFO)策略的集合类型。


1
2
3
4
5
6
7
8
9
先进先出FIFO队列

public class Queue<Item> implements Iterable<Item>

Queue() //创建空队列
void enqueue() //添加一个元素
Item dequeue() //删除最早添加的元素
boolean isEmpty() //判断队列是否为空
int size() //队列中元素的数量`

先进先出队列(链表实现)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Queue<Item> implements Iterable<Item>{
	private Node first;//指向最早添加的结点链接
	private Node last;//指向最近添加的结点链接
	private int N;//队列中的元素数量
	private class Node{
		Item item;
		Node next;
	}
	public boolean isEmpty(){return N == 0;}//或者first == null
	public int size(){return N;}
	public void enqueue(){
		//向表尾添加元素
		Node oldlast = last;
		last = new Node();
		last.item = item;
		last.next = null;
		if(isEmpty()){//判断队列是否之前是空队列,如果是那么此时队列里只有刚刚添加的一个元素
			first = last;
		}
		else
			oldlast.next = last;
		N++;
	}
	public Item dequeue(){
		//从表头删除元素
		Item item = first.item;
		first = first.next;
		if(isEmpty()){
			last = null;
		}
		N--;
		return item;
	}
}