數據結構專題( 二)——棧與隊列

什麼是棧?

其實棧就是一種後進先出的結構,就像一沓書本,每次取書本只能從最上面取,那麼壓在最底下的書本肯定是最先擺放在桌面上的,自然而然肯定是最後被取走的。

什麼是隊列?

隊列是一種先進先出的結構,隊列在生活中的例子就更多了,火車站排隊,食堂買盒飯的隊伍,其實都可以抽象成一個隊列,每當一個人買完票或者拿到盒飯就會離開隊列,先來到售票或者食堂窗口的人肯定是先買到票或者吃飯的。

數據結構其實充滿了生活的影子,它們都是實際生活中抽象出來供人們更好理解計算機的工具而已。那棧與隊列有什麼用處呢? 

先說棧吧,棧可以用來實現圖的搜索算法以及括號匹配,表達式計算等功能,正因爲有棧,纔有現在衆多能夠實現括號匹配功能的開發軟件,網絡頁面搜索等。隊列正因爲有先進先出的功能,可以用來實現操作系統中的消息隊列功能等。

關於棧與隊列的面試題

  (1)棧的創建

  (2)隊列的創建

  (3)兩個棧實現一個隊列

  (4)兩個隊列實現一個棧

  (5)設計含最小函數min()的棧,要求min、push、pop、的時間複雜度都是O(1)

  (6)判斷棧的push和pop序列是否一致

       (7)計算表達式的值

1. 棧的創建

在Java的API本來就已經有棧與隊列的對象,所以我們只需要使用已有的工具進行構造。但是若想要自己構建這兩種數據結構,具體實現可以使用數組或者鏈表,通過設置元素的添加與刪除順序就可以形成棧或者隊列的結構。接下來我將使用Java自己創建棧與隊列。

class Stack {
    final int MAX_SIZE = 10;
    int[] stack;
    int top;
 
    public Stack() {
        stack = new int[MAX_SIZE];
        top = -1;
    }
 
    public void push(int data) {
        stack[++top] = data;
    }
 
    public int pop() {
        return stack[top--];
    }
 
    //查看棧頂元素
    public int peek() {
        return stack[top];
    }
 
    public boolean isEmpty() {
        return top == -1;
    }
}

2. 隊列的創建

關於隊列的創建可以使用上一章內容的鏈表結構,通過設置頭節點與尾節點來控制鏈表的操作。至於Node<T>對象可以參照

數據結構(一)——數組與鏈表(問題解決與總結)中構建節點對象的過程。

class Queue{
	
	Node<Integer> front;
	Node<Integer> rear;
	int size = 0;
	
	public Queue(){
	    front = null;
	    rear = null;
	}
	
	//隊列尾部增加元素
	public void insert(Node<Integer> newNode) {
		
		if(isEmpty()) {
			front = newNode;
		}else {
			rear.next = newNode;
		}
		rear = newNode;
		size++;
	}
	
	//隊頭刪除元素
	public int remove() {
		
		int temp = 0;
		
		if(isEmpty()) {
			System.out.print("the queue is empty");
		}else {
			
			temp = front.data;
			front = front.next;
			size--;	
		}
		return temp;
	}
	
	//取出隊頭元素進行查看
	public int poll() {
		return front.data;
	}
	
	public boolean isEmpty() {
		return front == null;
	}
}

3. 兩個棧實現一個隊列

兩個棧實現一個隊列:通過繪圖我們可以很清楚看到解決辦法,棧1用來存儲數據,棧2用來取出數據。

public void stackToQueue(int[] arr) {
		
		Stack stack1 = new Stack();
		Stack stack2 = new Stack();
		
		for(int i = 0; i < arr.length; i++) {
			stack1.push(arr[i]);
		}
		
		if(stack2.isEmpty()) {
			while(!stack1.isEmpty()) {
				stack2.push(stack1.pop());
			}
			while(!stack2.isEmpty()) {
				System.out.println(stack2.pop());
			}
			
		}
	}

4. 兩個隊列實現一個棧

思路:兩個隊列交替進行進隊出隊操作,具體的可以通過步驟示意圖來進行了解。

具體的代碼實現如下:

public class queueToStack {
	
	Queue q1 = new Queue();
	Queue q2 = new Queue();
	
	//假設隊列1中有n個元素,在取數據時,需要將隊列1中的n-1個元素放到隊列2中存儲,隊列一最後一個元素則進行出隊操作,該元素出隊後的隊列1爲空
	//此時再將隊列2中的n-2個數據存儲到隊列1中,隊列2執行出隊操作,於是第二個元素就被取出,初步完成棧的操作。
	//存儲+取出的過程由兩個隊列交替完成。

	public int pop() {

		int result = 0;
		
		//pop操作 先判斷兩個隊列的狀態,如果q1,q2都爲空,那麼就沒有值可以返回,此時可以設置返回-1表示異常
		if(q1.isEmpty() && q2.isEmpty()) {
			return -1;
		}
		//如果q1爲空,q2不爲空,那麼就先操作q2
		if(q1.isEmpty() && !q2.isEmpty()) {
			//若q2裏有多個元素,那麼需要全部移動到q1中去,使得q2中只留下最後一個元素,那麼這是第一個應該輸出的元素
			while(!q2.isEmpty()) {
				result = q2.remove();
				if(!q2.isEmpty()) {
					q1.insert(new Node<Integer>(result));
				}	
			}
		//若q1裏本來有多個元素,那麼需要藉助q2去存儲,使得q1只剩下最後一個元素的時候,那就是第一個輸出元素,基本原理和上步驟一致
		}else if(!q1.isEmpty() && q2.isEmpty()) {
			while(!q1.isEmpty()) {
				result = q1.remove();
				if(!q1.isEmpty()) {
					q2.insert(new Node<Integer>(result));
				}
			}
		}
		return result;
	}	
	
	public void push(Node<Integer> newNode) {
		
		if(q1.isEmpty() && q2.isEmpty()) {
			q1.insert(newNode);
		}
		
		if(!q1.isEmpty() && q2.isEmpty()) {
			q1.insert(newNode);
		}
		
		if(q1.isEmpty() && !q2.isEmpty()) {
			q2.insert(newNode);
		}
	}

}

5. 設計含最小函數min()的棧,要求min、push、pop、的時間複雜度都是O(1)

具體思路可以使用空間換取時間的策略,通過製造一個新的棧去比較存儲最小值,每次進行進棧操作的時候都與存儲在另一個棧中的最小值進行比較,對結果進行不斷的更新,最後調用getMin()函數時,彈出最小棧中的值。

代碼實現如下:

public class MinStack {
	
	Stack mainStack = new Stack();
	Stack minStack = new Stack();
	
	public int pop() {
		if(mainStack.peek() == minStack.peek()) {
			return minStack.pop();
		}
		return mainStack.pop();
	}
	
	public void push(int data) {
		mainStack.push(data);
		int min = 0;
		if(minStack.isEmpty() || minStack.peek() >= data) {
			min = mainStack.peek();
			minStack.push(min);
		}
	}
	
	public int getMin() {
		return minStack.peek();
	}
}

(6)判斷棧的push和pop序列是否一致

題目要求是判斷棧的push與pop是否一致。要求函數接受兩個整數數組的輸入(兩組整數對應的分別是push順序與pop順序),然後判斷在該push順序下是否存在輸入的pop順序。

最容易想到的解決方式就是構造一個棧模擬輸入,看是否能夠得到輸入的pop順序。

	public void judgeStack(int[] input, int[] sample) {
		
		Stack s = new Stack();
		
		int i = 0, j = 0;
		List<Integer> ans = new ArrayList<Integer>();
	
		//算法思想:設立兩個指針,分別指向這兩個數組,注意指針角標移動的位置,避免出現空指針異常。
		while(i <= input.length - 1) {
			if(input[i] != sample[j]) {
				s.push(input[i]);
				i++;
			}else{
				ans.add(sample[j]);
				i++;
				j++;
			}
		}
		
		while(!s.isEmpty()) {
			int temp = s.pop();
			ans.add(temp);
		}
		
		for(int k = 0; k < ans.size(); k++) {
			System.out.println(ans.get(k));
		}
	}

以上的算法佔用的資源是比較大的,也許會有更好的解法,希望讀者能給出更好的意見哈~

7. 計算表達式的值

這道題目使我發自內心喜歡Python的簡介,真的是“人生苦短,我用python”的真實反映了。

如果選擇用python實現這個功能,那麼代碼將只有如下兩行:

s=input()
print(s)

這就是python非常強大的地方,如果使用Java實現,那麼代碼量可能就是大幾十行,可能超過一百行。計算表達式的數據結構也是基於棧。首先我們需要了解的知識點有前綴,中綴與後綴表達式。

人們最常用的表達式是中綴表達式,運算符號在數字之間,需要使用括號來確定運算次序,例如:(1+2)*3

中綴表達式符合人類的習慣,但是對於計算機來說去沒有這麼容易理解,所以很多時候人們會將中綴表達式變形爲前綴與後綴表達式,從而讓計算機更好的進行計算工作。

前綴表達式又稱爲“波蘭式”(發明者是一位波蘭科學家)其特徵就是沒有括號,運算符都在數字前面,例如:* 3 + 1 2

後綴表達式的特徵也是沒有括號,但運算符都在數字後面,例如:3 * 2 1 +

使用棧實現計算機的一種方法就是狄傑斯特拉的雙棧模型,即創建兩個棧,一個用來存在數字,另外一個用於存儲各種運算符號。具體的解決過程網絡上有很多啦,請讀者自己查閱。

棧與隊列的內容先到這兒,後期若有新的思路會進一步進行補充,以上的解法有很多可以改進的地方,希望各位讀者給出寶貴的意見,共同進步,一起發展!

最後還要祝各位讀者豬年大吉,幸福安康:>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章