資料結構(2)—— 棧與佇列
阿新 • • 發佈:2021-07-12
寫在前面
為了考研,需要複習資料結構。而對於資料結構這門學科來說,寫程式碼是非常必要的。用程式碼把一些常見的資料結構與演算法實現一遍,非常有利於對於資料結構的理解。
關於第一篇:資料結構(1)—— 線性表
這一篇主要是棧與佇列的實現。棧與佇列都是操作受限的線性表,棧只能從一端進出,佇列只能一端出一端進。這兩種資料結構在我們的實際開發中應用的十分多,因此對這部分不管是出於考研還是工作都要掌握。
順序棧
注意點
關於順序棧的實現,要注意的地方其實並不多。最核心的就是top指標的初始化情況了。這裡我把top指標設在了-1,也可以設在0,不過設定為0時判空和進出棧的條件就不大一樣了,需要注意。
程式碼
/* * @Description: 順序棧的實現 * @version: 1.0 * @Author: Liuge * @Date: 2021-07-09 20:25:53 */ #include<bits/stdc++.h> #define maxSize 50 // 順序棧的儲存結構 typedef struct{ int data[maxSize]; // 棧頂指標 int top; }SqStack; // 初始化棧 void initStack(SqStack &S){ // 這裡把棧頂指標設為-1,也可以設為0 S.top = -1; } // 判空 bool isStackEmpty(SqStack S){ return S.top == -1; } // 進棧 bool push(SqStack &S, int e){ // 棧滿 if(S.top == maxSize -1){ return false; } S.data[++S.top] = e; return false; } // 出棧 bool pop(SqStack &S,int &e){ // 棧空 if(S.top == -1){ return false; } e = S.data[S.top--]; return true; } // 讀棧頂元素 bool getTop(SqStack &S,int &e){ if(S.top == -1){ return false; } e = S.data[S.top]; return true; } // 遍歷棧 void printStack(SqStack S){ printf("棧中的元素為:\n"); while(!isStackEmpty(S)){ int topElem; getTop(S,topElem); printf("%d ",topElem); S.top--; } printf("\n"); } // 主函式測試 int main(){ SqStack S; // 建立一個棧 initStack(S); printStack(S); // 放幾個元素試試 push(S,3); push(S,4); push(S,5); // 輸出應該是5 4 3 printStack(S); // 出一個 int popElem; pop(S,popElem); printf("\n出棧的元素為 -> %d\n",popElem); printStack(S); // 讀一個 int topElem; getTop(S,topElem); printf("\n棧頂的元素為 -> %d\n",topElem); return 0; }
迴圈佇列
注意點
由於佇列的特殊性,順序佇列實現的價值並不高。這裡直接實現迴圈佇列。迴圈佇列最核心的地方便在於判斷當前佇列是否為滿,這裡我的程式碼使用的是犧牲一個記憶體單元來區分隊滿和隊空,當然你也可以採用別的方式,如王道書上提到的增設一個size變數,或增加一個tag標記,都可以實現對於隊滿的判斷,這裡不再贅述。
程式碼
/* * @Description: 迴圈佇列的實現——犧牲一個記憶體單元來區分隊滿和隊空 * @version: 1.0 * @Author: Liuge * @Date: 2021-07-10 19:13:15 */ #include<bits/stdc++.h> #define maxSize 50 // 定義迴圈佇列的結構 typedef struct{ int data[maxSize]; // 隊頭指標 int front; // 隊尾指標 int rear; }SqQueue; // 初始化佇列 void initQueue(SqQueue &Q){ // 將隊首和隊尾指標都指向0 Q.rear = Q.front = 0; } // 判隊空 rear==front bool isEmpty(SqQueue Q){ return Q.rear==Q.front; } // 求當前佇列長度 int getQueueLength(SqQueue Q){ return (Q.rear + maxSize - Q.front) % maxSize; } // 入隊 bool enQueue(SqQueue &Q,int x){ // 判隊滿,rear + 1 % maxSize = front if((Q.rear + 1) % maxSize == Q.front){ return false; } Q.data[Q.rear] = x; // 隊尾指標+1取模,起到迴圈效果 Q.rear = (Q.rear + 1) % maxSize; return true; } // 出隊 bool deQueue(SqQueue &Q,int &x){ // 判隊空 if(isEmpty(Q)){ return false; } x = Q.data[Q.front]; // front+1取模 Q.front = (Q.front + 1) % maxSize; return true; } // 列印 // 這裡可以這麼寫,是因為Q裡的是陣列 // 這樣這裡就是一個新的陣列了,不會影響原本的陣列,順序棧那裡同理 void printQueue(SqQueue Q){ printf("佇列中的元素為:\n"); while(!isEmpty(Q)){ int deQueueElem; deQueue(Q,deQueueElem); printf("%d ",deQueueElem); } printf("\n"); } // 主函式測試 int main(){ SqQueue Q; // 初始化佇列 initQueue(Q); printQueue(Q); // 入隊幾個元素 enQueue(Q,3); enQueue(Q,4); enQueue(Q,5); // 列印應該為 3 4 5 printQueue(Q); // 出隊幾個試試 int deElem; deQueue(Q,deElem); printf("出隊元素為 -> %d\n",deElem); printf("當前佇列長度為 -> %d\n",getQueueLength(Q)); printQueue(Q); }
鏈式佇列
注意點
鏈式佇列的實現,需要注意隊頭的設定。這裡我把隊頭設定在了連結串列的頭部,那麼隊尾就是連結串列的尾部了。我實現的是帶有頭結點的鏈式佇列,這樣的比較好實現,讀者也可以自己試試如何實現不帶頭結點的。
程式碼
/* * @Description: 鏈式佇列的實現 * @version: 1.0 * @Author: Liuge * @Date: 2021-07-10 20:45:24 */ #include<bits/stdc++.h> // 定義結點的結構 typedef struct LinkNode{ int data; struct LinkNode *next; }LinkNode; // 定義鏈式佇列的結構 typedef struct{ LinkNode *front; LinkNode *rear; }LinkQueue; // 初始化 void initQueue(LinkQueue &Q){ // 建立頭結點 Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode)); Q.front->next = NULL; } // 判隊空 bool isEmpty(LinkQueue Q){ return Q.front == Q.rear; } // 入隊 void enQueue(LinkQueue &Q,int x){ LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode)); s->data = x; s->next = NULL; Q.rear->next = s; // 隊尾指標移動到新的尾部 Q.rear = s; } // 出隊 bool deQueue(LinkQueue &Q,int &x){ if(isEmpty(Q)){ return false; } LinkNode *p = Q.front->next; x = p->data; Q.front->next = p->next; // 如果原佇列只有一個結點,刪除後變空 if(Q.rear == p){ Q.rear = Q.front; } free(p); return true; } // 列印 // 註釋掉的寫法中,雖然這裡的Q和main裡的Q不是同一個 // 但後續連線的結點都是同一個(即同一塊記憶體空間) // 如果呼叫了deQueue函式,會把結點free掉,導致main裡接下來的deQueue無法使用,報segement fault異常(記憶體無法訪問,因為已經被釋放了) // 正確的做法應該是拿另一個指標去遍歷這個連結串列 void printQueue(LinkQueue Q){ printf("佇列中的元素為:\n"); // while(!isEmpty(Q)){ // int deElem; // deQueue(Q,deElem); // printf("%d ",deElem); // } LinkNode *p = Q.front->next; while (p){ printf("%d ",p->data); p = p->next; } printf("\n"); } // 主函式測試 int main(){ LinkQueue Q; // 初始化 initQueue(Q); printQueue(Q); // 放幾個元素 enQueue(Q,1); enQueue(Q,3); enQueue(Q,5); // 應該輸出:1 3 5 printQueue(Q); // 出隊一個元素 int deElem; deQueue(Q,deElem); printQueue(Q); return 0; }
棧的應用——表示式求值
演算法簡述
這個演算法,稱得上是一個非常經典的演算法了。這裡我先用常規的語言描述一下這個演算法:
- 初始化兩個棧,運算元棧與運算子棧。
- 若掃描到運算元,壓入運算元棧。
- 若掃描到運算子或界限符(這裡主要指的就是
(
與)
),那麼就比對運算子棧頂與掃描到的運算子或界限符的優先順序,如果棧頂符號的優先順序大於等於掃描到的,那麼就彈出運算子棧,運算元棧彈出兩個值,按照彈出的運算子進行運算後再次壓入到運算元棧。 - 掃描到字串結尾,將運算元棧的棧頂元素彈出就是結果。
這個演算法具體的演示與操作步驟,大家可以百度搜一下。
而我這裡實現的這個演算法,實現了多位數的求值,如100*100+100這種的,主要是使用了一個counter變數來判斷是否為多位數。具體邏輯直接看程式碼吧。
程式碼
/*
* @Description: 表示式求值
* @version: 1.0
* @Author: Liuge
* @Date: 2021-07-12 19:30:34
*/
#include <bits/stdc++.h>
using namespace std;
// 運算子棧
stack<char> opter;
// 運算元棧
stack<double> opval;
// 獲取theta所對應的索引
int getIndex(char theta)
{
int index = 0;
switch (theta)
{
case '+':
index = 0;
break;
case '-':
index = 1;
break;
case '*':
index = 2;
break;
case '/':
index = 3;
break;
case '(':
index = 4;
break;
case ')':
index = 5;
break;
case '#':
index = 6;
default:
break;
}
return index;
}
// 獲取theta1與theta2之間的優先順序
char getPriority(char theta1, char theta2)
{
// 算符間的優先順序關係
const char priority[][7] =
{
{'>', '>', '<', '<', '<', '>', '>'},
{'>', '>', '<', '<', '<', '>', '>'},
{'>', '>', '>', '>', '<', '>', '>'},
{'>', '>', '>', '>', '<', '>', '>'},
{'<', '<', '<', '<', '<', '=', '0'},
{'>', '>', '>', '>', '0', '>', '>'},
{'<', '<', '<', '<', '<', '0', '='},
};
int index1 = getIndex(theta1);
int index2 = getIndex(theta2);
return priority[index1][index2];
}
// 計算a theta b
double calculate(double a, char theta, double b)
{
switch (theta)
{
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return a / b;
default:
return 0;
}
}
// 表示式求值
double getAnswer()
{
// 首先將'#'入棧opter
opter.push('#');
// 新增變數counter表示有多少個數字相繼入棧,實現多位數的四則運算
int counter = 0;
char c = getchar();
// 終止條件
while (c != '#' || opter.top() != '#')
{
// 如果c在'0'~'9'之間 使用isdigit函式用來判斷字元型變數是否是數字
if (isdigit(c))
{
// counter==1表示上一字元也是數字,所以要合併,比如12*12,要算12,而不是單獨的1和2
if (counter == 1)
{
double t = opval.top();
opval.pop();
opval.push(t * 10 + (c - '0'));
counter = 1;
}
else
{
// 將c對應的數值入棧opval
opval.push(c - '0');
counter++;
}
c = getchar();
}
else
{
// counter置零
counter = 0;
// 獲取運算子棧opter棧頂元素與c之間的優先順序,用'>','<','='表示
switch (getPriority(opter.top(), c))
{
// <則將c入棧opter
case '<':
opter.push(c);
c = getchar();
break;
// =將opter棧頂元素彈出,用於括號的處理
case '=':
opter.pop();
c = getchar();
break;
// >則計算
case '>':
char theta = opter.top();
opter.pop();
double a = opval.top();
opval.pop();
double b = opval.top();
opval.pop();
opval.push(calculate(b, theta, a));
}
}
}
// 返回opval棧頂元素的值
return opval.top();
}
int main()
{
cout << "請輸入算數運算式(以#結尾)" << endl;
while (!opter.empty())
opter.pop();
while (!opval.empty())
opval.pop();
double ans = getAnswer();
cout << ans << endl;
return 0;
}
總結
總的來說,這塊一定要好好掌握。參考書籍依然是王道的資料結構輔導書。參考程式碼來自網路。