栈和队列的定义和特点
栈(Stack)的相关概念
定义:限定只能在表的一端进行插入和删除运算的线性表(只能在栈顶操作)。
逻辑结构:与线性表相同,仍为一对一关系。
存储结构:包括顺序栈和链栈,但顺序栈更常见。
运算规则:只能在栈顶运算,访问结点是依照后进先出(Last In First Out , LIFO)的原则;栈与一般线性表的区别就在于此,一般线性表的运算规则是随机存取。
应用场景:递归调用,函数调用,表达式求值,匹配括号。
结构、操作图示:
队列(Queue)的相关概念
定义:只能在表的一端进行插入运算,在表的另一段进行删除运算的线性表(尾插头删)。
逻辑结构:与线性表相同,为一对一关系。
存储结构:包括顺序队和链队,以循环顺序队列更常见。
运算规则:只能在队首出队,在队尾入队,依照先进先出(First In First Out , FIFO)的原则。
图示:
栈的表示和操作的实现
栈的抽象数据类型的类型定义
顺序栈
顺序栈的存储方式
顺序栈和顺序表的存储方式完全相同,是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素(栈底一般在低地址端),
另设top指针和base指针,分别指示栈顶元素和栈底元素在顺序栈中的位置;但是为了操作方便,通常top指示的是栈顶元素之上的下标地址。
另外。用stacksize表示栈可使用的最大容量。
图示:
空栈:base==top
栈满:top-base==stacksize
栈满时的处理方法:
报错,返回操作系统。
分配更大的空间,作为栈的存储空间,将原栈的内容移入新栈。
使用数组作为顺序栈存储方式的特点:简单方便,但容易产生溢出;
上溢(overflow):栈已经满,又要压入元素;
下溢(underflow):栈已经空,还要弹出元素。
注:上溢是一种错误,使问题的处理无法进行;而下溢一般认为是一种结束条件,即问题处理结束。
顺序栈的表示和算法实现
顺序栈的表示
#define MAXSIZE 100
typedef struct{
SElemType *base;//栈底指针
SElemType *top;//栈顶指针
int stacksize;//栈可用最大容量
}SqStack;
顺序栈的初始化
Status InitStack(SqStack &S){//构造一个空栈
S.base=new SElemType[MAXSIZE];//开辟出一个数组空间,base作为数组的基地址
//或者如下:
//S.base=(SElemType*)malloc(MAXSIZE*sizeof(SElemType));
if(!S.base) exit(OVERFLOW);//若开辟失败,则S.base不存在,即为空,所以退出并返回OVERFLOW
S.top=S.base;//初始化时,栈顶指针等于栈底指针
S.stacksize=MAXSIZE;
return OK;
}
判断顺序栈是否为空
Status StackEmpty(SqStack S){
if(S.top==S.base)
return TRUE;
else
return FALSE;
}
求顺序栈的长度
int StackLength(SqStack S){
return S.top-S.base;
}
清空顺序栈
Status ClearStack(SqStack &S){
if(S.base) S.top=S.base;
return OK;
}
销毁顺序栈
Status DestroyStack(SqStack &S){
if(S.base){
delete S.base;
S.stacksize=0;
S.base=S.top=NULL;
}
return OK;
}
顺序栈的入栈
Status Push(SqStack &S,SElemType e){
if(S.top-S.base==S.stacksize)//栈满
return ERROR;
*S.top++=e;
return OK;
}
顺序栈的出栈
Status Pop(SqStack &S,SElemType &e){
if(S.top==S.base)
return ERROR;
e=*--S.top;
return OK;
}
链栈的表示和算法实现
链栈的表示
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStack;//定义一个结构类型StackNode作为栈的结点类型,再用这个类型定义一个指向这种类型的指针类型LinkStack
LinkStack S;//定义一个结点指针S,表示一个栈
链栈的特点
链栈的头指针就是栈顶;
不需要头结点,这样操作起来比较方便;
栈顶元素是首元结点存储的元素,栈底元素是尾结点存储的元素;
基本不存在栈满的情况,需要空间可以直接从内存里找;
空栈相当于头指针指向空(NULL);
插入和删除仅在栈顶处执行。
链栈的初始化
void InitStack(LinkStack &S){
S=NULL;//创造一个空栈,栈顶指针置空
return OK;
}
判断链栈是否为空
Status StackEmpty(LinkStack S){
if(S==NULL) return TRUE;
else return FALSE;
}
链栈的入栈
Status Push(LinkStack &S,SElemType e){
p=new StackNode;//生成新结点p
p->data=e;//将新结点的数据域置为e
p->next=S;//将新结点插入栈顶
S=p;//修改栈顶指针
return OK;
}
链栈的出栈
Status Pop(LinkStack &S,SElemType &e){
if(S==NULL) return ERROR;
e=S->data;
p=S;//预先定义一个指针p指向要弹出的链栈结点
S=S->next;
delete p;
return OK;
}
取栈顶元素
SElemType GetTop(LinkStack S){
if(S!=NULL)
return S->data;
}
队列的表示和操作的实现
队列的抽象数据类型定义
队列的顺序表示——用一维数组base[MAXQSIZE]
#define MAXQSIZE 100//最大队列长度
typedef struct{
QElemType *base;//用base数组来表示队列
int front;//头指针,不是指针型而是整型,因为存放的是下标,队头元素的下标
int rear;//尾指针,队列中可以存放(队尾元素下标+1)个元素
}SqQueue;//队列的数据中没有存储队长的部分,而是用到两个“指针”来表示队头元素和队尾元素的下标
顺序队的溢出
当rear=MAXQSIZE时,发生溢出(一般来说rear为MAXSIZE-1时顺序队就满了);
当front=0 , rear=MAXQSIZE时,若再入队,则为真溢出;
当front≠0 , rear=MAXQSIZE时,若再入队,则为假溢出(因为队头还有空间);
为规避溢出的问题,将顺序队改为循环顺序队,简称循环队列。
循环队列的表示和算法实现
循环队列的类型定义
#define MAXQSIZE 100
typedef struct{
QElemType *base;//用指针代替数组,这样可以动态分配存储空间,不受限制
int front;//头指针,若队列不空,指向队列头元素
int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
循环队列的初始化
Status InitQueue(SqQueue &Q){
Q.base=new QElemType[MAXQSIZE];//分配空间
if(!Q.base) exit(OVERFLOW);
Q.front=Q.rear=0;//头尾指针置为0,队列为空
return OK;
}
求循环队列的长度
int QueueLength(SqQueue Q){
return (Q.rear-Q.front+MAXQSIZE)%MAXQSIZE;//因为循环后会出现尾指针比头指针小的情况,所以加上队长再求余
}
循环队列入队
Status EnQueue(SqQueue &Q,QElemType e){
if((Q.rear+1)%MAXQSIZE==Q.front) return ERROR;
Q.base[Q.rear]=e;
Q.rear=(Q.rear+1)%MAXQSIZE;
return OK;
}
循环队列出队
Status DeQueue(SqQueue &Q,QElemType &e){
if(Q.front==Q.rear) return ERROR;//队空
e=Q.base[Q.front];//保存队头元素
Q.front=(Q.front+1)%MAXQSIZE;//队头指针+1
return OK;
}
取循环队列的队头元素
SElemType GetHead(SqQueue Q){
if(Q.front!=Q.rear)//队列不为空
return Q.base[Q.front];
}
链队的表示和算法实现
使用原因:用户无法估计所用队列的长度。
链队的类型定义
#define MAXQSIZE 100
typedef struct Qnode{
QElemType data;
struct Qnode *next;
}Qnode,*QueuePtr;
typedef struct{
QueuePtr front;//队头指针,指向头结点
QueuePtr rear;//队尾指针
}LinkQueue;
LinkQueue Q;
链队列初始化
Status InitQueue(LinkQueue &Q){
Q.front=Qrear=(QueuePtr)malloc(sizeof(Qnode));
if(!Q.front) exit(OVERFLOW);
Q.front->next=NULL;
return OK;
}
销毁链队列
Status DestroyQueue(LinkQueue &Q){
while(Q.front){//从队头结点开始,依次释放所有结点
p=Q.front->next;
free(Q.front);
Q.front=p;
}
return OK;
}
链队列的入队
Status EnQueue(LinkQueue &Q,QElemType e){
p=(QueuePtr)malloc(sizeof(Qnode));
if(!p) exit(OVERFLOW);
p->data=e;
p->next=NULL;
Q.rear->next=p;
Q.rear=p;
return OK;
}
链队列的出队
Status DeQueue(LinkQueue &Q,QElemTupe &e){
if(Q.front==Q.rear) return ERROR;
p=Q.front->next;//设置一个指针p指向要出队的元素
e=p->data;//因为要返回,所以将要出队的元素赋值给e
Q.front->next=p->next;//将头指针同后面的结点相连
if(Q.rear==p) Q.rear=Q.front;//如果出队的是尾结点,那么要让尾指针和头指针都指向头结点
delete p;
return OK;
}
求链队列的队头元素
Status GetHead(LinkQueue Q,QElemType &e){
if(Q.front==Q.rear) return ERROR;
e=Q.front->next->data;
return OK;
}
习题
课本习题
设有一个递归算法如下:
int fact ( int n )
{ //n 大于等于0
if(n<=0) return 1;
else return n*fact(n-1); }
则计算fact(n) 需要调用该函数的次数为( )。
A. n+1 B. n-1 C.n D.n+2
答案:A
解析:观察fact函数可以发现这个函数是用来计算n的阶乘,即为n*(n-1)…*1
所以将执行n+1次
特殊值法。n = 0 ,仅调用一次fact(n) 函数,所以执行n+1次
期末题库
3 | 若一个栈的输入序列为1,2,3,…,n,输出序列的第一个元素是i,则第j个输出元素是( )。 | A. i-j-1 | B. i-j | C. j-i+1 | D. 不确定的 |
答案:D
解析:i和j无关,纯纯的文字游戏。
初始化一个空间大小为5的顺序栈Ss后,Ss->top的值( )。 | A. 是0 | B. 不定 | C. 不再改变 | D. 动态变化 |
答案:A
循环队列占用的空间( )。 | A. 必须连续 | B. 可以不连续 | C. 不能连续 | D. 不必连续 |
答案:A
初始化一个空间大小为5的循环队列Sq后,Sq->front和Sq->rear的值都是( )。 | A. 0 | B. 不定 | C. 不再改变 | D. 1 |
答案:A