C++棧的應用——後綴表達式求值、中綴表達式到後綴表達式的轉換

一、前言

    通常我們把棧歸爲一種基本的數據結構,同時它也是一種線性表結構,也就是說你要自己實現一個棧的數據結構,既可以用數組實現,也可以用鏈表實現。棧最主要的特點就是“先進後出”,因爲棧只有一個入口和出口。

 

二、實現棧結構

    根據棧的先進後出的特點,很容易設置棧結構的接口:入棧、出棧、判空、size()等,熟悉數據庫的同學都知道數據庫無非就是四種操作:增、刪、改、查,其實對於一個數據結構的接口而言,也是這四種操作,就棧而言,入棧即增操作、出棧即刪操作、由於棧是線性表結構,所以查和改操作都需要遍歷整個棧結構。現在已經知道了棧的接口操作,我們就可以用線性表的方法來實現一個棧結構,其實也就兩種,用鏈表或數組實現棧。

    但是,在C++標準庫中已經爲我們實現了棧結構,而且是按照最高效率、最優的標準實現的,你可以放心的使用C++標準庫提供的棧結構,以C++一貫的作風,其實現的棧結構是一個棧類型,定義在<stack>頭文件中,使用的時候只需要#include該頭文件就行。

    根據C++STL的解釋,或C++Primer(第五版P329)的解釋,都把stack類型稱爲一個容器適配器(配接器),並沒有稱其爲一個容器,儘管如此,你可以把stack看作是一個特殊的容器,所謂適配器(配接器),指的是一種機制,一個容器適配器使一個容器的行爲看起來像另外一個容器,這句話說的是什麼意思呢?這是因爲C++的容器適配器都是基於基本容器實現的,比如stack就是基於queue實現的(默認,也可以自己顯視的指定爲vector),這也導致了任何stack的操作接口都是直接調用底層容器的操作來完成的,如stack的push操作(入棧)就是調用queue的push_back操作來完成的。下面給出STL中stack的定義文件:

 

//模板定義
template<class _Ty,class _Container = deque<_Ty> >
class stack
{
protected:
	_Container c;	//底層容器對象,_Container是指底層容器類型

public:
	typedef stack<_Ty, _Container> _Myt;     //類型別名定義
	typedef _Container container_type;
	typedef typename _Container::value_type value_type;
	typedef typename _Container::size_type size_type;
	typedef typename _Container::reference reference;
	typedef typename _Container::const_reference const_reference;

	stack(): c()
	{
		//默認構造函數,構造空棧,這裏是調用其成員容器對象的默認構造函數
	}

	stack(const _Myt& _Right): c(_Right.c)
	{	
		// construct by copying _Right
	}

	explicit stack(const _Container& _Cont): c(_Cont)
	{	
		// construct by copying specified container
	}

	void push(value_type&& _Val)
	{	
		//直接調用底層容器的操作實現stack自身接口
		// insert element at beginning
		c.push_back(_STD move(_Val));
	}

	bool empty() const
	{	
		// test if stack is empty
		return (c.empty());
	}

	size_type size() const
	{	
		// test length of stack
		return (c.size());
	}

	reference top()
	{	
		// return last element of mutable stack
		return (c.back());
	}

	const_reference top() const
	{	
		// return last element of nonmutable stack
		return (c.back());
	}

	void push(const value_type& _Val)
	{	
		// insert element at end
		c.push_back(_Val);
	}

	void pop()
	{	
		// erase last element
		c.pop_back();
	}
};

關於上面的C++STL中stack的定義,你可以不瞭解,你只需要知道stack提供給你哪些接口,這些接口應該怎麼用就行了,至於其內部實現,STL已經爲你實現好了,完全不用你擔心。

 

 

 

三、棧的應用

1、後綴表達式求值

 

    以人類的思維,中綴表達式是正常的表達式形式,因爲我們已經熟悉了各種運算符號的優先級,知道在一個表達式中第一個求哪一部分的值,最常見的就是先求括號內部,然後再求括號外部,但是這種求值順序在計算機看來是很麻煩的,最好的辦法是我們輸入給計算機的表達式不需要知道操作符優先級,計算機只管按照我們輸入的表達式從左到右求值即可,這就要用後綴表達式來實現。後綴表達式是針對中綴表達式而言的,大致可以理解爲操作符在兩個操作數之後,並不是像中綴表達式那樣每兩個操作數之間必須有一個操作符,後綴表達式最大的特點就是沒有必要知道任何運算符的優先規則,如下就是一個後綴表達式:

“4.99 1.06 * 5.99 + 6.99 1.06 * + ”

其中綴表達式爲:“4.99 * 1.06 + 5.99 + 6.99 * 1.06 ”(關於怎麼從中綴表達式轉爲後綴表達式後面會介紹)

    後綴表達式的求值規則爲:從左到右掃描後綴表達式,如果遇到一個操作數,將其壓入棧中,如果遇到一個操作符,則從棧中彈出兩個操作數,計算結果,然後把結果入棧,直到遍歷完後綴表達式,則計算完成,此時的棧頂元素即爲計算結果,如上的後綴表達式求值過程爲:

初始,棧空;步驟(1)

遇到操作數4.99,入棧;步驟(2)

遇到操作數1.06,入棧;步驟(3)

遇到操作符*,彈出棧中兩個元素,計算結果入棧;步驟(4)

遇到操作數5.99,入棧;步驟(5)

遇到操作符+,彈出棧中兩個元素,計算結果入棧;步驟(6)

遇到操作數6.99,入棧;步驟(7)

遇到操作數1.06,入棧;步驟(8)

遇到操作符*,彈出棧中兩個元素,計算結果入棧;步驟(9)

遇到操作符+,彈出棧中兩個元素,計算結果入棧;步驟(10)

C++實現代碼如下(注意輸入的後綴表達式每個元素之後一定要有一個空格,這用於分開不同的元素):

 

/*********************後綴表達式求值(直接利用C++STL提供的Stack實現)**************************/
double postfixExpression(const string &str)
{
	stack<double> mystack;    //棧空間

	string s = ".0123456789+-*/";
	string empty = " ";
	string numbers = ".0123456789";
	string c = "+-*/";

	double firstnum;
	double secondnum;
	double sum;

	for(unsigned int i=0; i<str.size(); )
	{
		string::size_type start = str.find_first_of(s,i);     //查找第一個數字或算術符號
		string::size_type end = str.find_first_of(empty,i);   //查找第一個空格
		string tempstr = str.substr(start, end-start);     //取出這一個元素

		//判斷元素
		if(tempstr == "+" || tempstr == "-" || tempstr == "*" || tempstr == "/")
		{
			secondnum = mystack.top();    //取當前棧頂元素,由於棧的先進後出特性,當前棧頂元素其實是二元操作符中右側的操作數,如表達式3-2的後綴表達式爲“3 2 -”,這裏secondnum取得數就是2
			mystack.pop();
			firstnum = mystack.top();
			mystack.pop();
			if(tempstr == "+")
			{
				sum = firstnum + secondnum;
				mystack.push(sum);
			}
			if(tempstr == "-")
			{
				sum = firstnum - secondnum;
				mystack.push(sum);
			}
			if(tempstr == "*")
			{
				sum = firstnum * secondnum;
				mystack.push(sum);
			}
			if(tempstr == "/")
			{
				sum = firstnum / secondnum;
				mystack.push(sum);
			}
		}
		else
		{
			double num = stod(tempstr);
			mystack.push(num);
		}

		//控制迭代
		i = end + 1;
	}
	return mystack.top();
}

代碼測試如下:還是以上面那個例子的後綴表達式作爲輸入,則測試代碼如下:

 

 

void main()
{
	string Postfistr = "4.99 1.06 * 5.99 + 6.99 1.06 * + ";    //每個元素後需要有一個空格“ ”字符串
	double res = postfixExpression(Postfistr);
	cout << res <<endl;
}

 

    上述計算後綴表達式的前提是輸入的表達式就是後綴表達式,但是一般我們給出的表達式爲中綴表達式,這就需要先把中綴表達式轉爲後綴表達式。

 

2、中綴表達式轉爲後綴表達式

    中綴表達式轉爲後綴表達式也有一定的規則,這個規則是根據操作符的運算優先級來定的,還是上面那個中綴表達式爲:“4.99*1.06+5.99+6.99*1.06”,轉爲後綴表達式的規則爲:

    (1)這裏定義一個操作符棧stack來保存遇到的操作符,還需要定義string作爲後綴表達式輸出返回;

    (2)首先需要對輸入的中綴表達式進行“切片”處理,所謂切片,即對所輸入的中綴表達式進行元素分割,這裏的每個元素要麼是一個操作符(“+-*/()”),要麼是一個操作數(“.0123456789”),把這些元素存儲到一個vector<string>Inputvec中;

    (3)然後依次遍歷Inputvec中元素,根據其是操作數還是操作符來進行不同的“處理”。這裏的處理規則爲:

            如果是操作數,則直接保存到輸出string中;

            如果是操作符

                如果操作符棧爲空,則把操作符入棧;
                否則,則比較當前運算符與棧頂操作符優先等級;

                    如果當前操作符優先等級高,則當前操作符入棧;

                    否則,彈出棧頂操作符到輸出string中;

中綴表達式轉後綴表達式C++實現代碼如下:

 

//設置操作符優先級,這裏考慮到括號("("、")")匹配,定義設置左括號"("的優先級最高,且只有在遇到右括號時才彈出左括號
int priority(const string str)  
{
	const char *op = str.c_str();
    switch(*op)  
    {
    case ')':
        return 0;  
    case '+':  
    case '-':  
        return 1;  
    case '*':  
    case '/':  
        return 2; 
	case '(':
		return 3;
    default :  
        return -1;  
    }  
}  

/*********************中綴表達式轉爲後綴表達式**************************/
string InfixToPostfi(const string &str)
{
	string operatorstr = "*-/+()";      //用於string搜索
	string numbers = "0123456789.";

	//對輸入的中綴表達式中每個元素進行切片,每個元素存儲到vector<string>Inputstr
	vector<string> Inputvec;   //存儲切片結果
	for(unsigned int i=0; i<str.size(); )
	{
		string::size_type operatorindex = str.find_first_of(operatorstr,i);     //搜索str中從i開始的第一個操作符
		if(operatorindex != string::npos)
		{
			//如果從i開始搜索到了操作符
			if(operatorindex == i)
			{
				//如果是兩個連續的操作符,即這種形式的表達式   a*(b+c)+d;
				string tempstr = str.substr(operatorindex,1);
				Inputvec.push_back(tempstr);
				i = i+1;
			}
			else
			{
				Inputvec.push_back(str.substr(i,operatorindex-i));
				Inputvec.push_back(str.substr(operatorindex,1));
				i = operatorindex+1;
			}
		}
		else
		{
			//如果從i開始搜索到了操作符,即輸入的中綴表達式以操作數結尾,不是以操作符結尾(其實一個表達式以操作符結尾的情況只可能是以右括號")"結尾,這裏就是爲防止這種特殊情況)
			Inputvec.push_back(str.substr(i,str.size()-i));
			i = str.size();
		}
	}

	//遍歷切片結果vector中每個元素
	stack<string> operatorstack;     //創建空棧,用來存儲操作符
	vector<string> PostfiOutvec;     //存儲中綴輸出,這裏是存儲到vector
	for(int i=0; i<Inputvec.size(); i++)
	{
		//如果當前元素是操作符
		if(Inputvec[i].find_first_of(operatorstr) != string::npos)
		{
			if(operatorstack.empty())     
			{
				operatorstack.push(Inputvec[i]);      //如果操作符棧空,則直接入棧
			}
			else
			{
				if(Inputvec[i] == ")")     //如果當前操作符是右括號
				{
					while(operatorstack.top() != "(")
					{
						PostfiOutvec.push_back(operatorstack.top());     //將棧頂操作符輸出
						operatorstack.pop();    //刪除棧頂元素
					}
					operatorstack.pop();    //刪除棧頂元素(這裏是刪除左括號"(")
				}
				else
				{
					int curpri = priority(Inputvec[i]);     //獲取操作符的優先級

					//比較當前操作符與棧頂元素優先級,如果小於或等於棧頂元素優先級則彈出棧頂元素,否則當前操作符入棧
					while(!operatorstack.empty())
					{
						string top = operatorstack.top();     //返回棧頂元素
						int toppor = priority(top);     //棧頂元素優先級

						if((curpri <= toppor) && top!="(")       //左括號優先級最大,但是它只有遇到右括號才輸出
						{
							PostfiOutvec.push_back(top);
							operatorstack.pop();    //刪除棧頂元素
						}
						else
							break;
					}
					operatorstack.push(Inputvec[i]);
				}
			}
		}
		//如果當前元素是操作數,直接輸出
		else
		{
			PostfiOutvec.push_back(Inputvec[i]);
		}
	}
	while(!operatorstack.empty())
	{
		PostfiOutvec.push_back(operatorstack.top());      //輸出操作符棧中的其他操作符
		operatorstack.pop();
	}

	//在輸出中插入空格
	vector<string>::const_iterator itr=PostfiOutvec.begin()+1;
	while(itr!=PostfiOutvec.end())
	{
		itr = PostfiOutvec.insert(itr," ");      //這裏一定要返回insert之後的指針,因爲改變容器的操作會使迭代器失效
		itr+=2;
	}
	
	PostfiOutvec.push_back(" ");     //添加最後一個空格

	//vector輸出爲string,作爲後綴表達式結果返回
	string result;
	for(int i=0; i<PostfiOutvec.size(); i++)
	{
		result.append(PostfiOutvec[i]);
	}

	return result;
}

測試代碼如下:

 

 

void main()
{
	string Infixstr1 = "4.99*1.06+5.99+6.99*1.06";      //沒有括號
	string Infixstr2 = "4.99*1.06+5.99+(6.99*1.06)";    //中綴表達式以操作符結尾(這種情況只能是以右括號結尾)
	string Infixstr3 = "4.99*(1.06+5.99)+6.99*1.06";    //括號在中間
	string Infixstr4 = "4.99*1.06+5.99+()6.99*1.06";    //插入括號,其內沒有表達式
	string Postfistr1 = InfixToPostfi(Infixstr1);
	string Postfistr2 = InfixToPostfi(Infixstr2);
	string Postfistr3 = InfixToPostfi(Infixstr3);
	string Postfistr4 = InfixToPostfi(Infixstr4);
	double res1 = postfixExpression(Postfistr1);
	cout << "res1=" << res1 <<endl;
	double res2 = postfixExpression(Postfistr2);
	cout << "res2=" << res2 <<endl;
	double res3 = postfixExpression(Postfistr3);
	cout << "res3=" << res3 <<endl;
	double res4 = postfixExpression(Postfistr4);
	cout << "res4=" << res4 <<endl;
}

以上測試代碼中,測試了4中不同的中綴表達式形式,運行結果如下:

 



 

 

 

 

發佈了40 篇原創文章 · 獲贊 127 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章