数据结构之栈和队列
由于篇幅问题,笔者将代码放在了文章的最后,读者可自行摘取。
①栈
栈(stack)是限定 仅在表尾进行插人或删除操作的线性表。因此,对栈来说,表尾
端有其特殊含义,称为栈顶(top),相应地,表头端称为栈底(bottom)。不含元素的空表称"
为空栈。
假设栈S=(a1,a2...,an,),则称as为栈底元素,a。为栈顶元素。栈中元素按a1,
a2,..,an的次序进栈,退栈的第-一个元素应为栈顶元素。换句话说,栈的修改是按后进
先出的原则进行的(如图3.1(a)所示)。因此,栈又称为后进先出(lastinfirstout)的线
性表(简称LIFO结构),它的这个特点可用图3.1(b)所示的铁路调度站形象地表示。
抽象数据类型栈的定义
栈的基本操作除了在栈顶进行插人或删除外,还有栈的初始化、判空及取栈顶元素
等,下面给出栈的抽象数据类型的定义:
栈的表示和实现
栈的顺序存储大概就和数组类似,读者只需要再其上记录一下栈顶元素的位置即可(可以使用结构体数组)。下面我们重点讲解链式存储的实现。
下面是链式存储中的结构体的定义:
*struct stack{
int value; // 栈内的数值
struct stack next;
};
int num
InitList(); // 创造一个空栈
对于链式存储来说,就是建立一个value值为特殊值(比如0,代表无元素),和next为null的单一结构体,并且返回头指针。这里定义head为stack的头指针,全局变量num用来记录stack的长度。
DestroyStack(); // 摧毁栈
使用malloc函数包里的free函数即可——free(head)
stack *ClearStack(); // 将head置为空栈
把head的next的值赋为null,即删除了之后的所以栈元素。
IfEmpty(); // 判断是否为空栈
即只需要判断num的值即可,若为0则为空栈,反之亦然。
StackLength(); // 返回栈的长度
返回num的长度即可
GetTop(); // 返回栈顶元素,如果栈为空则返回0
返回栈顶元素,即返回p->next = null 时的value值
Push(); // 进栈element
增加一个结构体数据,并将它连接在stack的最后
Pop(); // 若栈不空,删除栈顶元素
返回栈顶元素,即返回p->next = null 时的value值,然后把这个末尾的指针删去
下面是代码运行的结果截图
②栈的应用举例
表达式求值
一、表达式求值的规则
表达式求值是程序设计语言编译中的一个基本问题。它的实现就是对“栈”的典型应用。
首先了解算术四则运算的运算规则:
(1)先乘除,后加减。
(2)从左到右计算
(3)先算括号内,再算括号外
因此,下面这个算数表达式的计算顺序应为:
4 + 2 * 3 - 10 / 5
= 4 + 6 - 10 / 5
= 10 - 10 / 5
= 10 - 2
= 8
任何一个表达式都由操作数(operand)、运算符(operator)和界定符组成:
①操作数即可以是常量,也可以是被说明为变量或常量的标识符。
②运算符可以分为算术运算,关系运算和逻辑运算符。
③界定符有左右括号和结束符等。
为了叙述的简洁,我们仅讨论简单算数表达式的求值问题,这种表达式只包含加、减、乘、除等四种算术运算。需要时
,不难把它推广到更一般的表达式上。
二、运算符优先级
对于两个相继出现的操作符θ1和θ2 有三种关系:
θ1 <θ2 θ1的优先级低于θ2
θ1 =θ2 θ1的优先级等于θ2
θ1 >θ2 θ1的优先级高于θ2
由此可以列出“+-*/”之间的优先级。如下图
通过图可以看出:加减乘除优先性都低于“(”但是高于“)”,由运算从左到右可知,当θ1=θ2 ,令θ1>θ2;
为了算法简洁,在表达式的左边和右边虚设一个“#”,这一对“#”表示一个表达式求值完成。
“(”=“)”当一对括号相遇时表示括号内已运算完成。
“)”和“(”、“#”和“(”、“(”和“#”无法相继出现,如果出现则表达式出现语法错误。(实现时,可以用0存储)
这个表如何理解呢?例如:a+b+c,这里有两个运算符,运算符θ1 为+,运算符θ2 也为+ ,查上表,得到“>”的关系,那么意味着先计算前面的+号,也就是先算a+b,得到结果后,再考虑算后面的表达式。
三、算法思路
为实现优先算法,可以使用两个工作栈,一个是OPTR,用于寄存运算符,一个是OPND,用于寄存操作数和运算结果。算法的基本思想是:
(1) 首先置操作数栈为空栈,表达式起始符'#'为栈底元素。
(2)依次读入表达式中的每个字符,若是操作数则进OPND栈,若是运算符则和OPTR栈的栈顶运算符比较优先级作相应操作,直至整个表达式求值完毕(OPTR栈顶元素和当前读入的字符均为'#')
算数表达式求值的运算符优先算法。假定输入的表达式语法正确,以'#'做结束符。使用OPTR和OPND分别作为运算符栈和操作数栈。
expression函数的算法逻辑如下。以下是伪代码,需要用真实的代码代替:
setNull(OPTR);
push(OPTR, ‘#’); //将’#'压入操作符OPTR栈
setNull(OPND);
读入字符ch;
do{
if (ch IN op) //op为运算符的集合
swith( precede(top(OPTR),ch ) { //比较栈顶元素和ch的优先关系
case '<':
push(OPTR,ch); //栈顶元素优先级低,则压入操作符栈
读入字符ch;
break;
case '=':
if (ch==')')
x = pop(OPTR); //自己思考什么情况需要这个判断
读入字符ch;
break;
case '>'://栈顶元素优先级高,取出一个运算符,两个操作数,并计算
theta = pop(OPTR);
b = pop(0PND);
a = pop(OPND);
push(OPND,operate(a, theta, b));//将计算结果压入操作数栈
}
else{ //op为操作数的集合
push(OPND,ch);
读入字符ch;
}
}while (( ch != ‘#’) OR ( top(OPTR) != ‘#’))
return top(OPND);
算法中调用了两个函数,precede是判断运算符栈的栈顶运算符与读入的运算符之间的优先关系;
operate作一元运算:a θ b,算出运算结果,例如调用operate('1', '+', '5');算出结果6。
这个问题b站上笔者也为大家找到很好的讲解视频,亲测有用。这里附上连接表达式求值
③队列
抽象数据类型队列的定义
链队列
和线性表类似,队列也可以有两种存储表示。
用链表表示的队列简称为链队列,如图3.10所示。一个链队列显然需要两个分别指
示队头和队尾的指针(分别称为头指针和尾指针)才能惟一确定。这里,和线性表的单链
表一样,为了操作方便起见,我们也给链队列添加一个头结点,并令头指针指向头结点。
由此,空的链队列的判决条件为头指针和尾指针均指向头结点,如图3.11(a)所示。
链队列的操作即为单链表的插人和删除操作的特殊情况,只是尚需修改尾指针或头
指针,图3.11(b)~(d)展示了这两种操作进行时指针变化的情况。下面给出链队列类型
的模块说明。
链队列的基本操作的定义,大家可以参看抽象数据类型栈的定义,大同小异。鉴于篇幅和内容的难度,笔者帮助大家讲解循环队列的实现,至于链队列笔者对照着课本看看,相信循环队列搞懂之后,链队列的问题也就迎刃而解了。
循环队列
这里给出课本上循环队列的经典示意图,具体的文字笔者细细品味书籍即可。这里重点讲解关于对应不同操作的函数的实现中,需要注意的地方。循环队列即是在顺序栈的基础上改一些地方,相信笔者在了解后面附上的第一个程序之后,也可以自己完成这部分的代码。下面给大家提供队列基本操作的定义和说明。
终于到代码啦
栈的顺序实现
#include<stdio.h>
#include<malloc.h>
#define LEN sizeof(struct stack)
struct stack{
int value; // 栈内的数值
struct stack *next;
};
int num; // 全局变量,记录栈顶元素的序号
int main()
{
struct stack *InitList(); // 创造一个空栈
void DestroyStack(struct stack *head); // 摧毁栈
struct stack *ClearStack(struct stack *head); // 将head置为空栈
bool IfEmpty(); // 判断是否为空栈
int StackLength(); // 返回栈的长度
int GetTop(struct stack *head); // 返回栈顶元素,如果栈为空则返回0
void Push(struct stack *head,int element); // 进栈element
int Pop(struct stack *head); // 若栈不空,删除栈顶元素
// 测试
struct stack *head; // 创造栈指针
int element,length;
head = InitList();
Push(head,1);
Push(head,2);
element = Pop(head);
length = StackLength();
head = ClearStack(head);
DestroyStack(head);
}
struct stack *InitList() // 创造一个栈
{
// 创造一个栈
struct stack *head;
head = (struct stack*)malloc(LEN);
head->value = 0;
head->next = NULL;
num = 0; // 栈元素的位置为0,即空栈
return head;
}
void DestroyStack(struct stack *head)
{
// 摧毁栈
free(head);
printf("delete stack successfully!\n");
}
struct stack *ClearStack(struct stack *head)
{
// 将head置为空栈
head->value = 0;
head->next = NULL;
printf("reset successfully!\n");
return head;
}
bool IfEmpty()
{
// 判断是否为空栈
bool flag;
if(num == 0){
flag = true;
printf("stack is empty!\n");
}else{
printf("stack is not empty!\n");
flag = false;
}
return flag;
}
int StackLength()
{
// 返回栈的长度
int length;
length = num;
return length;
}
int GetTop(struct stack *head)
{
// 返回栈顶元素,如果栈为空则返回0
struct stack *p;
int element;
p = head;
while(head->next != NULL){
p = p->next;
}
element = p->value;
return element;
}
void Push(struct stack *head,int element)
{
// 进栈element
struct stack *p,*add;
add = (struct stack*)malloc(LEN);
add->value = element;
add->next = NULL;
p = head;
while(p->next != NULL){
p = p->next;
}
p->next = add;
num = num + 1;
}
int Pop(struct stack *head)
{
// 若栈不空,删除栈顶元素
struct stack *p;
bool flag;
int element;
p = head;
flag = IfEmpty();
if(flag == false){
element = 0;
printf("stack is empty! error!\n");
}else{
while(p->next->next != NULL){
p = p->next;
}
element = p->value;
p->next = NULL;
num = num - 1;
}
return 0;
}