我之前有接觸過棧和隊列,當時就覺得很好奇,那是以怎樣的存儲結構存儲數據的呢?撥開重重迷霧,終於學到基礎知識了。
學習《棧和隊列》有兩個星期了,有了前面兩個章節的思維基礎,我覺得棧和隊列學習起來還是很好理解的,通過一些實際應用例子,讓我有了更進一步的理解。現在我梳理一下知識,下面總結這一章我所學習到的東西。
一、棧(后進先出:LIFO)
1、順序棧
這是順序棧的存儲結構:
typedef struct { int *base;//棧底指針 int *top; //棧頂指針 int size; //棧容量 }SqStack;
開始棧底、棧頂指針都為空,之后隨着元素的插入,還有棧的特性(只能在棧頂插入刪除),base始終指向棧底位置,而top則加1,指向棧頂元素的上一個。對於順序棧的基本操作,我覺得跟前面順序表差不多,只需要在棧頂操作,好像更簡單些。
我無意中發現自己現在更喜歡鏈式存儲結構,可能是以前覺得鏈式比順序復雜,而學習之后覺得鏈式更有趣,它的抽象邏輯結構如果捋清楚,就使我豁然開朗,也是這也是一種樂趣吧。
2、鏈棧
這張圖很好表示了鏈棧的存儲結構,老師分析,鏈棧沒必要設頭結點,因為在這里對於鏈棧插入刪除,只需要在棧頂操作最方便,初始化時直接將頭指針置空就行。
//---------鏈棧存儲結構--定義 typedef struct stacknode { int data; //數據域 struct stacknode *next; // 指針域 }stacknode,*linkstack; 1、初始化 void initstack(linkstack &S) { S=NULL;//構造空棧S.棧頂指針置空 } 2、入棧 void push(linkstack &S,int e) { stacknode p; //定義變量 p=new stacknode;//申請變量空間,生成新結點 p->data=e; //將數據給新結點 p->next=S; //新結點插入棧頂 S=p; //修改棧頂指針為p } 3、出棧 void pop(linkstack &S,int &e) //刪除棧頂元素 { if(S==NULL) //判斷是否棧空 e=S->data; //棧頂元素數據賦給e p=S; //用p保存棧頂空間,以備釋放 S=S->next; // 修改棧頂指針 delete p; //釋放原棧頂空間 } 4、取棧頂元素 int gettop(linkstack &S) { if(S!=NULL) return S->data; }
以上這些代碼只是各自的算法,我在打的時候也在思考這一句代碼的作用是什么,老師也強調注釋的重要性,在這個過程中有時就是找出Bug的關鍵一步,我在自己做題目的時候或者跟同學討論時確實感受到注釋是檢查程序的一個工具。
我認為我們老師說的很有道理,一個程序的編寫,首先要考慮它的邏輯結構,這也是數據結構這門課的關鍵,其次是算法,最后是存儲結構。
比如,我在做PTA上面括號匹配題目時,開始就明確要用線性結構棧,算法我是參考一下課本案例里面,然后用鏈式存儲結構。
二、隊列(先進先出:FIFO)
1、順序隊列(循環隊列)
這種循環隊列是為了解決“假溢出”,這是由隊列特性:隊尾入隊,隊頭出隊限制造成的,實現操作一個巧妙方法就是(Q.rear+1)%MAXSIZE==Q.front.
2、鏈隊
《銀行業務隊列簡單模擬》那道題,讓我對鏈隊有了更深的理解。
設某銀行有A、B兩個業務窗口,且處理業務的速度不一樣,其中A窗口處理速度是B窗口的2倍 —— 即當A窗口每處理完2個顧客時,B窗口處理完1個顧客。給定到達銀行的顧客序列,請按業務完成的順序輸出顧客序列。假定不考慮顧客先后到達的時間間隔,並且當不同窗口同時處理完2個顧客時,A窗口顧客優先輸出。
輸入格式:
輸入為一行正整數,其中第1個數字N(≤1000)為顧客總數,后面跟着N位顧客的編號。編號為奇數的顧客需要到A窗口辦理業務,為偶數的顧客則去B窗口。數字間以空格分隔。
輸出格式:
按業務處理完成的順序輸出顧客的編號。數字間以空格分隔,但最后一個編號后不能有多余的空格。
輸入樣例:
8 2 1 3 9 4 11 13 15
輸出樣例:
1 3 2 9 11 4 13 15
#include<iostream> using namespace std; typedef struct qnode //隊列鏈式存儲結構 { int data; struct qnode *next; } qnode,*queue; typedef struct { queue front;//隊頭和隊尾指針 queue rear; } lqueue; void initqueue(lqueue &q) { q.front=q.rear=new qnode;//生成新結點作為頭結點,隊頭和隊尾指向頭結點 q.front->next=NULL;//頭結點指針域為空 } void enqueue(lqueue &q,int e) { qnode *p;//定義結構體指針,並申請變量空間 p=new qnode; p->data=e;//把e存入新結點數據域 p->next=NULL;q.rear->next=p;//尾指針指針域指向新結點,即插入隊尾 q.rear=p;//將p修改隊尾指針 } void dequeue(lqueue &q) { qnode *p; p=q.front->next;//指向隊頭 cout<<p->data;//出隊 q.front->next=p->next; //將頭結點指針域指向下一個元素 if(q.rear==p) q.rear=q.front;//最后一個元素出隊,將隊尾指針指向頭結點 delete p;//釋放p的空間 } int main() { lqueue P,Q; int D; initqueue(P);initqueue(Q);//初始化兩個鏈隊 int i,j,k,m=0,n=0; cin>>j; for(i=0;i<j;i++) { cin>>D; if(D%2)//奇數時入隊P { enqueue(P,D); m++;//記錄奇數個數 } if(D%2==0)//偶數時入隊Q { enqueue(Q,D);n++;//記錄偶數個數 } } k=(m/2)>=n?m/2:n;//確定循環輸出次數 for(i=0;i<k;i++) { if(m>1)//共有三種情況輸出 { if(n>0)//P出隊2個,Q出隊1個 { if(i!=0)cout<<" ";//第一次打印元素,前面不帶空格 dequeue(P);cout<<" ";dequeue(P);cout<<" ";dequeue(Q);n--;m-=2; } if(n<=0)//P出隊2個 { if(i!=0)cout<<" "; dequeue(P);cout<<" ";dequeue(P);m-=2; } } else if(m==1) { if(n>0)//P出隊1個,Q出隊1個 { if(i!=0)cout<<" "; dequeue(P);m--;cout<<" ";dequeue(Q);n--; } if(n<=0)//P出隊1個 { if(i!=0)cout<<" "; dequeue(P);m--;n--; } } else if(m==0&&n>0)//Q出隊 { if(i!=0)cout<<" "; dequeue(Q);n--; } } return 0; }
這就是我通過許多次debug之后的程序,在處理隊列問題時候,我考慮了所有可能出現的情況,程序結構看起來很清晰,我想還有不足之處,可以優化。
在解決這道題目時候,在無數次修正錯誤,我從中可以明白許多問題,所以多打代碼,多嘗試、實踐,學得會更明白。
上一周也學習到很多,接下來還是要堅持做題目,實踐與知識結合,我想這樣更加牢固,自己容易犯錯誤的點也不少,希望在以后學習中能夠嚴謹一些。