棧與隊列問題(主要是棧的使用)


轉載請注明原文地址:http://www.cnblogs.com/ygj0930/p/6857537.html 

一:問題概述

    棧與隊列的相關算法題,一般都是基於對棧、隊列基本性質的熟練掌握的前提下,如何巧妙地組合、包裝,以達到某種原來數據類型所沒有的性質。比如:設計出一種能getMin()獲取棧中最小值的棧、利用棧實現隊列等等。問題的本意為考察棧、隊列的基本性質與基本操作的靈活使用,所以解題思路也是從這邊出發:怎么利用現有的數據結構以及其基本性質、操作,去組合、包裝,實現題目要求?

 

二:棧、隊的基本性質與操作

    棧:后進先出。Java中Stack類實現了棧,基本操作有:empty()判斷是否為空、peek()查看棧頂、pop()彈出棧頂、push()入棧。

    隊列:先進先出。Java中用LinkedList實現了隊列(LinkedList也實現了List接口和deque接口,所以LinkedList中包含了list、單向隊列、雙向隊列的操作),單向隊列的基本操作有:peek()查看隊首、poll()彈出隊首、offer()加入隊尾、size()返回大小。

           

三:設計一個可查詢最值的棧

    我們知道,基本的棧是“后進先出”的,棧中的最值沒有固定的位置,所以想要靠位置記錄來查找是行不通的;其次,棧中元素隨着push/pop操作,最值是會變化的,怎么隨着push/pop操作動態修改最值呢?

    下面是例題:

    定義一種棧的數據結構,在該類型中實現一個能夠得到棧最小元素的min函數。

public class Solution {
    //兩個棧,一個記錄數據,一個記錄棧中每層數據對應着的當前棧中最小值
    Stack data=new Stack();
    Stack min=new Stack();
    int curr_min=Integer.MAX_VALUE;
    
    //入棧
    public void push(int node) {
        data.push(node);
        //min棧記錄當前入棧元素對應的當前棧中最小值是誰
        if(node<=curr_min){
            min.push(node);
            curr_min=node;//更新最小值
        }else{
            min.push(curr_min);
        }
    }
    //彈出元素,兩個棧同步彈出
    public void pop() {
        data.pop();
        min.pop();
    }
    
    public int top() {
        return (Integer)data.peek();
    }
    
    public int min() {
        return (Integer)min.peek();
    }
}

 

四:用兩個棧,實現隊列

    棧是“后進先出”的,所以一般用於實現“倒序”問題。而隊列是“先進先出”的,用棧實現隊列,由“負負得正”即可知道,需要用兩個棧。第一個棧接收數據,第二個棧負責把第一個棧的數據順序調整回來,使先進第一個棧的,先從第二個棧彈出。題目唯一要注意的是:不是單純地把第一個棧的數據轉移到第二個棧,轉移的時機很重要——第二個棧為空時,才把第一個棧的元素全部轉移過來。

 public int[] twoStack(int[] ope, int n) {
        Stack in=new Stack();
        Stack out=new Stack();
        ArrayList<Integer> pops=new ArrayList<Integer>();
        
        for(int i:ope){
            if(i>0){
                in.push(i);
            }else{
                //雙棧實現隊列的唯一重點:有出隊命令時,首先應該檢查out棧有無元素,有的話直接out棧彈出即可;
                //若out棧為空,才把in棧中元素全部彈出入out棧,調換了位置,然后彈出out棧棧頂
                if(out.empty()){
                    while(!in.empty()){
                        int val=(Integer)in.pop();
                        out.push(val);
                    }
                }
                int valout=(Integer)out.pop();
                pops.add(valout);
                
            }
        }
        int[] res=new int[pops.size()];
        for(int i=0;i<pops.size();++i){
            res[i]=pops.get(i);
        }
        return res;
    }

 

五:實現棧的逆序

    在不借助額外的數據結構的前提下,把當前棧中的存儲的數據逆序存放。即:原先位於棧頂的,放到棧底......

    思路:

    這道題,如果可以借助額外的數據結構,則很容易實現,比如:使用第二個棧依次接收第一個棧中彈出的數據即可。但是題目要求不能借助額外的數據結構,這時我們就不得不找替代品——除了棧之外,還有什么擁有“后進先出”的特性呢?沒錯,那就是遞歸!其實遞歸函數的執行,歸根結底還是使用了棧的,那就是函數棧。所以,這里我們就在遞歸函數中操作原有棧,利用遞歸函數的return順序實現數據倒序。

   解題偶得:遞歸通常在參數中傳遞被各層操作的數據、也在參數中傳遞控制遞歸深度的變量。遞歸返回數據有兩種方法:遞歸函數的返回值、通過操作參數中的數組來攜帶數據。

  遞歸思路整理:

 

recur(){

   遞歸下層前代碼:按遞歸順序執行,同時,也是遞歸終點返回數據的地方;
  
   recur();//遞歸指令:從這里進入下層執行,等待下層執行完畢並返回

   遞歸后代碼:獲取下層執行完畢並返回的數據,繼續完成該層的所有操作。按遞歸順序的逆序執行

}

 

代碼:

   //對每一層:先取出棧中底部元素,並且不改變上面的元素
   public int getBottom(Stack<Integer> data){
      int curr=(Integer)data.pop();
       if(data.empty()){//如果取出后,棧空了,說明這是棧底元素,返回上層
           return curr;
       }
       int bottom=getBottom(data);//獲取下層返回的棧底元素
       data.push(curr);//把當前層元素重新入棧
       return bottom;//把獲取到的棧底元素往上傳遞
   }
    //實現逆序
    public void recur(Stack<Integer> data,int n){
        if(n==0){
            return;//控制遞歸終點
        }
        int bottom=getBottom(data);//按遞歸順序獲取棧底
        recur(data,n-1);
        data.push(bottom);//按遞歸逆序把每一層獲取到的棧底入棧,從而實現原先的棧底最后入棧,成為棧頂
    }

 

六:棧排序問題

    給定一個棧,棧中有數據。要求最多只能額外借助一個棧,不能用其他數據結構復制數據。實現對棧中數據排序。

   思路:使用一個輔助棧來協助排序,其實就是一個不斷彈出、比較、放回、入棧的過程。(類比漢諾塔)

    public ArrayList<Integer> twoStacksSort(int[] numbers) {
      //由於Stack的peek()沒元素則拋出異常,而LinkedList中的peekFirst()無元素則返回Null。所以用LinkedList模擬棧
      LinkedList<Integer> A=new LinkedList<Integer>();
      LinkedList<Integer> B=new LinkedList<Integer>();
        //對需要處理的數據元素入棧
        for(int i=numbers.length-1;i>=0;--i){
            A.push(numbers[i]);
        }
        //雙棧排序
        while(A.size()>0){
            Integer curr=A.pop();
            //linkedlist沒有empty()方法,所以只能用size()與0比較判斷是否為空
            if(B.size()==0){
                B.push(curr);
                continue;
            }else{
                //如果A彈出的元素大於B棧頂,則需要把它放在當前B棧頂的下面某處
                while(B.peekFirst()!=null && curr>B.peekFirst()){
                //所以把B棧頂彈出,放回A,使得B棧中第二個元素成為棧頂,用於下一循環時比較
                Integer back=B.pop();
                //把B彈回的元素放入A棧,等下重新彈出入B棧
                A.push(back);
            }
            //B棧中小於當前A彈出元素的已回到A棧,這時把curr入B棧,就是合適的位置。B棧從下往上是大——>小。
            B.push(curr);
          }
        }
        //上面循環結束后,B棧中元素已經是有序的:從下往上==從大到小
        while(B.size()>0){
            A.push((Integer)B.pop());
        }
        ArrayList<Integer> res=new ArrayList<Integer>();
        while(A.size()>0){
            res.add((Integer)A.pop());
        }
        return res;
    }   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM