資料結構與演算法-棧與佇列
阿新 • • 發佈:2020-07-30
棧與佇列
棧與實現
ADT介面
棧(stack)是存放資料物件的一種特殊容器,其中的資料元素按線性的邏輯次序排列,只能對一端的資料進行操作,並且遵守先進後出的原則。
棧支援的操作介面
操作例項
給出了一個存放整數的棧從被建立開始,按以上介面實施一系列操作的過程。
實現
c++實現
定義一個Stack繼承Vector類,並新增一些相關操作方法
1 #include "../Vector/Vector.h" //以向量為基類,派生出棧模板類 2 template <typename T> class Stack: public Vector<T> { //將向量癿首/末端作為棧底/頂 3 public: //size()、empty()以及其它開放介面,均可直接沿用 4 void push(T const& e) { insert(size(), e); } //入棧:等效亍將新元素作為向量癿末元素揑入 5 T pop() { return remove(size() - 1); } //出棧:等效亍初除向量癿末元素 6 T& top() { return (*this)[size() - 1]; } //叏頂:直接迒迴向量癿末元素 7 };
java實現
使用list
public class Stack<E> { //棧中屬性 public List<E> items = new ArrayList<E>(); public Stack() { } //棧相關的方法 //壓棧操作:新增一個新元素到棧頂位置. public void push(E element){ items.add(element); } //出棧操作:移除棧頂的元素,同時返回被移除的元素。 public E pop(){ E e = items.get(items.size() - 1); items.remove(items.size() - 1); return e; } //peek操作:返回棧頂的元素,不對棧做任何修改(這個方法不會移除棧頂的元素,僅僅返回它)。 public E peek(){ return items.get(items.size() - 1); } //判斷棧中元素是否為空:如果棧裡沒有任何元素就返回true,否則返回false。 public Boolean isEmpty(){ return items.size() == 0; } //獲取棧中元素的個數:移除棧裡的所有元素。 public int size(){ return items.size(); } }
使用Vector
public class Stack02<E> { //棧中屬性 Vector items = new Vector<E>(); public Stack02() { } //棧相關的方法 //壓棧操作:新增一個新元素到棧頂位置. public void push(E element){ items.addElement(element); } //出棧操作:移除棧頂的元素,同時返回被移除的元素。 public E pop(){ E e = (E) items.elementAt(items.size() - 1); items.remove(items.size() - 1); return e; } //peek操作:返回棧頂的元素,不對棧做任何修改(這個方法不會移除棧頂的元素,僅僅返回它)。 public E peek(){ return (E) items.elementAt(items.size() - 1); } //判斷棧中元素是否為空:如果棧裡沒有任何元素就返回true,否則返回false。 public Boolean isEmpty(){ return items.size() == 0; } //獲取棧中元素的個數:移除棧裡的所有元素。 public int size(){ return items.size(); } }
棧的典型應用
分為4個方面:逆序輸出、遞迴巢狀、延遲緩衝、逆波蘭表示式
逆序輸出
對進位制進行處理
@Test
public void test2(){
StringBuffer str = dec2bin(100);
System.out.println(str);
}
public StringBuffer dec2bin(int decNumer) {
// 定義變數
Stack02 stack = new Stack02();
int remainder;
// 迴圈除法
while (decNumer > 0) {
remainder = decNumer % 2;
decNumer = (int) Math.floor(decNumer / 2);
stack.push(remainder);
}
// 將資料取出
StringBuffer stringBuffer = new StringBuffer();
while (!stack.isEmpty()) {
stringBuffer.append(stack.pop());
}
return stringBuffer;
}
public StringBuffer convert(int n,int base) {
// 定義變數
Stack02 stack = new Stack02();
char[] digit = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
while(n > 0){
stack.push(digit[n % base]);
n /= base;
}
// 將資料取出
StringBuffer stringBuffer = new StringBuffer();
while (!stack.isEmpty()) {
stringBuffer.append(stack.pop());
}
return stringBuffer;
}
遞迴巢狀
進行括號的匹配判斷
@Test
public void test4(){
char[] par ={'(','{','[','(',')','[',']',']','}',')'};
char[] par1 ={'(','(',')',')'};
/* System.out.println(paren1(par,0, par.length ));*/
System.out.println(paren2(par1,0, par1.length-1));
}
public Boolean paren1(char exp[],int lo,int hi){
Stack02<Character> stack = new Stack02<Character>();
for (int i = lo;i < hi; i++){
switch (exp[i]) {
//左括號直接進棧;右括號若與棧頂失配,則表示式必不匹配
case '(':
case '[':
case '{':
stack.push(exp[i]);
break;
case ')':
if ((stack.isEmpty()) || ('(' != stack.pop())) return false;
break;
case ']':
if ((stack.isEmpty()) || ('[' != stack.pop())) return false;
break;
case '}':
if ((stack.isEmpty()) || ('{' != stack.pop())) return false;
break;
default:
break;
//非括號字元一律忽略
}
}
return stack.isEmpty();
}
public void trim(char[] exp,int lo,int hi){
while((lo <= hi) && (exp[lo] != '(') && (exp[lo] != ')')) lo++;
while((lo <= hi) && (exp[hi] != '(') && (exp[hi] != ')')) lo--;
}
public int divide(char[] exp,int lo,int hi){
int mi = lo;
int crc = 1;
while ((0 < crc) && (++mi < hi)){
if (exp[mi] == ')') crc--;
if (exp[mi] == '(') crc++;
}
return mi;
}
public Boolean paren2(char[] exp,int lo,int hi){
//有一點問題
trim(exp,lo,hi);
if (lo > hi) return true;
if (exp[lo] != '(') return false;
if (exp[hi] != ')') return false;
int mi = divide(exp,lo,hi);
if (mi > hi) return false;
return paren2(exp,lo + 1,mi -1) && paren2(exp,mi + 1, hi);
}
棧混洗(與括號匹配類似)
存在一個特點
延遲緩衝
進行表示式的計算
package com.atguigu.shed;
import org.junit.Test;
import java.util.ArrayList;
/**
* @anthor shkstart
* @create 2020-07-30 8:43
*/
public class mulate {
@Test
public void test1() {
String str = "(11*15+26)-3";
char[] ch = str.toCharArray();
ArrayList sb = new ArrayList();
System.out.println(evaluate1(ch,sb));
}
//通過設定二維陣列,對各種比較的優先順序作出判定
public static int optrtwo(char op) {
switch (op) {
case '+':
return 0; //加
case '-':
return 1; //減
case '*':
return 2; //乘
case '/':
return 3; //除
case '^':
return 4; //乘方
case '!':
return 5; //階乘
case '(':
return 6; //左括號
case ')':
return 7; //右括號
case '':
return 8; //起始符與終止符
default:
System.exit(1); //未知運算子
}
return -1;
}
static char[][] pri = {
{'>', '>', '<', '<', '<', '<', '<', '>', '>'},
{'>', '>', '<', '<', '<', '<', '<', '>', '>'},
{'>', '>', '>', '>', '<', '<', '<', '>', '>'},
{'>', '>', '>', '>', '<', '<', '<', '>', '>'},
{'>', '>', '>', '>', '>', '<', '<', '>', '>'},
{'>', '>', '>', '>', '>', '>', ' ', '>', '>'},
{'<', '<', '<', '<', '<', '<', '<', '=', ' '},
{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
{'<', '<', '<', '<', '<', '<', '<', ' ', '='}
};
public char orderBetween(char op1, char op2) {
return pri[optrtwo(op1)][optrtwo(op2)];
}
//判斷是否是數字,並進行正確的讀取
public static boolean isdigit(char[] S, int i) {
if (('0' <= S[i]) && (S[i] <= '9')) {
return true;
}
return false;
}
public int readNumber(char[] S, Stack<Float> stk, int i) {
stk.push((float) (S[i] - 48));//當前數位對應的數值進棧
while (isdigit(S, ++i)) {
stk.push(stk.pop() * 10 + (float) (S[i] - 48));
}
if ('.' != S[i]) return i;
float fraction = 1; //否則,意味著還有小數部分
while (isdigit(S, ++i)) //逐位加入
stk.push(stk.pop() + ((float) (S[i] - 48)) * (fraction /= 10)); //小數部分
return i;
}
//兩種計算的情況:階乘或其他簡單運算
public float calcu1(float num) {
float sum = 1;
if (num == 1) {
return 1;//根據條件,跳出迴圈
} else {
sum = (num * calcu1(num - 1));//運用遞迴計算
return sum;
}
}
public float calcu2(float num1, char op, float num2) {
switch (op) {
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '*':
return num1 * num2;
case '/':
return num1 / num2;
case '^':
return (int) num1 ^ (int) num2;
default:
System.exit(1);
}
return num1;
}
//通過表示式實現對於算式的計算
public float evaluate1(char[] S, ArrayList list) {
int i = 0;
Stack<Float> opnd = new Stack();
Stack<Character> optr = new Stack();
optr.push('');
while (!optr.isEmpty()) {
if (isdigit(S, i)) {
i = readNumber(S, opnd, i);
list.add(opnd.peek());
} else {
switch (orderBetween(optr.peek(), S[i])) {
case '<': //棧頂運算子優先順序更低時
optr.push(S[i]);
i++; //計算推遲,當前運算子進棧
break;
case '=': //優先順序相等(當前運算子為右括號或者尾部哨兵'')時
optr.pop();
i++; //脫括號並接收下一個字元
break;
case '>': { //棧頂運算子優先順序更高時,可實現相應計算,並將結果重新入棧
char op = optr.pop(); //棧頂運算子出棧
list.add(op);
if ('!' == op) { //若屬於一元運算子
float pOpnd = opnd.pop();
opnd.push(calcu1(pOpnd)); //實斲一元計算,結枅入棧
} else { //對亍其它(二元)運算子
float pOpnd2 = opnd.pop();
float pOpnd1 = opnd.pop(); //
opnd.push(calcu2(pOpnd1, op, pOpnd2));
}
break;
}
default:
System.exit(1);
}
}
}
System.out.println(list);
return opnd.pop();
}
}
逆波蘭表示式
手動整理
程式碼實現
package com.atguigu.shed;
/**
* @anthor shkstart
* @create 2020-07-30 16:18
*/
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/*
* 求解逆波蘭表示式
* */
public class PolandNotation {
public static void main(String[] args) {
/*
//計算後續表示式
String lastExpersion = "1 2 + 5 * 6 -";
//將lastExpersion分解成單個字元並存入arraylist陣列中
List<String> list = transferArrayList(lastExpersion);
//遍歷list集合進行對應的計算操作
int res = 0;
res = calculator(list);
System.out.println(lastExpersion + "=" + res);*/
//中序轉後序
String expersion = "1+((2+3)*4)-5";
List<String> infixExpersion = new ArrayList();
infixExpersion = toInfixExpersionList(expersion);
System.out.println("中綴表示式:"+infixExpersion);
List<String> suffixExpersion = new ArrayList();
suffixExpersion = parseSuffixExpersion(infixExpersion);
System.out.println("字尾表示式:"+suffixExpersion);
System.out.println("expersion="+calculator(suffixExpersion));
}
/*
將中序表示式的list轉為後序表示式的list
準備一個棧s1,ArrayList集合s2
* 1.遍歷中序表示式
2.如果是運算元直接入s2
3.如果是括號:
左括號(:直接入s1
右括號):將s1棧頂元素依次出棧然後放入s2直至棧頂為(為止
4.如果是操作符
s1為空則存入s1
棧頂值為(則如s1
否則
優先順序如果大於棧頂運算子直接入s1
優先順序如果小於等於棧頂運算子則將s1的棧頂運算子加到s2中然後再次進行4操作
* */
private static List<String> parseSuffixExpersion(List<String> list) {
Stack<String> s1 = new Stack<>();
List<String> s2 = new ArrayList<>();
for (String oper : list) {
if (oper.matches("\d+")) {
//如果是運算元;
s2.add(oper);
} else if (oper.equals("(")) {
s1.push(oper);
} else if (oper.equals(")")) {
while (!s1.peek().equals("(")) {
s2.add(s1.pop());
}
s1.pop();
//將"( "出棧
} else {
//是操作符,當oper的優先順序大於棧頂時將oper加入s1,否者將s1棧頂出棧加入s2並迴圈判斷
while (s1.size() != 0 && getPriority(s1.peek().charAt(0)) >= getPriority(oper.charAt(0))) {
s2.add(s1.pop());
}
s1.push(oper);
}
}
while (s1.size() != 0) {
s2.add(s1.pop());
}
return s2;
}
//進行逆波蘭表示式的運算規則
//從左至右掃描逆波蘭表示式
//1.如果是運算元就進棧
//2.如果是操作符,就將兩個操作數出棧進行運算
private static int calculator(List<String> list) {
if (list == null) {
throw new RuntimeException("集合為空");
}
Stack<String> stack = new Stack<>();
for (String oper : list) {
//如果oper是運算元則入棧
if (oper.matches("\d+")) {
stack.push(oper);
} else {
//oper是字元則將兩個書pop出
int num2 = Integer.parseint(stack.pop());
int num1 = Integer.parseint(stack.pop());
int res = 0;
//實際計算操作
res = doCalculator(num1, num2, oper);
stack.push("" + res);
}
}
return Integer.parseint(stack.pop());
}
//進行實際的計算處理
private static int doCalculator(int num1, int num2, String oper) {
char c = oper.charAt(0);
int res = 0;
switch (c) {
case '+':
res = num1 + num2;
break;
case '-':
res = num1 - num2;
break;
case '*':
res = num1 * num2;
break;
case '/':
if (num1 == 0) {
throw new RuntimeException("被除數不能為0");
}
res = num1 / num2;
break;
default:
System.out.println("引數有誤");
break;
}
return res;
}
//將逆波蘭表示式逐個存入list集合中
private static List transferArrayList(String lastExpersion) {
if (lastExpersion == "") {
System.out.println("逆波蘭表示式為空!!");
return null;
}
String[] operArr = lastExpersion.split(" ");
//如果最後一位不是操作符而是運算元則表示式錯誤
if (operArr[operArr.length - 1].matches("\d+")) {
throw new RuntimeException("逆波蘭表示式有誤,最後一位應該為操作符");
}
List<String> list = new ArrayList<String>();
for (String str : operArr) {
list.add(str);
}
return list;
}
//將中序表示式裝入ArrayList中
public static List<String> toInfixExpersionList(String s) {
if (s == "") {
throw new RuntimeException("中序表示式不能為空!!");
}
int index = 0;
//相當於一個指標用於遍歷s
char oper = ' ';
//用於儲存s中index索引處的字元
List<String> list = new ArrayList<String>();
String str = "";
//用於處理多位數
do {
if ((oper = s.charAt(index)) < 48 || (oper = s.charAt(index)) > 57) {
//當前字元是非數字
list.add("" + oper);
index++;
} else {
str = "";
//當前字元為運算元,要判斷是不是多位數
while (index < s.length() && (oper = s.charAt(index)) >= 48 && (oper = s.charAt(index)) <= 57) {
str += oper;
//拼接;
index++;
}
list.add(str);
}
}
while (index < s.length());
return list;
}
//得到操作符的優先順序
public static int getPriority(int ch) {
if (ch == '+' || ch == '-') {
return 0;
} else if (ch == '*' || ch == '/') {
return 1;
} else {
return -1;
}
}
}
佇列與實現
與棧一樣,佇列(queue)也是存放資料物件的一種容器,其中的資料物件也按線性的邏輯
次序排列。佇列結構同樣支援物件的插入和刪除,但兩種操作的範圍分別被限制於佇列的兩端
若約定新物件只能從某一端插入其中,則只能從另一端刪除已有的元素。允許取出元素的一
端稱作隊頭(front),而允許插入元素的另一端稱作隊尾(rear)。
佇列的實現
通過對List的繼承
c++實現
java實現
public class Quene<T> extends List_DLNode<T> {
private Quene<T> qu;
public Quene() {
}
public Quene(Quene<T> qu) {
this.qu = qu;
}
public void enqueue(T e){
insertLast(e);
}
public T dequeue() throws ExceptionPositionInvalid {
return removeFirst();
}
public T front() throws ExceptionListEmpty {
return (T) first();
}
}
佇列的應用
迴圈分配器
RoundRobin {
//迴圈分配器
Queue Q(clients);
//參不資源分配癿所有客戶組成佇列Q
while (!ServiceClosed()) {
//在服務兲閉乀前,反覆地
e = Q.dequeue();
//隊首癿客戶出隊,幵
serve(e);
//接叐服務,然後
Q.enqueue(e);
//重新入隊
}
}
銀行服務模擬
struct Customer {
int window;
unsigned int time;
}
;
//順客類:所屬視窗(佇列)、服務時長
void simulate(int nWin, int servTime) {
//按指定視窗數、服務總時間模擬銀行業務
Queue<Customer>* windows = new Queue<Customer>[nWin];
//為殏一視窗建立一個佇列
for (int now = 0; now < servTime; now++) {
//在下班乀前,殏隑一個單位時間
if (rand() % (1 + nWin)) {
//新順客以nWin/(nWin + 1)癿概率刡達
Customer c ;
c.time = 1 + rand() % 98;
//新順客刡達,服務時長隨機確定
c.window = bestWindow(windows, nWin);
//找出最佳(最短)癿服務視窗
windows[c.window].enqueue(c);
//新順客加入對應癿佇列
}
for (int i = 0; i < nWin; i++) //分刪檢查
if (!windows[i].empty()) //各非空佇列
if (-- windows[i].front().time <= 0) //隊首順客癿服務時長減少一個單位
windows[i].dequeue();
//服務完畢癿順客出列,由後繼順客接替
}
//while
delete [] windows;
//釋放所有佇列(此前,~List()會自勱清空佇列)
}