數據結構與算法(二)--棧與隊列
棧和隊列
棧和隊列都是比較常用的數據結構。棧的應用非常的廣泛,比如說,遞歸函數的實現就是借助於棧保存相關的數據。操作系統中每個線程也會使用棧來保存函數調用涉及到的一些參數和其他變量等。棧最大的一個特點就是先進後出(FILO—First-In/Last-Out)。
隊列和棧不同的是,隊列是一種先進先出(FIFO—first in first out)的數據結構。
對應的STL中都有實現了的方法。
棧的相關方法:
入棧,s.push(x) 出棧,s.pop() 訪問棧頂,s.top() 判斷棧空,s.empty() 訪問棧中的元素個數,s.size() |
隊列的方法與棧大同小異,列舉如下:
入隊,q.push(x) 出隊,q.pop() 訪問隊首元素,q.front()、訪問隊尾元素,q.back() 判斷隊列空,q.empty() 訪問隊列中的元素個數,q.size() |
註意,兩者的pop()方法,僅僅刪除棧頂和隊列首元素,並不返回,如需截獲元素,在pop()方法之前使用top()或者front()方法。
棧的主要機制可以用數組來實現,也可以用鏈表來實現,下面用數組來實現棧的基本操作:
1 public class ArrayStack { 2 private long[] a; 3 private int size; //棧數組的大小 4 private int top; //棧頂 5 6 public ArrayStack(int maxSize) { 7 this.size = maxSize; 8 this.a = new long[size]; 9 this.top = -1; //表示空棧 10 } 11 12 public void push(long value) {//入棧 13 if(isFull()) { 14 System.out.println("棧已滿!");15 return; 16 } 17 a[++top] = value; 18 } 19 20 public long peek() {//返回棧頂內容,但不刪除 21 if(isEmpty()) { 22 System.out.println("棧中沒有數據"); 23 return 0; 24 } 25 return a[top]; 26 } 27 28 public long pop() { //彈出棧頂內容,刪除 29 if(isEmpty()) { 30 System.out.println("棧中沒有數據!"); 31 return 0; 32 } 33 return a[top--]; 34 } 35 36 public int size() { 37 return top + 1; 38 } 39 40 public boolean isEmpty() { 41 return (top == -1); 42 } 43 44 public boolean isFull() { 45 return (top == size -1); 46 } 47 48 public void display() { 49 for(int i = top; i >= 0; i--) { 50 System.out.print(a[i] + " "); 51 } 52 System.out.println(""); 53 } 54 }
數據項入棧和出棧的時間復雜度均為O(1)。這也就是說,棧操作所消耗的時間不依賴於棧中數據項的個數,因此操作時間很短。棧不需要比較和移動操作。
隊列也可以用數組來實現,不過這裏有個問題,當數組下標滿了後就不能再添加了,但是數組前面由於已經刪除隊列頭的數據了,導致空。所以隊列我們可以用循環數組來實現,見下面的代碼:
1 public class RoundQueue { 2 private long[] a; 3 private int size; //數組大小 4 private int nItems; //實際存儲數量 5 private int front; //頭 6 private int rear; //尾 7 8 public RoundQueue(int maxSize) { 9 this.size = maxSize; 10 a = new long[size]; 11 front = 0; 12 rear = -1; 13 nItems = 0; 14 } 15 16 public void insert(long value) { 17 if(isFull()){ 18 System.out.println("隊列已滿"); 19 return; 20 } 21 rear = ++rear % size; 22 a[rear] = value; //尾指針滿了就循環到0處,這句相當於下面註釋內容 23 nItems++; 24 /* if(rear == size-1){ 25 rear = -1; 26 } 27 a[++rear] = value; 28 */ 29 } 30 31 public long remove() { 32 if(isEmpty()) { 33 System.out.println("隊列為空!"); 34 return 0; 35 } 36 nItems--; 37 front = front % size; 38 return a[front++]; 39 } 40 41 public void display() { 42 if(isEmpty()) { 43 System.out.println("隊列為空!"); 44 return; 45 } 46 int item = front; 47 for(int i = 0; i < nItems; i++) { 48 System.out.print(a[item++ % size] + " "); 49 } 50 System.out.println(""); 51 } 52 53 public long peek() { 54 if(isEmpty()) { 55 System.out.println("隊列為空!"); 56 return 0; 57 } 58 return a[front]; 59 } 60 61 public boolean isFull() { 62 return (nItems == size); 63 } 64 65 public boolean isEmpty() { 66 return (nItems == 0); 67 } 68 69 public int size() { 70 return nItems; 71 } 72 }
和棧一樣,隊列中插入數據項和刪除數據項的時間復雜度均為O(1)。
還有個優先級隊列,優先級隊列是比棧和隊列更專用的數據結構。優先級隊列與上面普通的隊列相比,主要區別在於隊列中的元素是有序的,關鍵字最小(或者最大)的數據項總在隊頭。數據項插入的時候會按照順序插入到合適的位置以確保隊列的順序。優先級隊列的內部實現可以用數組或者一種特別的樹——堆來實現
1 public class PriorityQueue { 2 private long[] a; 3 private int size; 4 private int nItems;//元素個數 5 6 public PriorityQueue(int maxSize) { 7 size = maxSize; 8 nItems = 0; 9 a = new long[size]; 10 } 11 12 public void insert(long value) { 13 if(isFull()){ 14 System.out.println("隊列已滿!"); 15 return; 16 } 17 int j; 18 if(nItems == 0) { //空隊列直接添加 19 a[nItems++] = value; 20 } 21 else{//將數組中的數字依照下標按照從大到小排列 22 for(j = nItems-1; j >= 0; j--) { 23 if(value > a[j]){ 24 a[j+1] = a[j]; 25 } 26 else { 27 break; 28 } 29 } 30 a[j+1] = value; 31 nItems++; 32 } 33 } 34 35 public long remove() { 36 if(isEmpty()){ 37 System.out.println("隊列為空!"); 38 return 0; 39 } 40 return a[--nItems]; 41 } 42 43 public long peekMin() { 44 return a[nItems-1]; 45 } 46 47 public boolean isFull() { 48 return (nItems == size); 49 } 50 51 public boolean isEmpty() { 52 return (nItems == 0); 53 } 54 55 public int size() { 56 return nItems; 57 } 58 59 public void display() { 60 for(int i = nItems-1; i >= 0; i--) { 61 System.out.print(a[i] + " "); 62 } 63 System.out.println(" "); 64 } 65 }
這裏實現的優先級隊列中,插入操作需要O(N)的時間,而刪除操作則需要O(1)的時間。
面試題1:
用兩個棧實現隊列,請實現兩個函數appendTail和deleteHead,完成在隊列尾部插入結點和在隊列首部刪除結點的功能。模板定義如下:
1 template <typename T> class CQueue
2 {
3 public:
4 CQueue(void);
5 ~CQueue(void);
6
7 // 在隊列末尾添加一個結點
8 void appendTail(const T& node);
9
10 // 刪除隊列的頭結點
11 T deleteHead();
12
13 private:
14 stack<T> stack1;
15 stack<T> stack2;
16 };
分析:
這道題是要求通過兩個“先進後出”的操作完成“先進先出”的功能。下面這個例子比較形象的給出了實現的過程。
起初的時候,兩個棧都為空,那麽只要有元素來,那麽默認插入到第一個棧。這是,如果要求刪除一個元素,那麽元素已經不在棧頂,在第一個棧中肯定無法直接刪除了,此時我們發現第二個棧還沒有派上用場,這裏用到了,把第一個棧中的元素壓入到第二個棧中,可以發現原來在第一個棧中棧底的元素已經出現在第二個棧的棧頂上,所以刪除的功能就實現了。如果這個時候,“隊列”裏還有元素,我們還可以繼續出隊,而且,現在要出隊的元素就在第二個棧的棧頂,所以直接出棧即可。
分析到現在,下面給出總結:如果棧2不為空,同時又需要出隊,那麽順其自然直接彈出即可。如果棧2為空,那麽從棧1中逐個彈出壓入,那麽完整的實現了先進先出的功能。
具體的流程和代碼實現如下:
1 template<typename T> void CQueue<T>::appendTail(const T& element)
2 {
3 stack1.push(element);
4 }
5
6 template<typename T> T CQueue<T>::deleteHead()
7 {
8 if(stack2.size()<= 0)
9 {
10 while(stack1.size()>0)
11 {
12 T& data = stack1.top();
13 stack1.pop();
14 stack2.push(data);
15 }
16 }
17
18 if(stack2.size() == 0)
19 throw new exception("queue is empty");
20
21 T head = stack2.top();
22 stack2.pop();
23
24 return head;
25 }
面試題2:
用兩個隊列實現棧。
分析:
結合下圖,我們分析一下具體的流程,搞清楚了相關的流程,那麽對應的操作就明晰了。
起初的時候,兩個隊列都是空的,那麽當“棧”要壓入一個元素,我們就默認將該元素壓入到隊列1中。接下來繼續壓入剩余的元素。
接下來考慮,如果我們想要彈出一個元素如何操作。棧中要求出棧的為棧頂元素,那麽即為最後插入的元素,但是該元素在隊列的尾部,必須要求前面的元素出隊後才能訪問,說到這裏,你也就發現思路的:出隊前面的元素,到另一個隊列中,那麽就可以在原隊列中彈出唯一的元素了。
現在我們再考慮另一個情況,隊列裏面還有元素,“棧”又進來了新的元素,那麽就將新元素,壓入到存在元素的那一個隊列中,剩余的操作,上面已經提到了,一樣的操作,看圖也許就清晰多了。
1 template <typename T> class CStack
2 {
3 public:
4 CStack(void);
5 ~CStack(void);
6
7 // 在隊列末尾添加一個結點
8 void Input(const T& );
9
10 // 刪除隊列的頭結點
11 T Output();
12
13 private:
14 queue<T> queue1;
15 queue<T> queue2;
16 };
1 template<typename T> void CStack<T>::Input(const T& t){
2 if(queue1.empty()&&queue2.empty())
3 queue1.push(t);
4 else
5 if(!queue1.empty())
6 queue1.push(t);
7 else
8 queue2.push(t);
9 }
10
11 template<typename T> T CStack<T>::Output(){
12 T t;
13 if(queue1.empty()&&queue2.empty())
14 retutn NULL;
15 else
16 if(queue1.empty()){
17 while(queue2.size()!=1){
18 queue1.push(queue2.front());
19 queue2.pop();
20 }
21 t=queue2.front();
22 queue2.pop();
23 }
24 else
25 {
26 while(queue1.size()!=1){
27 queue2.push(queue1.front());
28 queue1.pop();
29 }
30 t=queue1.front();
31 queue1.pop();
32 }
33 return t;
34 }
35
數據結構與算法(二)--棧與隊列