這個作業屬於哪個班級 | C語言--網絡2011/2012 |
---|---|
這個作業的地址 | DS博客作業02--棧和隊列 |
這個作業的目標 | 學習棧和隊列的結構設計及運算操作 |
姓名 | 駱錕宏 |
0.PTA得分截圖
1.本周學習總結(0-5分)
知識體系略圖:
1.1 棧
the photo of stack is followed:
*
1.1.1 關於棧的初步介紹
- 棧在C++的STL庫中有專門的容器,僅需要
#include<stack>
就可以使用,不過值得一提的是,C++中的
stack容器的底層結構是鏈棧。 -
中的常用成員函數有如下幾種: - top():返回棧頂的元素;
- push():對某一數據執行進棧操作;
- pop():講棧頂元素出棧;
- empty():判斷當前棧是否為空,如果為空返回true;
- size():返回當前棧中所含有的數據的數目;
- 棧這種數據結構具有兩個特性:
<先入后出> <隨進隨出>- 所以基於棧的這兩個特性,我們可以得到一些基本性質:對於給定輸入順序的一組數據
我們可以根據他的第一個輸出的元素來判斷當前棧中有那些元素,以及可以判斷出將來可能的
棧的輸出組合。
- 所以基於棧的這兩個特性,我們可以得到一些基本性質:對於給定輸入順序的一組數據
- 棧是線性結構,所以他同樣擁有兩種存儲結構,一種是順序存儲結構,一種是鏈式存儲結構;
基於這兩種存儲結構也就會引申出我們下面要講的兩種棧的類型:順序棧和鏈棧。 - 棧適用於解決遞歸問題,適用於解決遞歸問題,函數遞歸調用的實現,
本身數據結構的基礎就是遞歸棧。
1.1.2 順序棧的結構及其操作函數
- 順序棧的結構定義:
typedef int ElementType;
typedef int Position;
struct SNode
{
ElementType* Data;
Position Top;
int MaxSize;
};
typedef struct SNode* SQStack;
- 順序棧初始化棧的代碼:
- 當然這里也有另類堆棧的辦法,就是令
top = 0
;
這會相應地影響到棧的其他基礎操作的具體實現。
- 當然這里也有另類堆棧的辦法,就是令
void CreateSQStack(SQStack& S, int MaxSize)
{
S = new struct SNode;
S->Data = new ElementType;
S->Top = -1;
S->MaxSize = MaxSize;
}
- 順序棧的進棧操作的代碼:
- 如果是另類棧的話,棧滿的判斷條件就要變成
S->Top == S->MaxSize
; - 同時入棧的操作會變成
S->Data[S->Top++] = X;
;
- 如果是另類棧的話,棧滿的判斷條件就要變成
bool Push(SQStack S, ElementType X)//順序堆棧的入棧操作
{
//入棧操作要先判斷是否棧滿
if (S->Top == S->MaxSize-1)//棧滿了
{
printf("SQStack Full\n");
return false;
}
//正常入棧
S->Data[++S->Top] = X;
return true;
}
- 順序棧的出棧操作(出棧包含了判斷棧是否為空的代碼)的代碼:
- 如果是另類建棧的話,那判斷棧是否為空的條件就會變成
S->Top == 0
; - 同樣的出棧的操作也就會變成
return S->Data[--S->Top]
; - 以及還有一點是,雖然是出棧,但是如果沒有進行后續入棧操作對現出棧位置進行數據覆蓋的話,其實出
棧的元素只是邏輯上被刪除了,但是實際上並沒有被刪除,准確給出下標地址的話,依然可用被輸出。
- 如果是另類建棧的話,那判斷棧是否為空的條件就會變成
ElementType Pop(SQStack S)//順序棧的出棧操作
{
/*先判斷棧是否為空*/
if (S->Top == -1)
{
printf("SQStack Empty\n");
return ERROR;
}
//正常出棧
return S->Data[S->Top--];
}
-
關於獲取當前棧的元素個數:
- 如果是正常建棧的話,那就是
return S->Top+1
; - 如果是另類建棧的話,那就是
return S->Top
;
- 如果是正常建棧的話,那就是
-
准確來說還有一個銷毀棧的操作,對於順序棧而言,一般直接delete棧就行。
1.1.3 順序棧中的共享棧結構及其操作函數
- 共享棧的結構定義:
typedef int ElementType;
typedef int Position;
struct SNode {
ElementType* Data;
Position Top1, Top2;
int MaxSize;
};
typedef struct SNode* SharedStack;
- 共享棧的創建:
void CreateSharedStack(SharedStack& S,int MaxSize)//創建共享棧
{
S = new struct SNode;
S->Data = new ElementType;
S->Top1 = -1;
S->Top2 = MaxSize;
}
- 共享棧的入棧操作:
- 要特別注意的是另外一個方向的棧,這個比較反常思維的棧的操作要尤其注意。
bool Push(SharedStack S, ElementType X, int Tag)//共享棧的進棧操作
{
/*進棧要考慮棧是否滿*/
if (S->Top1 + 1 == S->Top2)
{
printf("SharedStack Full\n");
return false;
}
if (Tag == 1)
{
S->Data[++S->Top1] = X;
return true;
}
else
{
S->Data[--S->Top2] = X;
return true;
}
}
- 共享棧的出棧操作:
- 仍然要注意的是,共享站另外一邊的棧思維有點反常態所以要特別注意。
- 其次是出棧的時候我們需要判斷棧是否為空,而共享站兩邊的棧棧空條件是不一樣的,所以要分開來判斷。
- 以及還有一點是,雖然是出棧,但是如果沒有進行后續入棧操作對現出棧位置進行數據覆蓋的話,其實出
棧的元素只是邏輯上被刪除了,但是實際上並沒有被刪除,准確給出下標地址的話,依然可用被輸出。
ElementType Pop(SharedStack S, int Tag)//共享棧的出棧操作
{
/*共享棧要分開考慮棧是否為空的情況*/
if (Tag == 1)
{
/*棧空判斷*/
if (S->Top1 == -1)
{
printf("SharedStack %d Empty\n", Tag);
return ERROR;
}
return S->Data[S->Top1--];
}
else
{
/*棧空判斷*/
if (S->Top2 == S->MaxSize)
{
printf("SharedStack %d Empty\n", Tag);
return ERROR;
}
return S->Data[S->Top2++];
}
}
-
共享棧的取棧頂操作:
- 對左棧來說,取棧頂元素就是
S->Data[S->Top1]
; - 對右棧來說,取棧頂元素就是
S->Data[S->Top2]
;
- 對左棧來說,取棧頂元素就是
-
准確來說還有一個銷毀棧的操作,對於順序棧而言,一般直接delete棧就行。
1.1.4 鏈棧的結構及其操作函數
-
鏈棧的結構基礎其實仍然是鏈表,出於棧只能在一端進行入棧和出棧的操作的考慮,所以一般選擇
鏈表的第一個結點處為棧頂,而取鏈表為棧底,又由於棧的入棧和出棧操作本質上就是鏈表的插入和
刪除操作,所以保留表頭方便再頭處(也就是棧頂)進行入棧和出棧操作,總之,可以把鏈棧看成一
個帶有頭指針的只在頭處進行插入刪除操作的鏈表來使用就行。 -
鏈棧的結構基本定義:(本質上還是鏈表的定義)
typedef int Elmetype;
struct SNode
{
Elmetype data;
struct SNode* next;
};
typedef struct SNode* LinkStack;
- 鏈棧的初始化函數:
- 此處添加了一個數據載入的過程,可以讓鏈棧的初始化擁有初始數據而非空。
void CreateLinkStack(LinkStack& S,int n)
{
S = new SNode;
S->next = NULL;
for (int i = 0; i < n; i++)
{
LinkStack NewS = new SNode;
/*數據載入*/
cin >> NewS->data;
/*頭插法插入新數據*/
NewS->next = S->next;
S->next = NewS;
}
}
- 鏈棧的進棧操作:
- 值得注意的一點是,鏈棧的進棧不需要考慮棧是否棧滿的問題。
void Push(LinkStack& S, Elmetype X)
{
LinkStack NewS = new SNode;
/*數據載入*/
NewS->data = X;
/*頭插法插入新數據*/
NewS->next = S->next;
S->next = NewS;
}
- 鏈棧的出棧操作:
- 出棧前一定要判斷棧是否為空,如果棧空則無法出棧;
- 相對於數組棧出棧時並非真的將源數據刪除,鏈棧的刪除是的的確確的把鏈棧中
出棧的結點,在物理意義上完全刪除了;
bool Pop(LinkStack& S, Elmetype& e)
{
/*首先要判斷棧頂是否為空*/
if (S->next == NULL)
{
return false;
}
e = S->next->data;
LinkStack delSNode = S->next;
S->next = delSNode->next;
delete delSNode;
return true;
}
-
鏈棧的取棧頂元素操作:
- 本質上來說就是取
S->next->Data
;
- 本質上來說就是取
-
鏈棧的銷毀操作:
- 本質上和鏈表的銷毀是一樣的,需要一個結點一個結點地銷毀。
void DestoryLinkStack(LinkStack& S)
{
LinkStack delS = S;
while (S != NULL)
{
S = S->next;
delete delS;
delS = S;
}
}
1.2 棧的應用
- 據目前有學習過的部分來說,棧有這幾個方面的應用:
- 一個是表達式轉換(常用的是將中綴表達式轉化為后綴表達式)
- 一個是表達式求值 (本質上仍然是先將中綴表達式轉化為后綴表達式,后綴表達式的求值就很簡單了)
- 還有一個是符號的配對,詳細的思路和代碼可以參考下文部分2的例題
- 當然棧還可以實現將十進制數轉化成n進制數。(來自課堂派測試題)
- 借用棧結構可以實現深度搜索(迷宮問題)
1.2.1 表達式問題
- 表達式轉換(將中綴表達式轉換為后綴表達式)
- 先規定一下前提:目前給出的表達式每個操作數都是一位整數,
且需要考慮的運算符號只有'+' '-' '*' '/' '(' ')'
這六個符號。 - 暫時不考慮操作數帶有 小數,帶有 負數,帶有多位整數這類情況的表達式轉換。
- 先規定一下前提:目前給出的表達式每個操作數都是一位整數,
- 解題思路:
- 遍歷一個給定的正確的表達式
- 當遇到數字的時候,我們讓數字直接輸出,或者直接存儲到一個字符串中之類的;
- 當棧空或者當前要進棧的運算符號比棧頂的運算符號優先級更大的時候,就讓運算符進入運算符棧;
- 當遇到當前要進棧的運算符號比棧頂的運算符號優先級更小或者相等的時候,就讓棧中的運算符出棧直到出到;
棧中的運算符比當前要入棧的運算符優先級更大或者棧空的時候,再讓現在的運算符入棧; - 當表達式遍歷結束而棧中還有符號的時候,就讓棧中所有的運算符輸出直到棧空;
- 對'('和')'的處理,當當下要進棧的時候,'('直接進棧,視為優先級最大,而當其處於棧頂時,
優先級將降為最低,任何運算符遇到它都入棧,直到當前要入棧的運算符時是')'時,再把'('上的所有
運算符全部輸出即可。
1.3 隊列
the photo of queue is followed:
1.3.1 隊列的一些基礎要素:
- 隊列是只允許在一端進行插入(俗稱隊尾),然后在另一端進行刪除(俗稱隊尾)的線性表,隊列具有先入先出的特性。
- C++中也帶有隊列的已經實現好了的類,只需要加上
#include<queue>
便可以使用了。 - 下面是常用到的一些內聯函數的介紹:
- front():返回 queue 中第一個元素的引用。如果 queue 是常量,就返回一個常引用;如果 queue 為空,返回值是未定義的。
- back():返回 queue 中最后一個元素的引用。如果 queue 是常量,就返回一個常引用;如果 queue 為空,返回值是未定義的。
- pop():刪除 queue 中的第一個元素。
- push():在 queue 的尾部添加一個元素的副本。
- size():返回 queue 中元素的個數。
- empty():如果 queue 中沒有元素的話,返回 true。
- 當然作為線性表,隊列同樣有兩種存儲結構一種是順序存儲結構,另外一種是鏈式存儲結構,而這兩種就分別對應了兩種
隊列類型的具體實現,也就是下面要講到的順序隊列和鏈隊列。
1.3.2 順序隊列的結構及其操作函數:
- 順序隊列的結構基礎是數組,由此我們給出順序隊列的結構定義:
typedef int ElemType;
#define MaxSize 100// 100只是一個例子具體情況視題目而定。
typedef struct
{
ElemType data[MaxSize];
int front, rear; //隊首和隊尾指針
}Queue;
typedef Queue* SqQueue;
- 當有了一個結構后,首先我們考慮對它進行初始化:
void CreatSqQueue(SqQueue& Que)
{
Que = new Queue;
Que->front = Que->rear = 0;
//初始化的狀態是隊空的狀態
//一般我們默認讓front代表隊頭的位置,讓rear代表隊尾的下一個位置
}
- 初始化后考慮對空隊列加入元素,也就是入隊:
bool EnQueue(SqQueue& Q, Elmetype e)//加入隊列
{
/*要先判斷是否隊滿*/
if (Q->rear == Maxsize)
{
return false;
}
Q->data[Q->rear++] = e;
return true;
}
- 同樣的,有入隊也就會有出隊,不過出隊前要先判斷隊列是否為空,空隊莫得出隊:
bool QueueEmpty(SqQueue& Q)//隊列是否為空
{
if (Q->front == Q->rear)
{
return true;
}
return false;
}
- 有了判斷隊列是否為空的函數就可以直接開始操作出隊啦:
bool DeQueue(SqQueue& Q, Elmetype& e)//出隊列
{
if (QueueEmpty(Q))
{
return false;
}
e = Q->data[Q->front++];
return true;
}
-
有始有終,使用完了后,得將隊列銷毀,此處由於結構比較簡單可以直接
delete
。 -
在這里不妨思考一個問題,剛才順序隊列中,當我們出隊的時候,當下隊頭前的空間實際是可以使用的
但是當前的數據結構卻不允許我們使用前面的空間,試想當數據量十分龐大的時候,使用這樣的數據結構豈
不是會浪費很大的空間?那有沒有什么辦法能夠利用到前面的的空間呢? -
———把數組首尾連接起來,變成一個環形,你會發現就能解決這樣的問題,於是新的結構營運而生,它便是環形隊列!
1.3.3 環形隊列的結構及其操作函數:
- 先來看看環形隊列長什么樣子趴:
typedef int ElementType;
typedef int Position;
struct QNode {
ElementType* Data; /* 存儲元素的數組 */
Position Front; /* 隊列的頭指針 */
Position Rear; /* 隊列的尾指針 */
int MaxSize; /* 隊列最大容量 */
};
typedef struct QNode* RingQueue;
- 同樣的,知道它的結構之后,我們就要對其初始化:
- 從初始化的狀態我們可以知道,
Q->Front = Q->Rear = 0;
這便是循環隊列為空的條件。 - 而且我們還可以知道一般來說,我們默認讓front代表隊頭的位置,讓rear代表隊尾的下一個位置
- 從初始化的狀態我們可以知道,
void CreateRingQueue(RingQueue& Q,int MaxSize)
{
Q = new struct QNode;
Q->Data = new ElementType[MaxSize];
Q->Front = Q->Rear = 0;
Q->MaxSize = MaxSize;
}
- 同理可以給出環形隊列的入隊函數:
bool EnQueue(RingQueue& Q, ElementType X)
{
if ((Q->Rear+1)%Q->MaxSize == Q->Front)//Queue Full
{
return false;
}
Q->Data[Q->Rear] = X;
Q->Rear = (Q->Rear + 1) % Q->MaxSize;
return true;
}
- 同理可以給出環形隊列的出隊函數:
bool DeQueue(RingQueue& Q, ElementType e)
{
if (Q->Front == Q->Rear)//Queue Empty
{
return false;
}
e = Q->Data[Q->Front];
Q->Front = (Q->Front + 1) % Q->MaxSize;
return true;
}
- 判斷隊空的函數隱藏在出隊函數中,而此處的循環隊列的銷毀同順序隊列直接
delete
。
1.3.4 鏈隊列的結構及其操作函數:
- 先來看看鏈隊列的結構定義:
- 要特別注意的一點是,鏈隊列的隊頭指針和隊尾指針是一個整體,
所以應當用一個結構體將他們包含起來。
- 要特別注意的一點是,鏈隊列的隊頭指針和隊尾指針是一個整體,
typedef int Elmenttype;
typedef Queue* PtrtoQueue;
typedef struct
{
Elmenttype data;
PtrtoQueue next;
}Queue;
typedef struct
{
PtrtoQueue Front;
PtrtoQueue Rear;
}LinkQueue;
- 鏈隊列的初始化函數:
void CreatLinkQueue(LinkQueue& LQ)
{
LQ.Rear = LQ.Front = new Queue;
LQ.Front->next = NULL;
}
- 鏈隊列的入隊函數:
- 鏈隊列不需要判斷棧滿的情況。
bool EnQueue(LinkQueue& LQ, Elmenttype X)
{
PtrtoQueue NewNode = new Queue;
NewNode->data = X;
NewNode->next = LQ.Rear->next;
LQ.Rear->next = NewNode;
LQ.Rear = NewNode;
}
- 鏈隊列的判空的函數:
bool Empty(LinkQueue LQ)
{
if (LQ.Front == LQ.Rear)
{
return true;
}
return false;
}
- 鏈隊列出隊的函數:
bool DeQueue(LinkQueue& LQ, Elmenttype& X)
{
if (!Empty(LQ))
{
X = LQ.Front->next->data;
PtrtoQueue DelNode;
DelNode = LQ.Front->next;
LQ.Front->next = DelNode->next;
delete DelNode;
return true;
}
return false;
}
- 其實對鏈隊列的操作本質上也是對單向鏈表的操作,只不過把重點集中在,尾插法和頭刪法上了而已。
1.3.5 隊列的具體應用(有代碼要求):
1.3.5.1那么不妨先思考一下,隊列能做些什么呢?
- 一個比較重要且難的應用就是借用隊列這個結構可以實現廣度搜索,而廣度搜索可以雖然搜索的效率低,但是能有找到最優解。
- 比如對二叉樹結構的層序遍歷,本質上也就是一種廣度優先搜索,借用一個隊列來存儲樹的每個結點,我們先讓樹的
根結點入隊,然后當其出隊后,讓它的兩個孩子入隊,緊接着,又依次讓它的兩個孩子出隊,每次一個孩子出隊后,就讓它的孩
子的孩子入隊,依次往復,就能夠實現一層一層地去遍歷二叉樹了。
- 比如對二叉樹結構的層序遍歷,本質上也就是一種廣度優先搜索,借用一個隊列來存儲樹的每個結點,我們先讓樹的
- 另外講一個基礎一點的應用吧,那就是處理實際問題中,需要對數據進行分隊,然后再進行隊與隊之間操作的實例應用都可以
使用隊列來實現。- 比如以為例舞伴問題來說:
- 比如以為例舞伴問題來說:
- 對這個實際問題,我們其實首先就是對其進行男女的分隊,這對應隊列的操作就是,非空建隊,也就是創建隊列和入隊的函數
的結合。再往后男女依次組成舞伴,其實就是出隊的操作。所以這樣看來的話,無非對隊列應用的考慮要從兩個角度出發:- 其一,就是數據要求滿足先入先出特點的,我們使用隊列。
- 其二,問題的實際應用的需求可以化為為 建隊、入隊和出隊 三要素的邏輯過程的問題。
- 額外說一句,個人認為,其實數據結構就是多種具有某種特性的不同的邏輯結構,而去分析具體問題的編程,本質上也不過是
對具體問題進行邏輯抽象,把具體需求轉化到對應數據結構的各種操作上,然后再去逐步考慮細節實現的方法,由主干到枝葉展
開,逐步完善而已。
1.3.5.2 部分相關代碼:
- 舞伴問題的函數實現代碼:
int QueueLen(SqQueue Q)//隊列長度
{
int len = Q->rear - Q->front;
return len;
}
int EnQueue(SqQueue& Q, Person e)//加入隊列
{
if (Q->rear == MAXQSIZE)
{
return ERROR;
}
Q->data[Q->rear++] = e;
}
int QueueEmpty(SqQueue& Q)//隊列是否為空
{
if (Q->front == Q->rear)
{
return OK;
}
return ERROR;
}
int DeQueue(SqQueue& Q, Person& e)//出隊列
{
if (QueueEmpty(Q))
{
return ERROR;
}
e = Q->data[Q->front++];
}
void DancePartner(Person dancer[], int num)//配對舞伴
{
Person man;
Person woman;
/*先分隊*/
for (int i = 0; i < num; i++)
{
if (dancer[i].sex == 'F')
{
EnQueue(Fdancers, dancer[i]);
}
else
{
EnQueue(Mdancers, dancer[i]);
}
}
/*再出隊配對*/
while (!QueueEmpty(Fdancers) && !QueueEmpty(Mdancers))
{
DeQueue(Fdancers,woman);
DeQueue(Mdancers, man);
cout << woman.name << " " << man.name << endl;
}
}
2.PTA實驗作業(4分)
2.1 符號配對
2.1.1 解題思路及偽代碼
解題思路:
- 首先要理清核心的思路,實現符號配對是運用棧來實現;
- 遍歷輸入數據時,遇到左符號就統一讓它入棧;
- 而遇到右符號首先考慮棧是否為空:
- 如果棧為空,那就直接不匹配,直接輸出缺少左符號;
- 如果棧不為空,那就判斷當前的右符號是否與棧頂左符號匹配:
- 如果匹配,就讓棧頂的元素出棧;
- 如果不匹配,就輸出缺少右符號;
- 當遍歷結束后,再次判斷:
- 如果當下棧空;就代表所有的符號都配對,直接按全配對輸出;
- 如果當下棧不空,就代表不配對,需要輸出缺少右符號;
- 然后再去思考怎么解決該題的數據讀入的問題,本次數據讀入的結束點是按字面來看是一個'.'后還有一個'endl',
不過其實用一行一行讀取的思想去看的話只需要讀取到當當前讀取的一行字符串是 "." 時,表示結束就行。 - 再然后就是關於對"/" "/"這兩個符號的處理,延續分別采用兩個hash表來分別存儲左右符號的思想,由於這兩個
符號是由兩個字符組成,所以就需要用string來存儲,因此本次的hash表用string[]的形式來建表。 - 由於hash表是自己建的,所以得老老實實自己造Find函數。
偽代碼如下:
//input
while(未到最后一行)
{
getline(單行str)
總str += 單行str;
}
//match
用string[]分別建好左右的符號表。
while(遍歷總str中)
{
if(cur_sign為左符號) 執行push;
if (cur_sign為右符號)
{
if(stack不空)
{
if (左右符號匹配) 執行pop;
else 執行輸出缺右符號
}
else 執行輸出缺左符號
}
if(stack empty)-> all matched
else 執行輸出缺右符號
}
//other function
<Find--function>
遍歷string[]找相同的string元素。
2.1.2 總結解題所用的知識點
- 一個沒用成功的知識點:
getline(cin,string,end_char)
,getline函數可以設置一個char常量為輸入的結束點。 - 使用了hash表的知識,用hash表來存儲會用到的符號。
- 使用了STL庫的string類。
- 學會了利用string類的特點對多行元素進行有指定結束標志的讀取。
- 使用了STL庫的stack類,學會使用pop、push、size等內聯函數。
2.2 銀行業務隊列簡單模擬
2.2.1 解題思路及偽代碼
解題思路如下:
- 由於符合先入先出的數據特點,因此選擇隊列作為該題目的數據結構,
又由於本題不考慮復雜的出隊和入隊的問題,因此選擇隊列中的順序隊列。 - 首先要做的是根據已輸入的數字情況的奇偶特性將輸入數據分成兩個隊列。
- 然后再根據不同隊列對應的不同窗口的業務處理特性進行不同的出隊操作。
- 要另外注意輸出格式。
偽代碼如下:
輸入顧客總數,
輸入顧客編號組成的數組,
在輸入數組數據的過程中,對編號進行判斷
if(編號是奇數),向隊列A執行入隊操作。
if(編號是偶數),向隊列B執行入隊操作。
業務處理過程函數()。
以下是業務處理過程函數的偽代碼,本質上是對隊列執行出隊操作
並按格式要求打印出出隊的數據。
首先是對兩個隊列進行同時遍歷
while(隊A不空 && 隊B不空)
{
先出隊一次隊A元素並打印(末尾有空格)
再次判斷if(隊A不空)
再出隊一次隊A元素並打印(末尾有空格)
再再次判斷if(隊A不空)
出隊一次隊B元素並打印(末尾有空格)
再再次判斷if(隊A為空 && 隊列B僅剩一個元素)
出隊一次隊B元素並打印(末尾無空格)
}
//處理一個空一個不空的問題
while(A不為空)
{
若隊列A僅剩一個元素時,出隊一個隊A元素並打印(末尾無空格)
若隊列A不為空時,出隊一個隊A元素並打印(末尾有空格)
}
對隊列B的處理同上。
2.2.2 總結解題所用的知識點
- 學會定義順序隊列並根據實際情況增加所需要的成員變量。
比如本題中需要增加計算成員個數的計數變量。 - 要根據實際情況寫好對隊列進行初始化的函數。該情況下,front代表的是隊頭元素的位置,
而rear則表示隊尾元素的下一個位置。front和rear的起始位置都在0處。 - 要熟練掌握順序隊列的出隊操作。
順序隊列的出隊前要判斷隊列是否為空,空隊列無法出隊,空隊列的判斷條件是
當rear == front。出隊的操作要求先保存出隊元素再讓front往前移動一個單位。
出隊后要記得讓計數變量減一。 - 要熟練掌握順序隊列的入隊操作。
順序隊列的出隊前要判斷隊列是否已滿,滿隊列無法入隊,滿隊列的判斷條件是
當rear == maxsize。入隊的操作要求先容納入隊元素再讓rear往前移動一個單位。
入隊后要記得讓計數變量加一。 - 解決銀行業務問題在本題中,本質上是處理兩個隊列的有格式要求的混合出隊問題。
在代碼框架上類似於合並兩個有序數組的框架,不過內部的實際操作不同,此處以出隊
操作為核心,同時這里還處理了在一層循環中對同一隊列同時進行連續兩次出隊的操作,
該操作要求在第二次操作前判斷該對象是否為空,避免數組越界引發程序崩潰。
3.閱讀代碼(0--1分)
3.1 題目及解題代碼
代碼如下:
class SortedStack {
public:
stack<int> stk, help;
SortedStack() {
}
void push(int val) {
if (stk.empty()) stk.push(val);
else {
while (!stk.empty() && stk.top() < val) { //尋找儲存棧中插入的位置(即,利用輔助棧help保存彈出元素
help.push(stk.top());
stk.pop();
}
stk.push(val); //找到合適位置后進行插入
while (!help.empty()) { //將之前彈出的元素重新導回儲存棧
stk.push(help.top());
help.pop();
}
}
}
void pop() {
if (!stk.empty()) stk.pop();
}
int peek() {
return stk.empty() ? -1 : stk.top();
}
bool isEmpty() {
return stk.empty();
}
};
作者:wen-jian-69
鏈接:https://leetcode-cn.com/problems/sort-of-stacks-lcci/solution/cha-ru-pai-xu-si-xiang-by-wen-jian-69-4mxy/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
3.2 該題的設計思路及偽代碼
以下是原作者的設計思路
- 按照題目要求,就是要我們構造一個從棧底到棧頂的降序序列,因此可以參考插入排序,每次對元素val進行插入時先尋找其在有序序列的插入位置;
- 棧內尋找插入位置通過不斷比較插入元素和棧頂元素的大小來實現;
- 若棧頂元素大於或等於插入元素,則可以直接入棧,若棧頂元素小於插入元素,則彈出棧頂,同時用輔助棧保存彈出的元素,
直到棧頂元素大於或等於插入元素,即可將val入棧,最后在將之前保存的彈出元素導回。
作者:wen-jian-69
鏈接:https://leetcode-cn.com/problems/sort-of-stacks-lcci/solution/cha-ru-pai-xu-si-xiang-by-wen-jian-69-4mxy/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
- 以下是我補充的偽代碼:(嘗試用英文來寫)
insertOrder():
in every Push():
if (dataStack is empty) then push(val) into dataStack;
else
{
while(find the insert location)
{
if (val > topdata) then push(topdata) into helpStack and pop(it);
}
when we find the location by the above circulation
push(val) into dataStack;
and then push all data in helpStack into dataStack by another circulation.
}
* In the end,what need we do is to run this insertOrder function
each time we insert a new data. And we can get a descending order
stack from button to top.
* 由於只使用了一層循環以及只使用了一個輔助棧來解決問題,所以該問題的時間復雜度和空間復雜度都是線性階O(n)。
3.3 分析該題目解題優勢及難點。
- 這道題需要深刻理解棧結構先入后出的特性
- 這道題需要深刻理解插入排序集中於每一次插入先找位置后插入的要求。
- 簡單來說這道題是棧結構與插入排序算法的結合應用。
- 難點在於想到用插入排序來解決只能使用另一個輔助棧來解決排序的限制,插入排序將排序的重點
微分到對每一個新元素查找插入位置的思維很妙。 - 單純從代碼來講,這個代碼依然使用到了三目運算符
exp1 ? exp2 : exp3
說明這個運算符的使用很簡潔,邏輯也很清晰,要學會使用。 - 單純從代碼來講,本題的代碼還有一個思想就是,對於需要在多個函數中使用的變量,比起在接口上頻繁設置參數來不斷傳參,直接設置成全局變量則會方便很多!
4.通過Debug得到的一些感想。
- 對一道題目的分析,我們應該從主干開始,先把題目所要求實現的核心功能的思路先理清楚,
然后再逐步細化去詳細探討數據要如何輸入,可能用到什么函數,以及某些特殊的細節點要怎
么特殊處理等等。 - 對題目進行分析的過程中,尤其是對分支情況進行分析的過程中,一定要格外慎重的注意,
分支的產生,必定包括了邏輯正面和邏輯反面,不能自以為是地只看到其中的一面就以為另
一面可以省略或者不考慮,有些不同分支的考慮,雖然邏輯結果相同,但是由於邏輯時序不同
所導向的結果也會有問題,所以請老老實實考慮每一個分支的正反兩面!- 比如題目《符號配對》中的最后一個測試點,結果和棧不空的處理方式雖然一致,但是如果舍
棄了else分支,就不能讓不配對的情況及時輸出,在循環繼續執行下去的過程中,無法避免來
自后面的元素對棧頂元素的影響,所以當最后循環結束時,難怕棧仍然不空,但是棧頂元素可
能已經不是當初判斷不匹配時的那第一個不匹配的棧頂符號了。所以每個分支的邏輯出口都應該
慎重考慮,慎重處理!
- 比如題目《符號配對》中的最后一個測試點,結果和棧不空的處理方式雖然一致,但是如果舍