本系列算法实现都是在学习数据结构(C++语言版),清华大学邓俊辉教授编,听邓老师edX网课过程中自己实现的例子。
参考链接
本文涉及到的栈应用有如下几种:
- 任意进制转换
- 括号匹配
- 逆波兰表达式
- 栈混洗
1 任意进制转换
1.1 使用stack实现的版本
#include <stack>
#include <algorithm>
//! stack version
//! 任意进制转换
//! 数字n(任意进制)-->字符s(base)
void convert_stackversion(int& dst, int n, int base)
{
const char digit[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'};
stack<char> s;
while (n > 0)//save
{
s.push(digit[n % base]);
n /= base;
}
string str("");
while (!s.empty())//use stack pop to implement string reverse
{
str.append(1, s.top());
s.pop();
}
dst = atoi(str.c_str());
}
1.2 使用string实现的版本
//! string version
//! 任意进制转换
//! 数字n(任意进制)-->字符s(base)
void convert_stringversion(int& dst, int n, int base)
{
const char digit[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'
};
string s("");
while (n > 0)
{
s = digit[n % base] + s;//use string + implement reverse
n /= base;
}
dst = atoi(s.c_str());
}
1.3 测试例子
int dst = 0;
convert_stackversion(dst, 0xff, 10);
cout << "dst " << dst << endl;;
convert_stringversion(dst, 0xff, 10);
cout << "dst " << dst << endl;
2 括号匹配
判断一个表达式括号是否匹配,
//! 括号匹配
bool bracket_match(const char exp[], int lo, int hi)
{
stack<char> s;
for (int i = lo; i < hi; ++i)
{
switch (exp[i])
{
case '(':
case '[':
case '{':
s.push(exp[i]);
break;
case ')':
if (s.empty() || '(' != s.top())
return false;
else
s.pop();
break;
case ']':
if (s.empty() || '[' != s.top())
return false;
else
s.pop();
break;
case '}':
if (s.empty() || '{' != s.top())
return false;
else
s.pop();
break;
default:
break;
}
}
return s.empty();
}
3 逆波兰表达式
3.1 算法解析
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于找顶符号(乘除优先加减)则栈顶元素依次出找并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
——来自将中缀表达式转化为后缀表达式——Veda 原型
3.2 算法接口
这里使用stack实现对表达式求值,并且将其转换为逆波兰表达式,使用的其实就是调度场算法
此处编写的代码主要参考是邓老师课件中的源代码,我只是重新设计了两个类并且改写了代码中的部分程序来实现该算法,OperatorPriority类是关于运算符优先级比较和根据运算符计算
接口如下
class OperatorPriority
{
public:
OperatorPriority() {}
~OperatorPriority() {}
//! 运算符优先级比较
char order_between ( char op1, char op2 );
//! 执行二元运算
float calcu ( float a, char op, float b );
//! 执行一元运算
float calcu ( char op, float b );
private:
typedef enum {ADD, SUB, MUL, DIV, POW, FAC, L_P, R_P, EOE} Operator; //运算符集合
#define N_OPTR 9 //运算符总数
//加、减、乘、除、乘斱、阶乘、左括号、右括号、起始符不终止符
const char pri[N_OPTR][N_OPTR] =
{
//运算符优先等级 [栈顶] [当前]
/*------------------------- 当前运算符 -----------------------*/
/* + - * / ^ ! ( ) \0*/
/* -- + */ {'>', '>', '<', '<', '<', '<', '<', '>', '>'},
/* - */ {'>', '>', '<', '<', '<', '<', '<', '>', '>'},
/* 栈 * */ {'>', '>', '>', '>', '<', '<', '<', '>', '>'},
/* 顶 / */ {'>', '>', '>', '>', '<', '<', '<', '>', '>'},
/* 运 ^ */ {'>', '>', '>', '>', '>', '<', '<', '>', '>'},
/* 算 ! */ {'>', '>', '>', '>', '>', '>', ' ', '>', '>'},
/* 符 ( */ {'<', '<', '<', '<', '<', '<', '<', '=', ' '},
/* ) */ {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
/* -- \0 */ {'<', '<', '<', '<', '<', '<', '<', ' ', '='}
};
Operator optr2rank ( char op );
};
第二个类就是ShuntingYard,主要实现了调度场算法,也就是中缀表达式转为逆波兰表达式。
接口如下
//! 调度场算法(Shunting Yard Algorithm)
//! 是一个用于将中缀表达式(Infix Notation)转换为逆波兰式RPN(Reverse Polish Notation)的经典算法,
//! 由艾兹格·迪杰斯特拉引入,因其操作类似于火车编组场而得名
class ShuntingYard : public OperatorPriority
{
public:
ShuntingYard(const std::string s): OperatorPriority()
{
m_str = const_cast<char*>(s.c_str());
remove_space(m_str);
}
~ShuntingYard(){}
float in2rpn_calcu(std::string & RPN);//! 中缀表达式转换为逆波兰式RPN并且计算中缀表达式的值
void in2rpn(std::string & RPN);//!中缀表达式转换为逆波兰式
float in_calcu();//! 计算中缀表达式的值
float rpn_calcu(const std::string rpn);//! 计算后缀表达式的值
private:
char* m_str;//string without space
void remove_space (char* s); //! 去除空格并且在字符串最后追加'\0'
float read_num( char*& p);//! 从当前字符串中读一个数字,直到遇到非数字且非小数点
};
3.3 详细代码
// shunting_yard.h中包含的头文件
#pragma once
#include <cstdlib>
#include <iostream>
#include <stack>
#include <string>
#include <boost/lexical_cast.hpp>
#include <boost/math/special_functions/factorials.hpp>
算法实现
// shunting_yard.cpp
#include "shunting_yard.h"
//!------------------------------------
//! class OperatorPriority
//!------------------------------------
//!
//! public
//!
//! 运算符优先级比较
char OperatorPriority::order_between ( char op1, char op2 ) //比较两个运算符之间的优先级
{
return pri[optr2rank ( op1 ) ][optr2rank ( op2 ) ];
}
//! 执行二元运算
float OperatorPriority::calcu ( float a, char op, float b )
{
switch ( op )
{
case '+' : return a + b;
case '-' : return a - b;
case '*' : return a * b;
case '/' :
if ( 0 == b )
exit ( -1 );
return a / b; //注意:如此判浮点数为零可能不安全
case '^' : return pow ( a, b );
default : exit ( -1 );
}
}
//! 执行一元运算
float OperatorPriority::calcu ( char op, float b )
{
switch ( op )
{
case '!' : return boost::math::factorial<float>((int)b);
default : exit ( -1 );
}
}
//!
//! private
//!
OperatorPriority::Operator OperatorPriority::optr2rank ( char op )
{
switch ( op ) {
case '+' : return ADD; //加
case '-' : return SUB; //减
case '*' : return MUL; //乘
case '/' : return DIV; //除
case '^' : return POW; //乘方
case '!' : return FAC; //阶乘
case '(' : return L_P; //左括号
case ')' : return R_P; //右括号
case '\0': return EOE; //起始符与终止符
default : exit ( -1 ); //未知运算符
}
}
//!------------------------------------
//! class ShuntingYard
//!------------------------------------
//!
//! public
//!
//! 中缀表达式转换为逆波兰式RPN
//! 并且计算中缀表达式的值
float ShuntingYard::in2rpn_calcu(std::string & RPN)
{
char* str = m_str;
std::stack<float> opnd;//运算数栈
std::stack<char> optr; //运算符栈, 任何时刻,其中每对相邻元素之间均大小一致
optr.push ( '\0' ); //尾哨兵'\0'也作为头哨兵首先入栈
while (!optr.empty())//在运算符栈非空之前,逐个处理表达式中各字符
{
if (isdigit(*str))//当前字符为操作数
{
float num = read_num(str);
opnd.push(num);
RPN.append(boost::lexical_cast<std::string>(opnd.top())); //读入操作数,并将其接至RPN末尾
RPN.append(" ");
}
else //若当前字符为运算符
{
switch ( order_between ( optr.top(), *str ) )//视其与栈顶运算符之间优先级高低分别处理
{
case '<': //栈顶运算符优先级更低时
{
optr.push ( *str );
str++; //计算推迟,当前运算符进栈
break;
}
case '=': //优先级相等(当前运算符为右括号或者尾部哨兵'\0')时
{
optr.pop();
str++; //脱括号并接收下一个字符
break;
}
case '>': //栈顶运算符优先级更高时,可实施相应的计算,并将结果重新入栈
{
char op = optr.top();
optr.pop();
RPN.append(1, op);//栈顶运算符出栈并续接至RPN末尾
RPN.append(" ");
if ( '!' == op )//若属于一元运算符
{
float pOpnd = opnd.top();
opnd.pop();
opnd.push (calcu(op, pOpnd));
}
else //对于其它(二元)运算符
{
float pOpnd1 = opnd.top();
opnd.pop();
float pOpnd2 = opnd.top();
opnd.pop();
opnd.push (calcu(pOpnd1, op, pOpnd2));
}
break;
}
default :
exit ( -1 ); //逢语法错误,不做处理直接退出
}//switch
}
}//while
return opnd.top(); //弹出并返回最后的计算结果
}
//! 中缀表达式(Infix Notation) 转换为逆波兰式RPN(Reverse Polish Notation)
void ShuntingYard::in2rpn(std::string & RPN)
{
char* str = m_str;
std::stack<char> optr; //运算符栈, 任何时刻,其中每对相邻元素之间均大小一致
optr.push ( '\0' ); //尾哨兵'\0'也作为头哨兵首先入栈
while (!optr.empty())//在运算符栈非空之前,逐个处理表达式中各字符
{
if (isdigit(*str))//当前字符为操作数
{
float num = read_num(str);
RPN.append(boost::lexical_cast<std::string>(num)); //读入操作数,并将其接至RPN末尾
RPN.append(" ");
}
else //若当前字符为运算符
{
switch ( order_between ( optr.top(), *str ) )//视其与栈顶运算符之间优先级高低分别处理
{
case '<': //栈顶运算符优先级更低时
{
optr.push ( *str );
str++; //计算推迟,当前运算符进栈
break;
}
case '=': //优先级相等(当前运算符为右括号或者尾部哨兵'\0')时
{
optr.pop();
str++; //脱括号并接收下一个字符
break;
}
case '>': //栈顶运算符优先级更高时,可实施相应的计算,并将结果重新入栈
{
char op = optr.top();
optr.pop();
RPN.append(1, op);//栈顶运算符出栈并续接至RPN末尾
RPN.append(" ");
break;
}
default :
exit ( -1 ); //逢语法错误,不做处理直接退出
}//switch
}
}//while
}
//! 计算中缀表达式的值
float ShuntingYard::in_calcu()
{
char* str = m_str;
std::stack<float> opnd;//运算数栈
std::stack<char> optr; //运算符栈, 任何时刻,其中每对相邻元素之间均大小一致
optr.push ( '\0' ); //尾哨兵'\0'也作为头哨兵首先入栈
while (!optr.empty())//在运算符栈非空之前,逐个处理表达式中各字符
{
if (isdigit(*str))//当前字符为操作数
{
float num = read_num(str);
opnd.push(num);
}
else //若当前字符为运算符
{
switch ( order_between ( optr.top(), *str ) )//视其与栈顶运算符之间优先级高低分别处理
{
case '<': //栈顶运算符优先级更低时
{
optr.push ( *str );
str++; //计算推迟,当前运算符进栈
break;
}
case '=': //优先级相等(当前运算符为右括号或者尾部哨兵'\0')时
{
optr.pop();
str++; //脱括号并接收下一个字符
break;
}
case '>': //栈顶运算符优先级更高时,可实施相应的计算,并将结果重新入栈
{
char op = optr.top();
optr.pop();
if ( '!' == op )//若属于一元运算符
{
float pOpnd = opnd.top();
opnd.pop();
opnd.push (calcu(op, pOpnd));
}
else //对于其它(二元)运算符
{
float pOpnd1 = opnd.top();
opnd.pop();
float pOpnd2 = opnd.top();
opnd.pop();
opnd.push (calcu(pOpnd1, op, pOpnd2));
}
break;
}
default :
exit ( -1 ); //逢语法错误,不做处理直接退出
}//switch
}
}//while
return opnd.top(); //弹出并返回最后的计算结果
}
//! 计算后缀表达式的值
float ShuntingYard::rpn_calcu(const std::string rpn)
{
char* str = const_cast<char*>(rpn.c_str());
std::stack<float> opnd;//运算数栈
while (*str)//在运算符栈非空之前,逐个处理表达式中各字符
{
if (isspace(*str))
{
str++;
continue;
}
if (isdigit(*str))//当前字符为操作数
{
float num = read_num(str);
opnd.push(num);
}
else //若当前字符为运算符
{
char op = *str++;
if ( '!' == op )//若属于一元运算符
{
float pOpnd = opnd.top();
opnd.pop();
opnd.push (calcu(op, pOpnd));
}
else //对于其它(二元)运算符
{
float pOpnd1 = opnd.top();
opnd.pop();
float pOpnd2 = opnd.top();
opnd.pop();
opnd.push (calcu(pOpnd1, op, pOpnd2));
}
}
}//while
return opnd.top(); //弹出并返回最后的计算结果
}
//!
//! private
//!
//! 去除空格并且在字符串最后追加'\0'
void ShuntingYard::remove_space (char* s)
{
char* p = s, *q = s;
while ( true )
{
while (isspace(*q))
q++;
if ('\0' == *q)
{
*p = '\0';
return;
}
*p++ = *q++;
}
}
//! 从当前字符串中读一个数字,直到遇到非数字且非小数点
float ShuntingYard::read_num( char*& p)
{
std::stack<float> stk;
stk.push (static_cast<float>(*p - '0')); //当前数位对应的数值进栈
while (isdigit(*(++p))) //只要后续还有紧邻的数字(即多位整数的情况),则
{
float top = stk.top();
stk.pop();
stk.push (top * 10 + static_cast<float>(*p - '0')); //弹出原操作数并追加新数位后,新数值重新入栈
}
if ('.' != *p)
return stk.top(); //此后非小数点,则意味着当前操作数解析完成
float fraction = 1; //否则,意味着还有小数部分
while (isdigit(*(++p))) //逐位加入
{
float top = stk.top();
stk.pop();
fraction /= 10;
stk.push (top + static_cast<float>(*p - '0')*fraction); //小数部分
}
return stk.top();
}
4 栈混洗
有三个栈,src, tmp, dst.
这三个栈的元素出入顺序是src–>tmp–>dst。
判断结束的条件是src.empty() && !tmp.empty()成立时,判断tmp.top() == dst.top()
//! stack permutation
//! dirction: src-->tmp-->dst
template <typename T>
bool is_stack_permutation(stack<T>src, stack<T>dst)
{
stack<T> tmp;
while (!dst.empty())
{
if (!src.empty() && !tmp.empty()) //src.top() != dst.top() tmp.top() != dst.top()
{
if (tmp.top() != dst.top())
{
tmp.push(src.top());
src.pop();
}
else
{
tmp.pop();
dst.pop();
}
}
else if (!src.empty() && tmp.empty())
{
tmp.push(src.top());
src.pop();
}
else if (src.empty() && !tmp.empty())
{
while (tmp.top() == dst.top())
{
tmp.pop();
dst.pop();
if (dst.empty())
return true;
}
return false;
}
}
return true;
}