数据结构入门3-2(栈与队列—例题分析)

目录

 注:

例1:数制的转换

例2:括号匹配的检测

例3:表达式求值

例4:舞伴问题


 注:

        本笔记参考:《数据结构(C语言版)》


例1:数制的转换

【要求】

        对于任何一个非负十进制数,打印输出与其等值的八进制数。

【分析要求】

  设待转换的十进制整数为 N 。

  1. 进栈:将 (N % 8) 所得值依次进栈;
  2. 出栈:将栈中的值(八进制数)依次出栈。

【代码】

|||  注:代码所使用栈为链栈。

函数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;
	}
}

【算法分析】

        该算法的时间和空间复杂度都为O(log_{8}n)

         值得一提的是,上述的操作通过数组也可以做到。这里使用堆栈的方式完成,有几个优点:

  • 简化了程序设计的问题;
  • 划分了关注层次;
  • 缩小了思考范围。

例2:括号匹配的检测

【要求】

        检验表达式中的括号是否正确匹配,要求:

  • 如果匹配,返回true;
  • 如果不匹配,返回false。

【分析要求】

        该算法使用一个栈,规定:

  1. 当读入一个左括号,直接入栈;
  2. 当读入一个右括号,与之前入栈的左括号进行匹配。如果成功,将左括号出栈。

        另一方面,考虑无法匹配的情况,如:

  • (( )[ ])) —— 最右边的右括号无法匹配;
  • [([ ]) —— 最左边的左括号无法匹配;
  • (( )] —— 最边缘的两个括号不匹配。

【代码】

|||  注:代码所使用栈为链栈。

  规定:表达式以 # 结尾。

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:表达式求值

【要求】

        把一个表达式翻译成正确求值的一串机器指令序列,或者直接对表达式求值。(使用“算符有优先法”)

  算符优先法:根据算术四则运算规则确定的运算优先关系,实现对表达式的编译或解释执行。

【分析要求】

        接下来只讨论其中的简单算术表达式(只包含加、减、乘、除)的求值问题。

  四则运算遵循的规则:

  1. 先乘除,后加减;
  2. 从左到右;
  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:舞伴问题

【要求】

        将出场的舞伴进行配对,先入队的男士或女士先出队配成舞伴。

【分析要求】

        男士与女士需要配对舞伴,故需要分别设置两个队列用来存储男士与女士的信息。

        配对要求:

  1. 将两个队列的队头的成员进行配对,直到某一队列变空;
  2. 若配对结束时,某一队列仍有未完成配对者,输出队头成员(此人将在下轮舞曲中找到舞伴)

【代码】

--- 有关数据结构的定义 ---

//舞蹈者的个人信息
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队列的长度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值