第三章 栈和队列
栈和队列是两种重要的数据结构。
从数据结构的角度看,栈和队列都是线性表
其特殊性在于,栈和队列是线性表操作的子集,
它们是操作受限的线性表,因此可称为限定性数据结构。
从数据类型角度看,它们是和线性表大不相同的两类重要的抽象数据结构。
3.1栈
3.1.1抽象数据类型栈的定义
栈(stack)是限定仅在表尾进行插入或删除操作的线性表,表尾称为栈顶,表头称为栈底。不含元素的栈称为空栈栈是后进先出的线性表
ADT Stack{
数据对象:
数据关系:
基本操作:
InitStack(&S);
DestroyStack(&S);
ClearStack(&S);
StackEmpty(S);
StackLength(S);
GetTop(S,&e);
Push(&S,e);
Pop(&S,&e);
StackTraverse(S,visit());
}ADT Stack;
3.1.2栈的表示和实现
//栈的顺序存储表示
#define STACK_INIT_SIZE 100//存储空间初始分配量
#define STACKINCREAMENT 10//存储空间分配增量
typedef struct{
SElemType *base; //在栈构造之前和销毁之后,base的值为NULL
SElemType *top; //栈顶指针
int stacksize; //当前分配的存储空间,以元素为单位
}SqStack;
Stack InitStack(SqStack &S)
{
//构造一个空栈
S.base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if(!S.base) exit(OVERFLOW);//存储分配失败
S.top=S.base;
S.stacksize=STACK_INIT_SIZE;
return OK;
}// InitStack
Stack GetTop(SqStack S,ElemType &e)
{
if(S.top==S.base) return ERROR;
e=*(S.top-1);
return OK;
}//GetTop
Status Push(SqStack &S,SElemType e)
{
//插入元素e为新的栈顶元素
if(S.top-S.base>=S.stacksize)
{
S.base=(SElemType *)realloc(S.base,(S.stacksize+STACKINCREAMENT)*sizeof(SElemType));
if(!(S.base) exit(OVERFLOW);
S.top=S.base+S.stacksize;
S.stacksize+=STACKINCREAMENT;
}
*S.top++=e; //先取值,再自加1
return OK;
}//Push
Status Pop(SqStack &S,SElemType &e)
{
//若栈不空,则删除栈顶元素,用e返回其值
if(S.top==S.base) return ERROR;
e=*--S.top; //先自减1,再取值
return OK;
}
栈的链式表示如图所示,由于栈的操作是线性表操作的特例,则
链栈的操作易于实现,在此不作详细讨论
//栈的链式存储表示
typedef struct Stack_Node
{ ElemType data ;
struct Stack_Node *next ;
}Stack_Node ;
链栈基本操作的实现
//(1) 栈的初始化
Stack_Node *Init_Link_Stack(void)
{
Stack_Node *top ;
top=(Stack_Node *)malloc(sizeof(Stack_Node )) ;
top->next=NULL ;
return(top) ;
}
//(2) 压栈(元素进栈)
Status push(Stack_Node *top , ElemType e)
{
Stack_Node *p ;
p=(Stack_Node *)malloc(sizeof(Stack_Node)) ;
if (!p) return ERROR; /* 申请新结点失败,返回错误标志 */
p->data=e ;
p->next=top->next ;
top->next=p ; /* 钩链 */
return OK;
}
//(3) 弹栈(元素出栈)/* 将栈顶元素出栈 */
Status pop(Stack_Node *top , ElemType *e)
{
Stack_Node *p ;
ElemType e ;
if (top->next==NULL )
return ERROR ; /* 栈空,返回错误标志 */
p=top->next ; e=p->data ; /* 取栈顶元素 */
top->next=p->next ; /* 修改栈顶指针 */
free(p) ;
return OK ;
}
3.2栈的应用举例
3.2.1数制转换
3.2.1数制转换
十进制数N转化为其他d进制数的转化
//算法3.1
void conversion(){
InitStack(S);
scanf("%d",N);
while(N)
{
Push(S,N%8);
N=N/8;
}
while(!StackEmpty(S))
{
Pop(S,e);
printf("%d",e);
}
}//conversion
3.2.2括号匹配检测[([][])]12345678
#define TRUE 0
#define FLASE -1
SqStack S ;
S=Init_Stack() ; /*堆栈初始化*/
int Match_Brackets( )
{
char ch , x ;
scanf(“%c” , &ch) ;
while (asc(ch)!=13)
{
if ((ch==‘(’)||(ch==‘[’)) push(S , ch) ;
else if (ch==‘]’)
{
x=pop(S) ;
if (x!=‘[’)
{
printf("’[’括号不匹配") ;
return FLASE ;
}
}
else if (ch==‘)’)
{
x=pop(S) ;
if (x!=‘(’)
{
printf("’(’括号不匹配") ;
return FLASE ;
}
}
}//while
if (S.top!=0)
{
printf("括号数量不匹配!") ;
return FLASE ;
}
else return TRUE ;
}
3.2.3行编辑程序
3.2.4迷宫求解
typedef struct
{
int ord; //通道块在路径上的“序号”
PosType seat;//通道快在迷宫中的“坐标”
int di; //从此通道快走向下一个通道快的“方向”
}SElemTypde;
Status MazePath(MazeType maze,PosType start,PosType end)
{
//若迷宫maze中存在从入口start到出口end的通道,则求得一条存放在栈中
//并返回TRUE,否则返回FALSE
InitStack(S); curpos=start;//设置当前位置为入口地址
curstep=1; //探索第一步
do{
if(Pass(curpos))//当前位置可以通过,则是未曾走过的通道快
{
FootPrint(curpose);//留下足迹
e=(curstep,curpos,1);
push(S,e) //加入路径
if(curstep==end) return TRUE;//到达终点
curpos=NextPos(curpos,1)//下一个位置是当前位置的东邻
curstep++; //探索下一步
}else //当前位置不可通过
{
if(!StackEmpty(S)
{
Pop(S,e);
while(e.di==4&&!StackEmpty(S))
{
MarkPrint(e.seat); Pop(S,e);//留下不能通过的标记,并退回一步
}//while
if(e.di<4)
{
e.di++; Push(S,e);//换下一个方向探索]
curpos=NextPos(e.seat,e.di);
}//if
}//if
}//else
}while(!StackEmpty(S));
return FALSE;
}//MazePath;
3.2.5表达式求值
为了实现算符优先算法,可以使用两个工作栈,一个称作OPTR,
用来寄存运算符,一个称做OPND,用来寄存操作数或运算结果
算法的基本思想是:
(1)首先置操作数栈为空栈,表达式起始符"#"为运算符的栈底元素
(2)依次读入表达式中的每个字符,操作数进OPND,运算符则和OPTR
栈顶元素进行比较,做相应的操作,直至整个表达式求值完毕
OperandType EvaluateExpression()
{
InitStack(OPTR); Push(OPTR,'#');
InitStack(OPND); c=getchar();
while(c!='#'||GetTop(OPTR)!='#')
{
if(!In(c,OP))
{
Push(OPND,c);
c=getchar();
}else
switch(Precede(GetTop(OPTR),c))
{
case '<'://栈顶元素优先权低
Push(OPTR,c); c=getchar();
break;
case '=': //脱括号,并接收下一个字符
Pop(OPTR,x); c=getchar();
break;
case '>': //退栈,并将运算结果入栈
Pop(OPTR,theta);
Pop(OPND,b); Pop(OPND,a);
Push(OPND,Operate(a,theta,b));
break;
}//swith
}//while
return GetTop(OPND);
}
3.3栈的递归与实现
递归调用:一个函数(或过程)直接或间接地调用自己本身,简称递归(Recursive)。
递归是程序设计中的一个强有力的工具。因为递归函数结构清晰,程序易读,正确性很容易得到证明。
为了使递归调用不至于无终止地进行下去,实际上有效的递归调用函数(或过程)应包括两部分:递推规则(方法),终止条件。
例如:求n!
为保证递归调用正确执行,系统设立一个“递归工作栈”,作为整个递归调用过程期间使用的数据存储区。 每一层递归包含的信息如:参数、局部变量、上一层的返回地址构成一个“工作记录” 。每进入一层递归,就产生一个新的工作记录压入栈顶;每退出一层递归,就从栈顶弹出一个工作记录。
从被调函数返回调用函数的一般步骤:
(1) 若栈为空,则执行正常返回。
⑵ 从栈顶弹出一个工作记录。
⑶ 将“工作记录”中的参数值、局部变量值赋给相应的变量;读取返回地址。
⑷ 将函数值赋给相应的变量。
(5) 转移到返回地址。
3.4队列
3.3.1 队列及其基本概念
1 队列的基本概念
队列(Queue):也是运算受限的线性表。是一种先进先出(First In First Out ,简称FIFO)的线性表。只允许在表的一端进行插入,而在另一端进行删除。
队首(front) :允许进行删除的一端称为队首。
队尾(rear) :允许进行插入的一端称为队尾。
例如:排队购物。操作系统中的作业排队。先进入队列的成员总是先离开队列。
2 队列的抽象数据类型定义
ADT Queue{
数据对象:D ={ ai|ai∈ElemSet, i=1, 2, …, n, n >= 0 }
数据关系:R = {<ai-1, ai> | ai-1, ai∈D, i=2,3,…,n }约定a1端为队首,an端为队尾。
基本操作:
Create():创建一个空队列;
EmptyQue():若队列为空,则返回true ,否则返回flase ;
⋯⋯
InsertQue(x) :向队尾插入元素x;
DeleteQue(x) :删除队首元素x;
} ADT Queue
3.4.2队列的顺序表示和实现
//静态顺序队列
#define MAX_QUEUE_SIZE 100
typedef struct queue
{
ElemType Queue_array[MAX_QUEUE_SIZE] ;
int front ;
int rear ;
}SqQueue;
3.4.3循环队列的基本操作
//1 循环队列的初始化
SqQueue Init_CirQueue(void)
{
SqQueue Q ;
Q.front=Q.rear=0; return(Q) ;
}
//2 入队操作 /* 将数据元素e插入到循环队列Q的队尾 */
Status Insert_CirQueue(SqQueue Q , ElemType e)
{
if ((Q.rear+1)%MAX_QUEUE_SIZE== Q.front)
return ERROR; /* 队满,返回错误标志 */
Q.Queue_array[Q.rear]=e ; /* 元素e入队 */
Q.rear=(Q.rear+1)% MAX_QUEUE_SIZE ; /* 队尾指针向前移动 */
return OK; /* 入队成功 */
}
//3 出队操作 /* 将循环队列Q的队首元素出队 */
Status Delete_CirQueue(SqQueue Q, ElemType *x )
{
if (Q.front+1== Q.rear)
return ERROR ; /* 队空,返回错误标志 */
*x=Q.Queue_array[Q.front] ; /* 取队首元素 */
Q.front=(Q.front+1)% MAX_QUEUE_SIZE ; /* 队首指针向前移动 */
return OK ;
}
3.4.4 队列的链式表示和实现
//1 队列的链式存储表示
typedef struct Qnode
{
ElemType data ;
struct Qnode *next ;
}QNode ;
//2 指针结点类型定义:
typedef struct link_queue
{
QNode *front , *rear ;
}Link_Queue ;
3 链队列的基本操作
//⑴ 链队列的初始化
LinkQueue *Init_LinkQueue(void)
{
LinkQueue *Q ; QNode *p ;
p=(QNode *)malloc(sizeof(QNode)) ; /* 开辟头结点 */
p->next=NULL ;
Q=(LinkQueue *)malloc(sizeof(LinkQueue)) ; /* 开辟链队的指针结点 */
Q.front=Q.rear=p ;
return(Q) ;
}
//⑵ 链队列的入队操作
//在已知队列的队尾插入一个元素e ,即修改队尾指针(Q.rear)。
Status Insert_CirQueue(LinkQueue *Q , ElemType e) /* 将数据元素e插入到链队列Q的队尾 */
{
p=(QNode *)malloc(sizeof(QNode)) ;
if (!p) return ERROR;/* 申请新结点失败,返回错误标志 */
p->data=e ; p->next=NULL ; /* 形成新结点 */
Q.rear->next=p ; Q.rear=p ; /* 新结点插入到队尾 */
return OK;
}
//⑶ 链队列的出队操作
Status Delete_LinkQueue(LinkQueue *Q, ElemType *x)
{
QNode *p ;
if (Q.front==Q.rear) return ERROR ; /* 队空 */
p=Q.front->next ; /* 取队首结点 */
*x=p->data ;
Q.front->next=p->next ; /* 修改队首指针 */
if (p==Q.rear) Q.rear=Q.front ;
/* 当队列只有一个结点时应防止丢失队尾指针 */
free(p) ;
return OK ;
//⑷ 链队列的撤消 /* 将链队列Q的队首元素出队 */
void Destroy_LinkQueue(LinkQueue *Q )
{
while (Q.front!=NULL)
{
Q.rear=Q.front->next; /* 令尾指针指向队列的第一个结点 */
free(Q.front); /* 每次释放一个结点 */
Q.ront=Q.rear; /* 第一次是头结点,以后是元素结点 */
}
}