C语言数据结构基础--限定链表--栈的应用

   栈结构具有“后进先出”(LIFO)的特性,使得栈在程序设计中发挥重要作用。本篇文章将讨论栈应用的两个经典例子。

一,括号匹配问题

 1.算法思想

    检验算法中可设置一个栈,每读入一个括号,若是左括号,直接入栈,等相匹配的同类右括号;

若读入右括号,且与当前栈顶左括号相匹配,则将栈顶左括号出栈,否则便不合法,直接返回错误。此外,如果输入序列已经读完,而栈中仍有等待的左括号,即栈非空,或者读入一个右括号,而栈中已无相匹配的左括号,皆是不合法的。只有输入序列与栈同时为空,才说明括号完全匹配

2.算法实现

          这里我们不妨设括号运算符只有圆括号,方括号,花括号,尖括号四种,他们可以相互嵌套,如({<>}){(}或{}<>()[]等。何为合法,自然如{}[]()或[(<{}>)]之类。据此,我们得到下面算法的判断条件。

        此算法的实现主要依赖于栈的实现。我这里便以我上篇文章栈的实现中,实现的线栈来操作。此外,就是判断两个符号是否匹配,键值对的设置倒是可以便于使用,不过与其在C中再建立键值对,用哈希表,倒不如把这些括号类型直接枚举出来,用switch进行匹配。

下面便是我用来检测括号是否匹配的函数:

int Char_Match(char x) {
	switch (x) { // 输入右括号,返回其左括号对应的ASCLL值
	case ')':
		return ((int)'(');
	case ']':
		return ((int)'[');
	case '>':
		return ((int)'<');
	case '}':
		return ((int)'{');
	}
}

       之所以如此设计,是因为我前面的链栈是以int类型存储数据元素的,改的话比较麻烦(偷懒,流汗),因此将括号对应的ASCLL值存入栈中,匹配与否也拿其匹配括号的ASCLL值比较判断。

        当然,还有一点,前面我的压栈函数 void  Push_LS(Head head),只引入一个形参,也就是所要操作的栈,在运行中输入所要压入的元素,自然不方便这里的检测,所以需要进行小制衡。

void  D_Push_LS(Head head,char c) {
	
	LS* node = (Head)malloc(sizeof(LS));  // 申请新的LS空间
	node->date = (int)c;                    // 数据赋值
	node->next = head->next;              // 把之前的数据元素连在他后面
	head->next = node;                    // 把他接在头结点后面
	++head->date;                         // 数量加一 
}

        余下的就是算法的核心,也就是如何判断,其中所使用函数除了上文添改的两个,余下的就是上篇文章实现的栈的基本操作。

bool Bracket_Match(char* str) {
	Head L_B = Init_LS();
	for (int i = 0; str[i] != '\0'; ++i) { // 对字符串逐一扫描
		switch (str[i]) {
		case '(':
		case '[':
		case '<':
		case '{':
			D_Push_LS(L_B,str[i]);  // 将左括号都压入栈内
			break;					// 将右括号拿出来与栈内左括号进行配对
		case ')':
		case ']':
		case '>':
		case '}':									
			if (L_B->date == 0) {		// 如果栈已空,但下一个是右括号,说明括号并不匹配,直接返回false			
				printf("右边不对\n");	// 因为我这个是存储int类型的链栈,
				return false;			// 所以将字符转化为其相应的ASCLL值来比较
			}							// Char_Match() 函数也是将右括号对应的左括号的ASCLL值进行返回	
			else if (Char_Match(str[i]) == L_B->next->date) {
										// 左右括号匹配,那么就可以消去栈内的左括号,然后进行下一个括号的匹配
				Pop_LS(L_B);
			}
			else return false;          // 左右不匹配,那就不匹配喽,直接返回false
		}
	}
	if (L_B->date == 0) // 直到字符串遍历结束,栈也空者,经历了考验,他们是匹配的
	{
		return true;
	}
	else return false;

}

void Test099() {
	char a[13] = "(({[< > ]}))";
	if (Bracket_Match(a)) {
		printf("True!!!\n");
	}
	else printf("False!!!\n");
}

https://mp.csdn.net/mp_blog/creation/editor/133800054icon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/133800054上篇文章,栈的实现。

二,表达式求值

1.前缀式,中缀式,后缀式

        表达式求值是高级语言编译中的一个基本问题,是栈的典型应用案例。任何一个表达式都是由运算对象(Operand),运算符(Operator)和界限符(Delimiter)组成的。运算对象既可以是常数,也可以是被说明为变量或常量的标识符;运算符可以分为算术运算符,关系运算符和逻辑运算符三类;基本界限符有括号和表达式结束符等。

        表达式分为三种记录方式,根据运算符与运算对象的位置不同,分为前缀式(波兰式),中缀式,后缀式。

前缀式:运算符在所要操作数的前方。

中缀式:与我们日常写法一致,运算符在其操作数之间。

后缀式:与前缀式相反,运算符在其所要操作数的后方。

我们可以以中缀式为例,写出其对应的前后缀表达式来理解此三种表达式。

例:

        中缀式:5+6*(4+5-8/2)+5-7

        前缀式:- + + 5 * 6 - + 4 5 / 8 2 5 7

        后缀式:5 6 4 5 + 8 2 / - * + 5 + 7

        或许有人吃惊,前后缀表达式不应该让运算符在操作数前面吗,这怎么鱼龙混杂的。这就要细看前面的定义了,其所要操作的数的前或后,也就是两个相应操作数之前或之后,因此可以根据此规定,遇到运算符时,可以很容易找到他们对应的运算数。

        由于我们日常接受中缀式,一时难以适应前后缀式,但我们要知道电脑是不如我们大脑那般灵活,可以直接将中缀式中各种运算符以及界限符号所蕴含的表达式运算优先级看穿。一个括号就可以直接令他们费解了。因此可以发现我们的前后缀式,将原来中缀式的括号“溶解”了,也就是将括号所蕴含的优先级关系直接通过前后缀表达式的方式展现。

        利用计算机计算表达式,通常需要将中缀式转换为前缀式或后缀式,然后求解。

2.算法思路

        这里提供一个无括号中缀式求解算法思路:

        1.首先,我们可以建立一个运算符优先级表,然后利用F(x,y)查阅此表,输入x='+',y='*',返回'<';之类,返回值类型可以自己定义,只要可以判断两运算符的优先级关系即可。(说的通俗其实就是整个可以判断运算符优先级的操作功能就行)。

        2.其次,我们要将从表达式中提取出来的操作数与运算符分别存储在运算数栈(OVS)与运算符栈(OPTR)。(为什么如此?因为我们每次操作就是判断两个相邻运算符的优先级,然后对其前或后的两个操作数进行简单的运算操作。通过栈,弹出一个运算符,再弹俩操作数,比较方便。)

        3.自左向右扫描表达式,进行如下操作:

        遇到运算数,就将其压入OVS栈中;遇到运算符就将它与OPTR栈顶的运算符进行优先级比较:

        若当前运算符优先级大于栈顶运算符优先级,就将其压入OPTR栈中。

        若当前运算符优先级小于等于栈顶运算符优先级,就将栈顶运算符退栈出来,然后与从OVS栈中连续退出的两个运算数进行运算,然后将其运算结果压入OVS栈中。(这里需要注意我们是从左向右扫描表达式,故入栈顺序是从左向右,根据栈的定义可知,出栈顺序是从右向左,因此第一个弹出来的运算数是中缀式情况下运算符右侧的数,需要注意顺序)

        

3.算法实现

代码实现如下:

int Operataion_B(char BDS[]) {
	IS* IS = Init_IS();
	CS* CS = Init_CS();

	while (1) {
		if (BDS[i] >= '0' && BDS[i] <= '9' && BDS[i + 1] >= '0' && BDS[i + 1] <= '9') { // 两位及以上字符转化成数字存放
			int j = i;
			int sum = 0;
			while (BDS[j] >= '0' && BDS[j] <= '9') {
				sum = (BDS[j] - '0') + sum * 10;
				++j;
			}
			IS_Push(IS, sum);
			i = j - 1;

		}
		else if (BDS[i] == '=') { // 截至条件,把里面的值运算返回
			if (CS_Top(CS)) {
				while (CS_Top(CS)) {
					int R = IS_Pop(IS);
					int L = IS_Pop(IS);
					IS_Push(IS, Value_Operator(R, L, CS_Pop(CS)));
				}
			}
			return IS_Top(IS);
		}
		else if (BDS[i] >= '0' && BDS[i] <= '9') { // 个位数存放
			IS_Push(IS, BDS[i] - '0');
		}
		else if (CS_Top(CS) == NULL) { // 空 ,第一个,直接入
			CS_Push(CS, BDS[i]);
		}
		else { // 非空,比较边算边入
			if (Char_Comper(CS_Top(CS), BDS[i])) { // 前面的大,算前面,// bukezhijie压后面 这样会失去一次判断机会,所以还是不要走近道
				int R = IS_Pop(IS);
				int L = IS_Pop(IS);
				IS_Push(IS, Value_Operator(R, L, CS_Pop(CS)));
				--i; // 这里老规矩,图省事,虽然现在看来并不省事
				//CS_Push(CS, BDS[i]);
			}
			else { // 后面优先,算后面,前面不用管,此计只可解无括号之围,非长计也
				// 长远之计,在于把运算符优先级高的后方暂存,等一切结算后再清算
				//char Op = BDS[i]; // 因为要变i,只好先把符号记录下来
				//++i; // 搞下一个数
				//IS_Push(IS, Value_Operator(GetNumber(BDS) , IS_Pop(IS),Op ));
				CS_Push(CS, BDS[i]);				
			}
		}
		++i;
	}
}

        

//数值计算 L =Left,前面的数,R=Right,后面的数,注意顺序
int Value_Operator(int R, int L, char x) {
	switch (x) {
	case '+':
		return L +R;
	case '-':
		return L - R;
	case '*':
		return L * R;
	case '/':
		return L / R;
	case '%':
		return L % R;
	}
}

此代码只是实现无括号的整数运算,各位可根据此思路扩展此算法。

补充:扩展了括号与负数的运算,要扩展到实数就得改改栈的数据类型了,后面再补充;

int Operataion_D(char BDS[]) {
	IS* IS = Init_IS();
	CS* CS = Init_CS();

	while (1) {
		if (BDS[i] >= '0' && BDS[i] <= '9' && BDS[i + 1] >= '0' && BDS[i + 1] <= '9') { // 两位及以上字符转化成数字存放
			int j = i;
			int sum = 0;
			while (BDS[j] >= '0' && BDS[j] <= '9') {
				sum = (BDS[j] - '0') + sum * 10;
				++j;
			}
			IS_Push(IS, sum);
			i = j - 1;

		}
		else if (BDS[i] == '=') { // 截至条件,把里面的值运算返回
			if (CS_Top(CS)) {
				while (CS_Top(CS)) {
					int R = IS_Pop(IS);
					int L = IS_Pop(IS);
					IS_Push(IS, Value_Operator(R, L, CS_Pop(CS)));
				}
			}
			return IS_Top(IS);
		}
		else if (BDS[i] == ')') { // 截至条件,把里面的值运算返回
			if (CS_Top(CS)!='(') {
				while (CS_Top(CS) != '(') {
					if (CS_Top(CS) == '-') {
						IS_Push(IS, - IS_Pop(IS));
						CS_Pop(CS);
					}
					else {
						int R = IS_Pop(IS);
						int L = IS_Pop(IS);
						IS_Push(IS, Value_Operator(R, L, CS_Pop(CS)));
					}
				}
			}
			CS_Pop(CS);
			
		}
		else if (BDS[i] >= '0' && BDS[i] <= '9') { // 个位数存放
			IS_Push(IS, BDS[i] - '0');
		}
		else if (CS_Top(CS) == NULL) { // 空 ,第一个,直接入
			CS_Push(CS, BDS[i]);
		}
		else { // 非空,比较边算边入
			if (Char_Comper(CS_Top(CS), BDS[i])) { // 前面的大,算前面,// bu ke zhijie压后面 这样会失去一次判断机会,所以还是不要走近道
				int R = IS_Pop(IS);
				int L = IS_Pop(IS);
				IS_Push(IS, Value_Operator(R, L, CS_Pop(CS)));
				--i; // 这里老规矩,图省事,虽然现在看来并不省事
				//CS_Push(CS, BDS[i]);
			}
			else { // 后面优先,算后面,前面不用管,此计只可解无括号之围,非长计也
				// 长远之计,在于把运算符优先级高的后方暂存,等一切结算后再清算
				//char Op = BDS[i]; // 因为要变i,只好先把符号记录下来
				//++i; // 搞下一个数
				//IS_Push(IS, Value_Operator(GetNumber(BDS) , IS_Pop(IS),Op ));
				CS_Push(CS, BDS[i]);
			}
		}
		++i;
	}
}
bool Char_Comper(char a,char b) {
	if (a == '('|| b == '(') return false;
	int A = Char_Grade(a), B = Char_Grade(b);
	return A >= B;
}

好了,哥们克服懒惰,虽说就是在原代码上再改进了些(狗头),但作用范围拓展到了所有实数:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<bits2_5.h>


typedef struct Char_Stack_char
{
	char date;
	struct Char_Stack_char* next;
}CSC;

typedef struct Linked_List_Stack_char
{
	float date;
	struct Linked_List_Stack_char* next;
}ISC;

ISC* Init_ISC() {
	ISC* head = (ISC*)malloc(sizeof(ISC)); // 分配空间
	head->date = 0;                       // 存栈长度
	head->next = NULL;
	return head;
}

CSC* Init_CSC() {
	CSC* head = (CSC*)malloc(sizeof(CSC)); // 分配空间
	head->date = '0';                       // 存栈长度
	head->next = NULL;
	return head;
}
//只需要压栈,出栈,初始化

void  CSC_Push(CSC* head, char c) {

	CSC* node = (CSC*)malloc(sizeof(CSC));  // 申请新的LS空间
	node->date = c;                    // 数据赋值
	node->next = head->next;              // 把之前的数据元素连在他后面
	head->next = node;                    // 把他接在头结点后面

}

void ISC_Push(ISC* head, float x) {
	ISC* node = (ISC*)malloc(sizeof(ISC));  // 申请新的LS空间
	node->date = x;                    // 数据赋值
	node->next = head->next;              // 把之前的数据元素连在他后面
	head->next = node;                    // 把他接在头结点后面
	++head->date;
}

char CSC_Pop(CSC* head) {
	if (head->next == NULL) {
		return NULL;
	}
	CSC* temp = head->next; // 为了最后释放此空间,留个指针指向该空间
	char Top = head->next->date;
	head->next = temp->next;
	free(temp);
	return Top;
}

float ISC_Pop(ISC* head) {
	if (head->next == NULL) {
		return 0;
	}
	ISC* temp = head->next; // 为了最后释放此空间,留个指针指向该空间
	float Top = head->next->date;
	head->next = temp->next;
	free(temp);
	--head->date;
	return Top;
}
//
float ISC_Top(ISC* head) {
	if (head->next == NULL) {
		return 0;
	}
	return head->next->date;
}

char CSC_Top(CSC* head) {
	if (head->next == NULL) {
		return 0;
	}
	return head->next->date;
}

//表达式运算

//运算符优先级判断
//给他个值表示优先级
int  Char_Grade_char(char a) {
	switch (a) {
	case '+':
	case '-':
		return 0;
	case '*':
	case '/':
	case '%':
		return 1;
	}
}

bool Char_Comper_char(char a, char b) {
	if (a == '(' || b == '(') return false;
	int A = Char_Grade_char(a), B = Char_Grade_char(b);
	return A >= B;
}

//数值计算 L =Left,前面的数,R=Right,后面的数,注意顺序
float Value_Operator_char(float R, float L, char x) {
	switch (x) {
	case '+':
		return L + R;
	case '-':
		return L - R;
	case '*':
		return L * R;
	case '/':
		return L / R;
	/*case '%':
		return L % R;*/
	}
}

const int Max = 40;
int Math_TQ_char(char a, char b) {
	return (a - 48) * 10 + (b - 48);
}

float Operataion(char BDS[]) {
	ISC* IS = Init_ISC();
	CSC* CS = Init_CSC();
	int i = 0;
	while (1) {
		if (BDS[i] >= '0' && BDS[i] <= '9' && BDS[i + 1] >= '0' && BDS[i + 1] <= '9') { // 两位及以上字符转化成数字存放
			int j = i;
			float sum = 0;
			while (BDS[j] >= '0' && BDS[j] <= '9') {
				sum = (BDS[j] - '0') + sum * 10;
				++j;
			}
			if (BDS[j] == '.') {  // 如果是小数
				++j;
				float chushu = 10;
				while (BDS[j] >= '0' && BDS[j] <= '9') {
					sum = (BDS[j] - '0') / chushu + sum;
					++j;
					chushu *= 10;
				}
			}
			ISC_Push(IS, sum);
			i = j - 1;

		}
		else if (BDS[i] >= '0' && BDS[i] <= '9' && BDS[i + 1] == '.') { // 一位有小数点数字符转化成数字存放
			int j = i+2;
			float sum = BDS[i]-'0';
			float chushu = 10;
			while (BDS[j] >= '0' && BDS[j] <= '9') {
				sum = (BDS[j] - '0')/chushu + sum ;
				++j;
				chushu *= 10;
			}
			ISC_Push(IS, sum);
			i = j - 1;

		}
		else if (BDS[i] == '=') { // 截至条件,把里面的值运算返回
			if (CSC_Top(CS)) {
				while (CSC_Top(CS)) {
					float R = ISC_Pop(IS);
					float L = ISC_Pop(IS);
					ISC_Push(IS, Value_Operator_char(R, L, CSC_Pop(CS)));
				}
			}
			return ISC_Top(IS);
		}
		else if (BDS[i] == ')') { // 截至条件,把里面的值运算返回
			if (CSC_Top(CS) != '(') {
				while (CSC_Top(CS) != '(') {
					if (CSC_Top(CS) == '-') {
						ISC_Push(IS, -ISC_Pop(IS));
						CSC_Pop(CS);
					}
					else {
						int R = ISC_Pop(IS);
						int L = ISC_Pop(IS);
						ISC_Push(IS, Value_Operator_char(R, L, CSC_Pop(CS)));
					}
				}
			}
			CSC_Pop(CS);

		}
		else if (BDS[i] >= '0' && BDS[i] <= '9') { // 个位数存放
			ISC_Push(IS, BDS[i] - '0');
		}
		else if (CSC_Top(CS) == NULL) { // 空 ,第一个,直接入
			CSC_Push(CS, BDS[i]);
		}
		else { // 非空,比较边算边入
			if (Char_Comper_char(CSC_Top(CS), BDS[i])) { // 前面的大,算前面,// bu ke zhijie压后面 这样会失去一次判断机会,所以还是不要走近道
				float R = ISC_Pop(IS);
				float L = ISC_Pop(IS);
				ISC_Push(IS, Value_Operator_char(R, L, CSC_Pop(CS)));
				--i; // 这里老规矩,图省事,虽然现在看来并不省事
				//CS_Push(CS, BDS[i]);
			}
			else { // 后面优先,算后面,前面不用管,此计只可解无括号之围,非长计也
				// 长远之计,在于把运算符优先级高的后方暂存,等一切结算后再清算
				//char Op = BDS[i]; // 因为要变i,只好先把符号记录下来
				//++i; // 搞下一个数
				//ISC_Push(ISC, Value_Operator(GetNumber(BDS) , ISC_Pop(ISC),Op ));
				CSC_Push(CS, BDS[i]);
			}
		}
		++i;
	}
}

int main() {
	char BDS[Max];
	memset(BDS, 0, sizeof(BDS));
	printf("请输入表达式,遇到'='结束\n");
	scanf("%s", BDS);
	printf("%.4f", Operataion(BDS));
	return 12;
}

括号的处理可以特殊定义,如前括号存入栈,遇到其相匹配的后括号,就可以将括号中元素取出来运算,结束后将他们的值压入OVS中,而实数就可以在获取数字的地方扩展。

本文就主要介绍栈的应用,令大家熟悉栈的操作,更多细节。如有更好意见,望各位不吝赐教,感谢。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值