详解顺序栈

172 阅读11分钟

详解顺序栈

定义

顺序栈是一种特殊的线性表数据结构,它利用了顺序存储结构的特点,即采用地址连续的存储空间(数组)依次存储栈中的数据元素。

顺序栈的元素依次存放在一个一维数组中,下标小的一端作为栈底。用一个变量记录栈顶位置,称“栈顶指针”。进栈操作是将元素存放在栈顶后面一个位置,栈顶往后移;出栈操作是删除栈顶元素,栈顶往前移。

特点

顺序栈的特点是先进后出(LIFO),即最后进入栈的元素最先被删除。顺序栈在逻辑上是线性结构,可以采用顺序存储结构实现,也可以采用链式存储结构实现。顺序栈在操作上采用传地址的方式把出栈的值传给*地址。

栈的运算

初始化

分配内存来存储数据,检查内存是否成功分配,设置栈的最大容量,设置栈顶指针为-1。

/* 1. 初始化 */
int init(SeqStack *S, int MaxSize)
{
	/*申请内存空间*/
	S->data = (DataType*)malloc(sizeof(DataType)*MaxSize);

	if(!S->data)
	{
		printf("内存申请错误,初始化失败![10001]\n");
		return 10001;
	}
	S->maxsize = MaxSize;
	S->top = -1;
	return 0;
}

进(入)栈

这个函数是用来将数据放入顺序栈中的。如果栈已满,它会打印错误信息并返回一个错误码。如果栈还有空间,它会将数据放入栈顶,然后将栈顶指针向上移动一个位置。如果操作成功,它会返回0。

/* 2. 进(入)栈 */
int push(SeqStack *S, DataType x)
{
	/*是否满?*/
	if(full(S))
	{
		printf("栈已满!10002\n");
		return 10002;
	}

	S->top++;			/*移动指针*/
	S->data[S->top] = x;/*放入数据*/
	return 0; /*OK*/
}

出栈

这个函数是用来从顺序栈中取出元素的。如果栈为空,它会打印错误信息并返回一个错误码。如果栈非空,它会将栈顶元素赋值给x,然后移除栈顶元素(通过将栈顶指针减一),并返回0表示操作成功。

/* 3. 出栈 */
int pop(SeqStack *S, DataType *x)
{
	/*是否空?*/
	if(empty(S))
	{
		printf("栈为空!10003\n");
		return 10003;
	}

	*x = S->data[S->top];	/*栈顶元素赋值给x*/
	S->top--;				/*移动栈顶指针*/	

	return 0;
}

取栈顶元素

这个函数是用来获取顺序栈的栈顶元素的。如果栈为空,它会打印错误信息并返回一个错误码。如果栈非空,它会将栈顶元素赋值给x,但不移除该元素,然后返回0表示操作成功。

/* 4. 取栈顶元素 */
int get_top(SeqStack *S, DataType *x)
{
	/*是否空?*/
	if(empty(S))
	{
		printf("栈为空!10003\n");
		return 10003;
	}

	*x = S->data[S->top];	/*栈顶元素赋值给x*/

	return 0;
}

栈为空?

这个函数是用来检查一个顺序栈是否为空的。如果栈顶指针为-1,它就返回1,否则返回0。

/* 5. 栈为空?*/
int empty(SeqStack *S)
{
   return (S->top == -1)?1:0;
}


栈满

这个函数是用来检查一个顺序栈是否已满的。如果栈顶指针到达了栈的最大容量减1(因为栈顶指针是从0开始计数的),它就返回1,否则返回0。

/* 6. 栈满?*/
int full(SeqStack *S)
{
   return (S->top == S->maxsize - 1)?1:0;
}

销毁

这个函数是用来释放顺序栈的数据数组所占用的内存的。它通过调用free函数来释放内存,并返回0表示成功。

/* 7. 销毁*/
int destroy(SeqStack *S)
{
   free(S->data);
   return 0;
}

栈的应用

十进制转换为二进制

这段代码是一个将十进制数转换为二进制的函数。它使用了一个栈结构(SeqStack)来处理转换过程。下面是代码的简要解释:

  1. 定义一个名为d_to_b的函数,该函数接受一个整数d作为输入。

  2. 定义一个SeqStack类型的变量S和一个整数变量b

  3. 调用init函数初始化栈S,指定其容量为32。

  4. 使用while循环,当d不为0时执行转换过程:

    • 通过取模运算获取d的余数,将余数推进栈S中。
    • d除以2,更新d的值。
  5. 使用while循环,当栈不为空时执行出栈操作:

    • 使用pop函数将栈顶元素弹出并存储在变量b中。
    • 打印出栈顶元素(即二进制数的每一位)。
  6. 调用destroy函数销毁栈S

/*十进制转换为二进制*/
int d_to_b(int d)
{
	SeqStack S;
	int b;

	/*初始化栈*/
	init(&S,32);

	/*d不为0,余数进栈*/
	while(d)
	{
		push(&S, d % 2);
		d /= 2;
	}
	
	/*依次出栈*/
	while(!empty(&S))
	{
		pop(&S,&b);
		printf("%d", b);
	}

	/*销毁栈*/
	destroy(&S);
}

后缀表达式计算

这段代码是一个后缀表达式计算器,也被称为逆波兰表示法(Reverse Polish Notation,RPN)。在后缀表达式中,操作符位于相应操作数的后面,例如“56+”表示“5+6”。与中缀表达式不同,后缀表达式不使用括号来表示优先级,因此计算过程更为简洁。

代码的执行流程如下:

  1. 初始化一个空栈和一些变量。

  2. 从用户输入中读取后缀表达式。

  3. 遍历后缀表达式的每个字符,根据字符类型执行相应的操作:

    • 如果是数字字符(0-9),将其转换为对应的数值,并将其压入栈中。
    • 如果是加法运算符(+),从栈中弹出两个操作数,进行加法运算,并将结果压入栈中。
    • 如果是减法运算符(-),从栈中弹出两个操作数,进行减法运算,并将结果压入栈中。
    • 如果是乘法运算符(*),从栈中弹出两个操作数,进行乘法运算,并将结果压入栈中。
    • 如果是除法运算符(/),从栈中弹出两个操作数,进行除法运算,并将结果压入栈中。
  4. 最后,从栈中弹出一个操作数,即为最终的计算结果。

  5. 输出计算结果。

  6. 销毁栈。

/*后缀表达式计算*/
int expression()
{
	SeqStack S;
	int i;
	int op1, op2;	
	int x;

	char exp[20]; /*后缀表达式*/

	init(&S, 10);

	printf("请输入一个后缀表达式(eg. 56+):");
	scanf("%s", exp);

	for(i=0;i<strlen(exp);i++)
	{
		switch(exp[i])
		{
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			/*入栈*/
			push(&S, exp[i]-48);
			break;
		case '+':
			/*出2个*/
			pop(&S, &op1);
			pop(&S, &op2);
			x = op1 + op2;
			push(&S, x);
			break;
		case '-':
			/*出2个*/
			pop(&S, &op1);
			pop(&S, &op2);
			x = op2 - op1;
			push(&S, x);
			break;

		case '*':
			pop(&S, &op1);
			pop(&S, &op2);
			x = op1 * op2;
			push(&S, x);
			break;
		case '/':
			pop(&S, &op1);
			pop(&S, &op2);
			x = op2 * op1;
			push(&S, x);
			break;
		}
	}
	pop(&S, &x);
	printf("计算结果为:%s = %d\n", exp, x);
	destroy(&S);
}

栈的实现

完整代码

项目结构


    main.c
    seqstack.c
    seqstack.h

项目文件

main.c

#include <stdio.h>
#include <string.h>

#include "seqstack.h"

/*十进制转换为二进制*/
int d_to_b(int d);

/*后缀表达式计算*/
int expression();

int main(int argc, char* argv[])
{
	SeqStack S;

	int cmd;
	int d;

	DataType x;
	int maxsize;
	char yn;

	
	do
	{
		printf("---------顺序栈演示程序-----------\n");
		printf(" 1. 初始化\n");
		printf(" 2. 入栈\n");
		printf(" 3. 出栈\n");
		printf(" 4. 取栈顶元素\n");
		printf(" 5. 栈是否空?\n");
		printf(" 6. 栈是否满?\n");
		printf(" 7. 销毁栈\n");
		printf(" 8. 栈的应用\n");
          printf(" 9. 帮助\n");
		printf("请选择(0~9,0退出):");
		scanf("%d", &cmd);
		switch(cmd)
		{
		case 1:
			printf("请输入栈的最大存储空间(MaxSize):");
			scanf("%d", &maxsize);
			if(!init(&S, maxsize))
			{
				printf("栈已初始化!\n");
			}
			break;
		case 2:
			printf("请输入入栈元素:x=");
			scanf("%d", &x);
			if(!push(&S, x))
			{
				printf("元素【%d】已入栈!\n", x);

			}
			break;
		case 3:
			printf("确定要出栈(出栈后数据不可恢复,y|n,n)?");
//			flushall();
			getchar();
			scanf("%c", &yn);
			if(yn == 'y' || yn == 'Y')
			{
				if(!pop(&S, &x))
				{
					printf("栈顶元素【%d】已出栈!\n", x);
				}
			}

			break;
		case 4:
			if(!get_top(&S, &x)){
				printf("栈顶元素【%d】\n", x);
			}

			break; 
		case 5:
			if(!empty(&S)){
				printf("栈不空!\n");
			} else{
				printf("栈是空! \n");
			}
			break;
		case 6:
			if(full(&S)){
				printf("栈已满!\n");
			} else{
				printf("栈没有满,还可以入栈哦!\n");
			} 
			break;	
		case 7:
			if(empty(&S)){
				printf("栈是空的哦!\n");
				break;
			} 
			printf("确定要出栈(出栈后数据不可恢复,y|n)?\n");
			empty(&S);
			getchar(); 
			scanf("%c", &yn);
			if(yn == 'y' || yn == 'Y')
			{
				if(!destroy(&S))
				{
					printf("栈已经全都销毁!\n");
				}
			}
			break;
		case 8:
			do
			{
				printf("----------8.栈的应用------------\n");
				printf(" 1. 十进制转换为二进制\n");
				printf(" 2. 后缀表达式计算\n");			
				printf(" 0. 返回\n");
				printf("请选择:");
				scanf("%d", &cmd);

				if(cmd == 1)
				{
					printf("请输入一个十进制数:");
					scanf("%d", &d);
					printf("二进制为:");
					d_to_b(d);
					printf("\n");
				}

				if(cmd == 2)
				{
					expression();
				}
			}while(cmd!=0);
			cmd = -1;
			break;
           case 9:
		    printf("本程序为顺序栈的演示程序,由付雄宇靓仔设计开发,\n如有问题可以一起学习交流讨论!\n");
		
		}

	}while(cmd!=0);

	return 0;
}


/*十进制转换为二进制*/
int d_to_b(int d)
{
	SeqStack S;
	int b;

	/*初始化栈*/
	init(&S,32);

	/*d不为0,余数进栈*/
	while(d)
	{
		push(&S, d % 2);
		d /= 2;
	}
	
	/*依次出栈*/
	while(!empty(&S))
	{
		pop(&S,&b);
		printf("%d", b);
	}

	/*销毁栈*/
	destroy(&S);
}


/*后缀表达式计算*/
int expression()
{
	SeqStack S;
	int i;
	int op1, op2;	
	int x;

	char exp[20]; /*后缀表达式*/

	init(&S, 10);

	printf("请输入一个后缀表达式(eg. 56+):");
	scanf("%s", exp);

	for(i=0;i<strlen(exp);i++)
	{
		switch(exp[i])
		{
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			/*入栈*/
			push(&S, exp[i]-48);
			break;
		case '+':
			/*出2个*/
			pop(&S, &op1);
			pop(&S, &op2);
			x = op1 + op2;
			push(&S, x);
			break;
		case '-':
			/*出2个*/
			pop(&S, &op1);
			pop(&S, &op2);
			x = op2 - op1;
			push(&S, x);
			break;

		case '*':
			pop(&S, &op1);
			pop(&S, &op2);
			x = op1 * op2;
			push(&S, x);
			break;
		case '/':
			pop(&S, &op1);
			pop(&S, &op2);
			x = op2 / op1;
			push(&S, x);
			break;
		}
	}
	pop(&S, &x);
	printf("计算结果为:%s = %d\n", exp, x);
	destroy(&S);
}

seqstack.h

/*
	seqstack.h
	顺序栈
*/

typedef int DataType;


typedef struct
{
	DataType *data; /* 堆空间 */
	int maxsize;
	int top;		/* 栈顶指针 */
}SeqStack;


/* 1. 初始化 */
int init(SeqStack *S, int MaxSize);

/* 2. 进(入)栈 */
int push(SeqStack *S, DataType x);

/* 3. 出栈 */
int pop(SeqStack *S, DataType *x);

/* 4. 取栈顶元素 */
int get_top(SeqStack *S, DataType *x);

/* 5. 栈为空?*/
int empty(SeqStack *S);

/* 6. 栈满?*/
int full(SeqStack *S);

/* 7. 销毁*/
int destroy(SeqStack *S);

seqstack.c

/*
	seqstack.c
*/
#include <stdio.h>
#include <malloc.h>
#include "seqstack.h"


/* 1. 初始化 */
int init(SeqStack *S, int MaxSize)
{
	/*申请内存空间*/
	S->data = (DataType*)malloc(sizeof(DataType)*MaxSize);

	if(!S->data)
	{
		printf("内存申请错误,初始化失败![10001]\n");
		return 10001;
	}
	S->maxsize = MaxSize;
	S->top = -1;
	return 0;
}


/* 2. 进(入)栈 */
int push(SeqStack *S, DataType x)
{
	/*是否满?*/
	if(full(S))
	{
		printf("栈已满!10002\n");
		return 10002;
	}

	S->top++;			/*移动指针*/
	S->data[S->top] = x;/*放入数据*/
	return 0; /*OK*/
}

/* 3. 出栈 */
int pop(SeqStack *S, DataType *x)
{
	/*是否空?*/
	if(empty(S))
	{
		printf("栈为空!10003\n");
		return 10003;
	}

	*x = S->data[S->top];	/*栈顶元素赋值给x*/
	S->top--;				/*移动栈顶指针*/	

	return 0;
}

/* 4. 取栈顶元素 */
int get_top(SeqStack *S, DataType *x)
{
	/*是否空?*/
	if(empty(S))
	{
		printf("栈为空!10003\n");
		return 10003;
	}

	*x = S->data[S->top];	/*栈顶元素赋值给x*/

	return 0;
}

/* 5. 栈为空?*/
int empty(SeqStack *S)
{
	return (S->top == -1)?1:0;
}

/* 6. 栈满?*/
int full(SeqStack *S)
{
	return (S->top == S->maxsize - 1)?1:0;
}


/* 7. 销毁*/
int destroy(SeqStack *S)
{
	free(S->data);
	return 0;
}

运行结果

QQ截图20231021152212.jpg

QQ截图20231021152723.jpg

Snipaste_2023-10-19_23-40-00.png

小结

顺序栈是一种重要的数据结构,它遵循 LIFO(Last In First Out,后进先出)的原则。

  1. 定义:栈是一种有序的数据集合,其中数据项按照添加和删除的顺序排列。栈的特性是只能在栈顶进行插入或删除操作。
  2. 操作:栈主要包含两种操作,即 push 和 pop。push 操作在栈顶添加一个元素,而 pop 操作则删除栈顶的元素。除此之外,有些栈实现还提供了其他操作,如 (查看栈顶元素但不删除)等。
  3. 应用:栈在许多领域都有广泛的应用,如程序中的函数调用和递归、表达式求值、括号匹配、深度优先搜索等。
  4. 实现:栈可以通过数组、链表、动态数组等不同的数据结构实现。其中,数组实现较为简单,但可能造成空间浪费;链表实现较为灵活,但需要额外的空间存储节点;动态数组则结合了数组和链表的优点。
  5. 性质:栈具有后进先出的性质,即最后进入栈的元素最先被删除。这个性质在很多情况下非常有用,但也需要谨慎使用,以避免出现逻辑错误。
  6. 常见问题:在使用栈时,需要注意一些常见问题,如溢出(当栈满时无法继续添加元素)、下溢(当栈空时无法继续删除元素)等。这些问题可以通过设置大小限制、检查栈状态等方式避免。

总之,顺序栈是一种非常有用的数据结构,掌握它的基本概念、性质和实现方式,可以帮助我们在编程中解决很多问题。

参考文献

文心一言 (baidu.com)