什麼是棧?
其實棧就是一種後進先出的結構,就像一沓書本,每次取書本只能從最上面取,那麼壓在最底下的書本肯定是最先擺放在桌面上的,自然而然肯定是最後被取走的。
什麼是隊列?
隊列是一種先進先出的結構,隊列在生活中的例子就更多了,火車站排隊,食堂買盒飯的隊伍,其實都可以抽象成一個隊列,每當一個人買完票或者拿到盒飯就會離開隊列,先來到售票或者食堂窗口的人肯定是先買到票或者吃飯的。
數據結構其實充滿了生活的影子,它們都是實際生活中抽象出來供人們更好理解計算機的工具而已。那棧與隊列有什麼用處呢?
先說棧吧,棧可以用來實現圖的搜索算法以及括號匹配,表達式計算等功能,正因爲有棧,纔有現在衆多能夠實現括號匹配功能的開發軟件,網絡頁面搜索等。隊列正因爲有先進先出的功能,可以用來實現操作系統中的消息隊列功能等。
關於棧與隊列的面試題
(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 +
使用棧實現計算機的一種方法就是狄傑斯特拉的雙棧模型,即創建兩個棧,一個用來存在數字,另外一個用於存儲各種運算符號。具體的解決過程網絡上有很多啦,請讀者自己查閱。
棧與隊列的內容先到這兒,後期若有新的思路會進一步進行補充,以上的解法有很多可以改進的地方,希望各位讀者給出寶貴的意見,共同進步,一起發展!
最後還要祝各位讀者豬年大吉,幸福安康:>