1. 程式人生 > 實用技巧 >資料結構與演算法-棧與佇列

資料結構與演算法-棧與佇列

棧與佇列

棧與實現

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()會自勱清空佇列)
}

試探回溯法(還未寫)