栈与队列

1.数据结构

1.1 ArrayDeque (栈、队列)

1.2 LinkedList

1.3 PriorityQueue

2.编程题

Q1.描述如何只用一个数组来实现三个栈

Ans.思路:

  • 简单的方法是分配固定空间大小
  • 较难的方式:弹性处理栈空间。将数组设计成环状。
    1)push的实现方式:
    1-1)正常情况,直接增加,注意是环形的;
    1-2)当栈的size达到capacity时,此时需要扩容该栈。
    当三个栈全满时,无法处理,此时抛出异常;
    否则,将当前栈的下一个栈向后移动一个位置,如果下一个栈也满了,递归处理(总会有一个栈没满,因为全满时会抛出异常)。移动时,因为是环形的,处理时也要注意。
    2)pop实现方式:
    size为空,抛出异常;
    否则,按照栈的正常弹出处理,注意是环形的。比push要简单。

简单的方式:

    static int stackSize = 10;
    static int [] buffer = new int [stackSize * 3];
    
    // 3 stack pointers to keep track of the index of the top element
    static int [] stackPointer = {-1, -1, -1};
    static void push(int stackNum, int value) throws Exception {
        /* Check that we have space for the next element */
        if (stackPointer[stackNum] + 1 >= stackSize) { 
            throw new FullStackException();
        }
        /* Increment stack pointer and then update top value*/      
        stackPointer[stackNum]++;
        buffer[absTopOfStack(stackNum)] = value;    
    }

    static int pop(int stackNum) throws Exception {
        if (isEmpty(stackNum)) {
            throw new EmptyStackException();
        }
        int value = buffer[absTopOfStack(stackNum)]; // Get top
        buffer[absTopOfStack(stackNum)] = 0; // Clear index
        stackPointer[stackNum]--; // Decrement pointer
        return value;
    }

    static int peek(int stackNum) {
        if (isEmpty(stackNum)) {
            throw new EmptyStackException();
        }       
        return buffer[absTopOfStack(stackNum)];
    }

    static boolean isEmpty(int stackNum) {
        return stackPointer[stackNum] == -1;
    }
    
    /* returns index of the top of the stack "stackNum", in absolute terms */
    static int absTopOfStack(int stackNum) {
        return stackNum * stackSize + stackPointer[stackNum];
    }   

弹性方式:

    static void push(int stackNum, int value) throws Exception {
        StackData stack = stacks[stackNum];
        /* Check that we have space */
        if (stack.size >= stack.capacity) {
            if (numberOfElements() >= total_size) { // Totally full
                throw new Exception("Out of space."); 
            } else { // just need to shift things around
                expand(stackNum);
            }
        }
        /* Find the index of the top element in the array + 1, 
         * and increment the stack pointer */   
        stack.size++;
        stack.pointer = nextElement(stack.pointer);     
        buffer[stack.pointer] = value;  
    }

    static int pop(int stackNum) throws Exception {
        StackData stack = stacks[stackNum];     
        if (stack.size == 0) {
            throw new Exception("Trying to pop an empty stack.");
        }
        int value = buffer[stack.pointer];
        buffer[stack.pointer] = 0;
        stack.pointer = previousElement(stack.pointer);
        stack.size--;
        return value;
    }

    static int peek(int stackNum) {
        StackData stack = stacks[stackNum];         
        return buffer[stack.pointer];
    }

    static boolean isEmpty(int stackNum) {
        StackData stack = stacks[stackNum];
        return stack.size == 0;
    }

辅助方法:

    public static int numberOfElements() {
        return stacks[0].size + stacks[1].size + stacks[2].size;
    }
    
    public static int nextElement(int index) {
        if (index + 1 == total_size) {
            return 0;
        } else {
            return index + 1;
        }
    }
    
    public static int previousElement(int index) {
        if (index == 0) {
            return total_size - 1;
        } else {
            return index - 1;
        }
    }
    
    public static void shift(int stackNum) {
        StackData stack = stacks[stackNum];
        if (stack.size >= stack.capacity) {
            int nextStack = (stackNum + 1) % number_of_stacks;
            shift(nextStack); // make some room
            stack.capacity++;
        }
        for (int i = (stack.start + stack.capacity - 1) % total_size; // end of array
                      stack.isWithinStack(i, total_size); 
                      i = previousElement(i)) {
            buffer[i] = buffer[previousElement(i)];
        }
        buffer[stack.start] = 0;
        stack.start = nextElement(stack.start); // move start start
        stack.pointer = nextElement(stack.pointer); // move stack pointer
        stack.capacity--; // return capacity to original
    }

栈数据为:

public class StackData {
    public int start;
    public int pointer;
    public int size = 0;
    public int capacity;
    public StackData(int _start, int _capacity) {
        start = _start;
        pointer = _start - 1;
        capacity = _capacity;
    }
    
    public boolean isWithinStack(int index, int total_size) {
        // Note: if stack wraps, the head (right side) wraps around to the left. 
        if (start <= index && index < start + capacity) { 
            // non-wrapping, or "head" (right side) of wrapping case
            return true;
        } else if (start + capacity > total_size && 
                   index < (start + capacity) % total_size) {
            // tail (left side) of wrapping case
            return true;
        }
        return false;
    }
}

Q2.设计一个栈,除pop与push方法,还支持min方法,可返回栈元素中的最小值。push、pop和min三个方法的时间复杂度必须为O(1)。

Ans.思路:

  • 使用举例方法,即可发现规律
    push(5) —— 栈:5,min 5
    push(6) —— 栈:5 6,min 5
    push(3) —— 栈:5 6 3,min 3
    push(7) —— 栈:5 6 3 7,min 3
    pop() —— 栈:5 6 3, min 3
    pop() —— 栈:5 6, min 5
    pop() —— 栈:5, min 5
    一种直接的思路是,对于每个入栈的元素,会记录下当前最小值。
  • 改进:上面做法较为浪费空间,一种改进的做法,利用栈来记录这些min。当压入栈的值value <= min当前最小值,则入栈。必须要取等号,防止多个想等值入栈后,弹出时出错。
public class StackWithMin2 extends ArrayDeque<Integer> {
    ArrayDeque<Integer> s2;
    
    public StackWithMin2() {
        s2 = new ArrayDeque<Integer>();
    }
    
    @Override
    public void push(Integer value){
        if (value <= min()) {
            s2.push(value);
        }
        super.push(value);
    }

    @Override
    public Integer pop() {
        int value = super.pop();
        if (value == min()) {
            s2.pop();           
        }
        return value;
    }
    
    public int min() {
        if (s2.isEmpty()) {
            return Integer.MAX_VALUE;
        } else {
            return s2.peek();
        }
    }
}

Q3.设想有一堆盘子,堆太高可能会倒下。因此,在现实生活中,盘子堆到一定高度时,就会另外堆一堆盘子。请实现数据结构SetOfStacks,模拟这种行为。SetOfStacks应该由多个栈组成,并且在前一个栈填满时新建一个栈。此外,SetOfStacks.push()和SetOfStacks.pop()应该与普通栈的操作方法相同。 实现一个popAt(int index)方法,根据指定子栈,执行pop操作。

Ans.思路:

  • push:对当前最后一个栈调用push,若最后一个栈是满的,则新建一个栈
  • pop:操作最后一个栈,弹出后,若最后一个栈为空,则必须移除该栈
  • popAt(int index):1)弹出时,后续栈补入,这样操作对于用户是透明的;2)弹出时,后续不进行补入,但是底层的假设有变化,即所有栈都是满的这一条件不再满足。
public class SetOfStacks {
    ArrayList<Stack> stacks = new ArrayList<Stack>();
    public int capacity;
    
    public SetOfStacks(int capacity) { 
        this.capacity = capacity; 
    }
    
    public Stack getLastStack() {
        if (stacks.size() == 0) {
            return null;
        }
        return stacks.get(stacks.size() - 1);
    }
    
    public void push(int v) {
        Stack last = getLastStack();
        if (last != null && !last.isFull()) { // add to last
            last.push(v);
        } else { // must create new stack
            Stack stack = new Stack(capacity);
            stack.push(v);
            stacks.add(stack);
        }
    }
    
    public int pop() {
        Stack last = getLastStack();
        int v = last.pop();
        if (last.size == 0) {
            stacks.remove(stacks.size() - 1);
        }
        return v;
    }
    
    public int popAt(int index) {
        return leftShift(index, true);
    }
    
    public int leftShift(int index, boolean removeTop) {
        Stack stack = stacks.get(index);
        int removed_item;
        if (removeTop) removed_item = stack.pop();
        else removed_item = stack.removeBottom();
        if (stack.isEmpty()) {
            stacks.remove(index);
        } else if (stacks.size() > index + 1) {
            int v = leftShift(index + 1, false);
            stack.push(v);
        }
        return removed_item;
    }
    
    public boolean isEmpty() {
        Stack last = getLastStack();
        return last == null || last.isEmpty();
    }
}

单个栈:

public class Stack {
    private int capacity;
    public Node top;
    public Node bottom;
    public int size = 0;
    
    public Stack(int capacity) { 
        this.capacity = capacity; 
    }
    
    public boolean isFull() { 
        return capacity == size; 
    }
    
    public void join(Node above, Node below) {
        if (below != null) below.above = above;
        if (above != null) above.below = below;
    }
    
    public boolean push(int v) {
        if (size >= capacity) return false;
        size++;
        Node n = new Node(v);
        if (size == 1) bottom = n;
        join(n, top);
        top = n;
        return true;
    }
    
    public int pop() {
        Node t = top;
        top = top.below;
        size--;
        return t.value;
    }
    
    public boolean isEmpty() { 
        return size == 0; 
    }
    
    public int removeBottom() {
        Node b = bottom;
        bottom = bottom.above;
        if (bottom != null) bottom.below = null;
        size--;
        return b.value;
    }
}

元素类型:

public class Node {
    public Node above;
    public Node below;
    public int value;
    public Node(int value) {
        this.value = value;
    }
}

Q4.在经典问题汉诺塔中,有三根柱子及N个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自底向上从大到小依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时有以下限制:
1)每次只能移动一个盘子
2)盘子只能从柱子顶端滑出移到下一根柱子
3)盘子只能叠在比它大的盘子上
请运用栈,编写程序将所有盘子从第一根柱子移动到最后一根柱子。

Ans.思路:

  • 一种很显然的思路是:递归法。
    从最终状态(所有盘子在第三根柱子)回退至前面一大步(最大盘子在第三根柱子,其他盘子在第二根柱子);
    然后转化为解决子问题:怎样将n-1个盘子从第一个柱子移动到第二根柱子。
    递归子问题性质一样,可以一直这样向下进行解决。

思路伪代码描述:

moveDisks(int n, Tower origin, Tower destination, Tower buffer) {
  if (n <= 0) return;
  // 将n-1个盘子从origin移动到buffer
  moveDisks(n - 1, origin, buffer, destination);
  // 将剩下的最大的盘子移动到destination
  moveTop(origin, destination);
  // 将顶部n - 1个盘子从buffer移至destination
  moveDisks(n - 1, buffer, destination, origin);
}

代码:

public class Tower {
    private ArrayDeque<Integer> disks;
    private int index;
    public Tower(int i) {
        disks = new ArrayDeque<Integer>();
        index = i;
    }
    
    public int index() {
        return index;
    }
    
    public void add(int d) {
        if (!disks.isEmpty() && disks.peek() <= d) {
            System.out.println("Error placing disk " + d);
        } else {
            disks.push(d);
        }
    }
    
    public void moveTopTo(Tower t) {
        int top = disks.pop();
        t.add(top);
    }
    
    public void print() {
        System.out.println("Contents of Tower " + index() + ": " + disks.toString());
    }
    
    public void moveDisks(int n, Tower destination, Tower buffer){
        if (n > 0) {
            String tag = "move_" + n + "_disks_from_" + this.index + "_to_" + destination.index + "_with_buffer_" + buffer.index; 
            System.out.println("<" + tag + ">");
            moveDisks(n - 1, buffer, destination);
            System.out.println("<move_top_from_" + this.index + "_to_" + destination.index + ">");
            System.out.println("<before>");
            System.out.println("<source_print>");
            this.print();
            System.out.println("</source_print>");
            System.out.println("<destination_print>");
            destination.print();
            System.out.println("</destination_print>");
            System.out.println("</before>");
            moveTopTo(destination);
            System.out.println("<after>");
            System.out.println("<source_print>");
            this.print();
            System.out.println("</source_print>");
            System.out.println("<destination_print>");
            destination.print();
            System.out.println("</destination_print>");
            System.out.println("</after>");
            System.out.println("</move_top_from_" + this.index + "_to_" + destination.index + ">");
            buffer.moveDisks(n - 1, destination, this);
            System.out.println("</" + tag + ">");
        }
    }
}

Q5.实现一个MyQueue类,该类用两个栈来实现一个队列。

Ans.思路

  • 一个栈用于入队;利用另一个栈反转次序,该栈用于出队。
  • 当出队的栈为空时,此时再将入队的栈压入。
public class MyQueue<T> {
    ArrayDeque<T> stackNewest, stackOldest;
    
    public MyQueue() {
        stackNewest = new ArrayDeque<T>();
        stackOldest = new ArrayDeque<T>();
    }
    
    public int size() {
        return stackNewest.size() + stackOldest.size();
    }
    
    public void add(T value) {
        // Push onto stack1
        stackNewest.push(value);
    }
    
    /* Move elements from stackNewest into stackOldest. This is usually done so that we can
     * do operations on stackOldest.
     */
    private void shiftStacks() {
        if (stackOldest.isEmpty()) { 
            while (!stackNewest.isEmpty()) {
                stackOldest.push(stackNewest.pop());
            }
        }
    }
    
    public T peek() {
        shiftStacks();
        return stackOldest.peek(); // retrieve the oldest item.
    }
    
    public T remove() {
        shiftStacks();
        return stackOldest.pop(); // pop the oldest item.
    }
}

Q6.编写程序,按升序对栈进行排序(即最大元素位于栈顶)。最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构中(如数组)。该栈支持如下操作:push、pop、peek和isEmpty。

Ans.思路:

  • 核心点是:s1栈是乱序的,s2是有序的,怎样将s1中的数有序插入到s2中?
  • 方法(本质跟汉诺塔一样):
    step1.弹出s1的数到临时变量top
    step2.将s2中大于top的全部弹出到s1中,压入top
    step3.再将之前弹出到s1中的压回到s2
    public static ArrayDeque<Integer> sort(ArrayDeque<Integer> s) {
        ArrayDeque<Integer> r = new ArrayDeque<Integer>();
        while(!s.isEmpty()) {
            int tmp = s.pop();
            while(!r.isEmpty() && r.peek() > tmp) {
                s.push(r.pop());
            }
            r.push(tmp);
        }
        return r;
    }

如果不限栈数量,可以使用mergesort思路,实现时要特别注意:要保持栈里面的顺序是栈顶为最大,随意最后有一个reverse的过程必不可少。

   static int c = 0;
    public static ArrayDeque<Integer> mergesort(ArrayDeque<Integer> inStack) {
        if (inStack.size() <= 1) {
            return inStack;
        }

        ArrayDeque<Integer> left = new ArrayDeque<Integer>();
        ArrayDeque<Integer> right = new ArrayDeque<Integer>();
        int count = 0;
        while (inStack.size() != 0) {
            count++;
            c++;
            if (count % 2 == 0) {
                left.push(inStack.pop());
            } else {
                right.push(inStack.pop());
            }
        }

        left = mergesort(left);
        right = mergesort(right);

        while (left.size() > 0 || right.size() > 0)
        {
            if (left.size() == 0)
            {
                inStack.push(right.pop());
            }
            else if (right.size() == 0)
            {
                inStack.push(left.pop());
            }
            else if (right.peek().compareTo(left.peek()) <= 0)
            {
                inStack.push(left.pop());
            }
            else
            {
                inStack.push(right.pop());
            }
        }

        ArrayDeque<Integer> reverseStack = new ArrayDeque<Integer>();
        while (inStack.size() > 0)
        {
            c++;
            reverseStack.push(inStack.pop());
        }

        return reverseStack;
    }

Q7.有家动物收容所收容狗和猫,且严格遵循“先进先出”原则。在收养该收容所的动物时,收养人只能收养所有动物中“最老”的动物,或者,可以挑选猫或狗(同时必须是此类中最老的)。请创建适用于这个系统的数据结构,实现enqueue dequeueAny dequeueDog dequeueCat。允许使用LinkedList。

Ans.思路

  • 直观的方法是猫狗各创建一个队列,然后放入一个包裹类。
public class AnimalQueue {
    LinkedList<Dog> dogs = new LinkedList<Dog>();
    LinkedList<Cat> cats = new LinkedList<Cat>();
    private int order = 0;
    
    public void enqueue(Animal a) {
        a.setOrder(order);
        order++;
        if (a instanceof Dog) {
            dogs.addLast((Dog) a);
        } else if (a instanceof Cat) {
            cats.addLast((Cat)a);
        }
    }
    
    public Animal dequeueAny() {
        if (dogs.size() == 0) {
            return dequeueCats();
        } else if (cats.size() == 0) {
            return dequeueDogs();
        }
        Dog dog = dogs.peek();
        Cat cat = cats.peek();
        if (dog.isOlderThan(cat)) {
            return dogs.poll();
        } else {
            return cats.poll();
        }
    }
    
    public Animal peek() {
        if (dogs.size() == 0) {
            return cats.peek();
        } else if (cats.size() == 0) {
            return dogs.peek();
        }
        Dog dog = dogs.peek();
        Cat cat = cats.peek();
        if (dog.isOlderThan(cat)) {
            return dog;
        } else {
            return cat;
        }
    }
    
    public int size() {
        return dogs.size() + cats.size();
    }
    
    public Dog dequeueDogs() {
        return dogs.poll();
    }
    
    public Dog peekDogs() {
        return dogs.peek();
    }
    
    public Cat dequeueCats() {
        return cats.poll();
    }
    
    public Cat peekCats() {
        return cats.peek();
    }
}

动物类:

public abstract class Animal {
    private int order; 
    protected String name;
    public Animal(String n) {
        name = n;
    }
    
    public abstract String name();
    
    public void setOrder(int ord) {
        order = ord;
    }
    
    public int getOrder() {
        return order;
    }
    
    public boolean isOlderThan(Animal a) {
        return this.order < a.getOrder();
    }
}
public class Cat extends Animal {
    public Cat(String n) {
        super(n);
    }
    
    public String name() {
        return "Cat: " + name;
    }
}
public class Dog extends Animal {
    public Dog(String n) {
        super(n);
    }
    
    public String name() {
        return "Dog: " + name;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 在这篇文章里,我们来实现自定义的链式栈。首先我们来看看链式栈的结构及操作定义。 链式栈结构定义 首先,新建两个文件...
    我叫卡卡算了阅读 810评论 0 2
  • 栈 栈是限定仅在表尾进行插入和操作的线性表;允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为...
    Bangys阅读 435评论 0 0
  • 栈与队列 记录《剑指offer》中所有关于栈和队列的题目,以及LeetCode中的相似题目。 相关题目列表 题目 ...
    wenmingxing阅读 410评论 0 5
  • 1.栈 1.1.栈的定义 栈(stack)是限定仅在表尾(栈顶 top)进行插入和删除操作的后进先出的线性表。 p...
    JonyFang阅读 1,354评论 0 21
  • Important! Now JavaScript教程Y Combinator's How to Start a ...
    _Infinite阅读 339评论 0 0