目录
注:
本笔记参考:《数据结构(C语言版)》
例1:数制的转换
【要求】
对于任何一个非负十进制数,打印输出与其等值的八进制数。
【分析要求】
设待转换的十进制整数为 N 。
- 进栈:将 (N % 8) 所得值依次进栈;
- 出栈:将栈中的值(八进制数)依次出栈。
【代码】
||| 注:代码所使用栈为链栈。
函数StackEmpty —— 检测 栈S 是否为空。
Status StackEmpty(LinkStack &S)
{
if (S)
return false;
return true;
}
函数conversion —— 进行数制的转换。
void Conversion(int N)
{
using namespace std;
StackNode* S;
InitStack(S); //初始化空栈
while (N)
{
Push(S, (N % 8)); //将 N%8 的结果压入栈S
N = N / 8; //更新N
}
while (!StackEmpty(S)) //当栈S非空时下,循环
{
ElemType e;
Pop(S, e); //弹出栈顶元素
cout << e;
}
}
【算法分析】
该算法的时间和空间复杂度都为。
值得一提的是,上述的操作通过数组也可以做到。这里使用堆栈的方式完成,有几个优点:
- 简化了程序设计的问题;
- 划分了关注层次;
- 缩小了思考范围。
例2:括号匹配的检测
【要求】
检验表达式中的括号是否正确匹配,要求:
- 如果匹配,返回true;
- 如果不匹配,返回false。
【分析要求】
该算法使用一个栈,规定:
- 当读入一个左括号,直接入栈;
- 当读入一个右括号,与之前入栈的左括号进行匹配。如果成功,将左括号出栈。
另一方面,考虑无法匹配的情况,如:
- (( )[ ])) —— 最右边的右括号无法匹配;
- [([ ]) —— 最左边的左括号无法匹配;
- (( )] —— 最边缘的两个括号不匹配。
【代码】
||| 注:代码所使用栈为链栈。
规定:表达式以 # 结尾。
Status Match()
{
using namespace std;
StackNode* S;
InitStack(S); //初始化空栈
bool flag = true; //设置标记,用以表示匹配结果、控制循环和返回结果
ElemType ch = 0;
cin >> ch; //读入第一个字符
while (ch != '#' && flag)
{
switch (ch)
{
case '[' || '(':
Push(S, ch);
break;
case ')':
if (!StackEmpty(S) && GetTop(S) == '(') //若栈非空,比起栈顶元素为“(”
Pop(S, ch);
else
flag = 0; //匹配失败,改变标记值
break;
case ']':
if (!StackEmpty(S) && GetTop(S) == '[') //若栈非空,比起栈顶元素为“[[”
Pop(S, ch);
else
flag = 0; //匹配失败,改变标记值
break;
}
cin >> ch; //继续读入下一个字符
}
if (StackEmpty(S) && flag) //匹配成功
return true;
else //匹配失败
return false;
}
【算法分析】
设表达式的长度为n。
- 空间复杂度为O(n):此算法从头到尾扫描了表达式的每个字符。
- 时间复杂度为O(n):此算法运行时占用的辅助空间大小取决于S栈,栈S的空间大小 ≤ n。
例3:表达式求值
【要求】
把一个表达式翻译成正确求值的一串机器指令序列,或者直接对表达式求值。(使用“算符有优先法”)
算符优先法:根据算术四则运算规则确定的运算优先关系,实现对表达式的编译或解释执行。
【分析要求】
接下来只讨论其中的简单算术表达式(只包含加、减、乘、除)的求值问题。
四则运算遵循的规则:
- 先乘除,后加减;
- 从左到右;
- 先括号内,后括号外。
由四则运算规则,有以下规定:
- 先进行乘除运算,后进行加减运算;
- 运算遵循左结合性——当两个运算符优先级相同时,先出现的运算符优先级高;
- 括号内的优先级高于括号外。
【代码】
||| 注:代码所使用栈为链栈。
规定:表达式以“#”开始,以“#”结束,则表达式求值即为 “#” = “#”。
函数In —— 检测当前字符是否是运算符
bool In(char ch)
{
if (ch >= '0' && ch <= '0' + 9) //不是运算符的情况
return false;
else //是运算符的情况
return true;
}
函数Precede —— 判断两个操作符之间的优先级
char Precede(char x, char y)
{
if (x == '+' || x == '-')
{
if (y == '+' || y == '-' || y == ')' || y == '#')
return '>';
else
return '<';
}
else if (x == '*' || x == '\\')
{
if (y == '(')
return '<';
else
return '>';
}
else if (x == '(')
{
if (y == ')')
return '=';
else
return '<';
}
else if (x == ')')
{
if (y != '(')
return '>';
}
else
{
if (y == '#')
return '=';
else if (y != ')')
return '<';
}
}
函数Operate —— 进行二元计算
ElemType Operate(ElemType a, ElemType theta, ElemType b)
{
if (theta == '+')
return b + a - '0'; //(a - '0') + (b - '0') + '0'
else if (theta == '-')
return b - a + '0'; //(a - '0') - (b - '0') + '0'
else if (theta == '*')
return ((b - '0') * (a - '0')) + '0';
else
return ((b - '0') / (a - '0')) + '0';
}
函数evaExp —— 运行表达式求值的函数
char evaExp()
{
using namespace std;
StackNode* OPND;
InitStack(OPND); //初始化OPND栈——寄存运算符
StackNode* OPTR;
InitStack(OPTR); //初始化OPTR栈——寄存操作数和运算结果
Push(OPTR, '#'); //将表达式起始符“#”压入OPTR栈
ElemType ch = 0;
cin >> ch;
while (ch != '#' || GetTop(OPTR) != '#') //表达式没有扫描完毕或者OPTR的栈顶元素不为“#”
{
if (!In(ch)) //ch不是运算符的情况——进入OPND栈
{
Push(OPND, ch);
cin >> ch;
}
else
{
ElemType theta = 0; //初始化,准备存储运算符
ElemType a = 0, b = 0; //初始化,准备存储运算数
switch (Precede(GetTop(OPTR), ch))
{
case '<': //ch内存储运算符的优先级较高,当前ch值入栈
Push(OPTR, ch);
cin >> ch;
break;
case '>':
Pop(OPTR, theta); //弹出OPTR栈顶的运算符
Pop(OPND, a); //弹出OPND栈顶的两个运算数
Pop(OPND, b);
Push(OPND, Operate(a, theta, b)); //将运算结果压入OPND栈
break; //由于此处没有输入新的ch值,ch内存储的值将进入下个循环,再次判断
case '=': //OPTR的栈顶元素是“(”,且“)”
ElemType x = 0;
Pop(OPTR, x); //弹出OPTR栈顶的“(”,读入下一个字符
cin >> ch;
break;
}
}
}
return GetTop(OPND); //OPND栈顶元素即为表达式求值结果
}
由于上述OPND是一个字符栈,所以在计算过程中能够使用的操作数也只能是一位,如果要进行多位数的运算,可以改变OPND栈的大小。
【算法分析】
- 空间复杂度为O(n):此算法从头到尾扫描了表达式的每个字符。
- 时间复杂度为O(n):此算法运行时占用的辅助空间大小取决于OPTR栈和OPND栈,这两个栈的空间大小之和 ≤ n。
例4:舞伴问题
【要求】
将出场的舞伴进行配对,先入队的男士或女士先出队配成舞伴。
【分析要求】
男士与女士需要配对舞伴,故需要分别设置两个队列用来存储男士与女士的信息。
配对要求:
- 将两个队列的队头的成员进行配对,直到某一队列变空;
- 若配对结束时,某一队列仍有未完成配对者,输出队头成员(此人将在下轮舞曲中找到舞伴)。
【代码】
--- 有关数据结构的定义 ---
//舞蹈者的个人信息
typedef struct
{
char name[20]; //姓名
char sex; //性别,规定‘F’代表女性,‘M’代表男性
}Person;
//队列的顺序存储结构
#define MAXQSIZE 100
typedef struct
{
Person* base; //队列中的数据类型是Person
int front; //头指针
int rear; //尾指针
}SqQueue;
SqQueue Mdancers, Fdancers; //分别存放男士和女士入队者
----------------------------------
本例题使用循环队列,具体可见数据结构入门3-1。
函数EmptyQueue —— 检测队列是否为空
Status QueueEmpty(SqQueue Q)
{
if (Q.front == Q.rear)
return true;
else
return false;
}
函数DancePartner —— 分配舞伴
void DancePartner(Person dancer[], int num)
{//结构数组dancer中存放舞者的信息,num是跳舞的人数
using namespace std;
InitQueue(Mdancers);
InitQueue(Fdancers);
int i = 0;
Person p;
for (i = 0; i < num; i++)
{
p = dancer[i];
if (p.sex == 'F') //插入女队
EnQueue(Mdancers, p);
else //插入男队
EnQueue(Fdancers, p);
}
cout << "即将跳舞的两位舞伴是: \n";
while (!QueueEmpty(Mdancers) && !QueueEmpty(Fdancers))
{
DeQueue(Fdancers, p); //女士出队
cout << p.name << " ";
DeQueue(Mdancers, p); //男士出队
cout << p.name << endl;
}
if (!QueueEmpty(Fdancers)) //女士队列非空,输出队头女士的姓名
{
p = GetHead(Fdancers);
cout << "将在下一曲开始时第一位获得舞伴的女士是: " << p.name << endl;
}
else if (!QueueEmpty(Mdancers)) //男士队列非空,输出队头男士的姓名
{
p = GetHead(Mdancers);
cout << "将在下一曲开始时第一位获得舞伴的男士是: " << p.name << endl;
}
}
【算法分析】
设舞者总人数为n。
- 时间复杂度为O(n);
- 空间复杂度为O(n):空间复杂度取决于Mdancers队列和Fdancers队列的长度。