栈和队列

147 阅读7分钟

栈和队列

限定了插入和删除位置的线性表

栈(stack),后进先出,LIFO

一般限定在表尾插入与删除

表尾称为栈顶(Top)

表头称为栈底(Base)

入栈:插入元素到栈顶

出栈:删除栈顶元素

问:三个元素a,b,c 入栈,顺序是 a,b,c,问出栈的可能顺序?
	可能为
	- a,b,c
	- a,c,b
	- b,a,c
	- b,c,a
	- c,b,a
	不可能得到 c,a,b 的顺序

栈的应用

  • 进制转换(10→8)

    除 8 取余

  • 括号匹配的检验

  • 表达式求值

    算符优先算法

  • 舞伴问题

抽象数据类型

// 类型定义
ADT Stack{
    数据对象:
        D = { ai | ai∈ElemSet, i=1,2,...,n, n≧0 }
    数据关系:
        R1 = {<ai-1, ai> | ai-1, ai∈D, i=2,...,n}
    	约定an端为栈顶,a1端为栈底
	基本操作:初始化、进栈、出栈、取栈顶元素等
}ADT Stack

基本操作

InitStack(&S)
    // 栈的初始化
    // 操作结果:构造一个空栈S
DestoryStack(&S)
    // 栈的销毁
    // 初始条件:栈S已存在
    // 操作结果:栈S被销毁
StackEmpty(S)
    // 判断栈S是否为空栈
    // 初始条件:栈S已存在
    // 操作结果:若栈S为空栈,返回TRUE,否则返回FALSE
StackLength(S)
    // 求栈的长度
    // 初始条件:栈S已存在
    // 操作结果:返回S的元素个数,即栈的长度
GetTop(S,&e)
    // 取栈顶元素
    // 初始条件:栈S已存在且非空
    // 操作结果:用e返回S的栈顶元素
ClearStack(&S)
    // 将栈置空
    // 初始条件:栈S已存在
    // 操作结果:将S清空
Push(&S,e)
    // 入栈
    // 初始条件:栈S已存在
    // 操作结果:插入元素e作为新的栈顶元素
Pop(&S,&e)
    // 出栈
    // 初始条件:栈S已存在
    // 操作结果:删除栈顶元素an,并用e返回

顺序栈

**存储方式:**与一般线性表的顺序存储结构相同

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 = (SElemType*)malloc(sizeof(SElemType)*MAXSIZE);
    // C++: S.base = new SElemType[MAXSIZE];
    if(!S.base)
        exit (OVERFLOW);	// 存储空间分配失败
    S.top = S.base;
    S.stacksize = MAXSIZE;
    return OK;
}

顺序栈判空

Status StackEmpty(SqStack S){
    // 栈空时返回 TRUE, 否则返回 FALSE
    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){	
        free(S.base);	// 删除栈底指针
        S.stacksize = 0;	// 栈长度设置为0
        S.base = S.top = NULL;	// top 与 base 指针置空
        // 疑问:base 指针不是已经在上面被删除了吗?
        // 解答: free 是使数组回归内存,下面的语句是将结构类型的值设置为空
    }
    return OK;
}

入栈

  1. 判断栈是否满,若满则出错(上溢)
  2. 元素 e 压入栈顶
  3. 栈顶指针加 1
Status Push(SqStack &S, SElemType e){
    if(S.top-S.base == S.stacksiZe)	// 栈满
        return ERROR;
    *S.top++ = e;
    return OK;
}

出栈

  1. 判断是否栈空,若空则出错(下溢)
  2. 栈顶指针减1
  3. 获取栈顶元素e
Status Pop(SqStack &S, SElemType &e){
    if(S.top == S.base)
        // 或 if(StackEmpty(S))
        return ERROR;
    e = *--S.top;
    return OK;
}

链栈

运算受限的单链表,只能在链表头部进行操作

类型定义

typedef struct StackNode{
    SElemType data;
    struct StackNode *next;
}StackNode, *LinkStack;
LinkStack S;
  • 链栈中指针的方向与链表中指针的方向相反:an→an-1

  • 链表的头指针就是栈顶

  • 不需要头结点

初始化

void InitStack(LinkStack &S){
    // 构造一个空栈,栈顶指针置空
    S = NULL;
    reutrn OK;
}

链栈判空

Status StackEmpty(LinkStack S){
    if(S == NULL)
        return TRUE;
    else 
        return FALSE;
}

入栈

Status Push(LinkStack &S, SElemType e){
    p = (LinkStack)malloc(sizeof(StackNode));
    P->data = e;
    p->next = S;
    S = p;
    return OK;
}

出栈

Status Pop(LinkStack &S, SElemType &e){
    if(S == NULL)
        return ERROR;
    e = S->data;
    p = S;
    S = S->next;
    free(p);
    return OK;
}

取栈顶元素

Status GetTop(LinkStack S){
    if(S!= NULL)
        return S->data;
}

栈与递归

  • 求 n 的阶乘
  • Fibonaci 数列
  • 二叉树
  • 广义表
  • 迷宫问题
  • Hanoi 塔

**递归优点:**结构清晰,程序易读

**缺点:**每次调用都要生成工作记录,保存状态信息,入栈;返回时要出栈,回复状态信息,事件开销大

递归→非递归

  1. 尾递归、单向递归→循环结构

    long Fact(long n){
        if(n == 0)
            return 1;
        else
            return n*Fact(n-1);
    }
    
    // 改为
    long Fact(long n){
        t = 1;
        for(i=1; i<=n; i++)
            t = t*i;
        return t;    
    }
    
  2. 自用栈模拟系统的运行时栈

队列

队列(queue),先进先出,FIFO

在表一端(表尾)插入,在另一端(表头)删除

队尾:表尾 an

队头:表头 a1

插入元素称为 入队 ,删除元素称为 出队

队列的应用

  • 脱机打印
  • 多用户系统循环使用CPU
  • 实时控制系统信号的处理

抽象数据类型定义

ADT Queue{
    数据对象: D={ai | ai∈ElemSet, i=1,2,...,n, n≧0}
    数据关系: R={<ai-1, ai> | ai-1, ai∈D, i=2,...,n}
    // 约定 ai 端为队头,an端为队尾
    基本操作:
    	InitQueue(&Q)
            // 操作结果:构造空队列 Q
        DestroyQueue(&Q)
            // 条件:队列 Q 已存在;
            // 操作结果:将 Q 清空
		QueueLength(Q)
            // 条件:队列 Q 已存在;
            // 操作结果:返回 Q 的元素个数,即对长
		GetHead(Q, &e)
            // 条件:队列 Q 为非空队列;
            // 操作结果:用 e 返回 Q 的队头元素
		EnQueue(&Q, e)
            // 条件:队列 Q 已存在;
            // 操作结果:插入元素 e 为 Q 的队尾元素
		DeQueue(&Q, &e)
            // 条件:队列 Q 为非空队列;
            // 操作结果:删除 Q 的队头元素,用 e 返回
		// 还有将队列置空,遍历队列等……
}

顺序队列

数据类型定义

// 队列的顺序存储——用一维数组 base[MAXQSIZE] 表示
#define MAXQSIZE 100 // 最大队列长度
Typedef struct{
    QElemType *base;	// 初始化的动态分配存储空间
    int front;	// 头指针
    int rear;	// 尾指针
    // 虽然它们叫指针,其实不是指针
}SqQueue;

队列初始情况,即 队空 时:front = rear = 0

入队: base[rear]=x; rear++;

出队:x=base[front]; front++;,若出队到队空时,有 front == rear;

**注:**设数组大小为 MAXQSIZE,则 rear == MAXQSIZE; 时,发生溢出,此时存在两种情况

  1. front == 0;,真溢出
  2. front != 0;,假溢出
  • 解决 假溢出 的办法:循环队列

    rear == MAXQSIZE 时,若向量的开始端空着,则 rear 回到初始端,从头使用空着的空间;当 front == MAXQSIZE 时也用同样的办法

采取“模运算”使尾指针回到 0 

插入元素举例: Q.base[Q.rear]=x; Q.rear=(Q.rear+1)%MAXSIZE;

删除元素举例: x=Q.base[Q.front]; Q.front=(Q.front+1)%MAXQSIZE;

但是此时存在一个问题:
	队空时:front == rear;
	队满时:front == rear;
该如何区分队空和队满呢?
	1. 设置一个标志来区分队空与队满;
	2. 设置一个变量记录元素个数;
	3. 少用一个元素空间(√)
少用一个元素空间

解决循环队列队满时的判断问题

队空时: front == rear;

队满时: (rear+1)%MAXQSIZE == front;

循环队初始化

Status InitQueue(SqQueue &Q){
    Q.base = (QElemType*)malloc(MAXQSIZE*sizeof(QElemType));
    // C++ 语法: Q.base = new QElemType[MAXQSIZE];
    if(!Q.base)
        // 存储分配失败
        exit(OVERFLOW);
    Q.front = Q.rear = 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;	// 队尾指针 +1
    return OK;
}

循环队列出队

Status DeQueue(SqQueue &Q, QElemType &e){
    if(Q.front == Q.rear)
        return ERROR;	// 队空
    e = Q.base[front];
    Q.front = (Q.front + 1)%MAXQSIZE;
    return OK;
}

取队头元素

SElemType GetHead(SqQueu Q){
    if(Q.front != Q.rear)
        return Q.base[front];
}

链队

用户无法估计所用队列的长度时采用链队

类型定义

#define MAXSIZE 100 // 最大队列长度
typedef struct QNode{
    QElemType data;
    struct QNode *next;
}QNode,*QueuePtr;

typedef struct{
    QueuePtr front; // 队头指针
    QueuePtr rear; // 队尾指针
}LinkQueue;

链队初始化

Status InitQueue(LinkQueue &Q){
    Q.front = Q.rear = (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;
}

元素e入队

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, QElemType &e){
    if(Q.front == Q.rear)
        return ERROR;
    p = Q.front->next;
    e = P->data;
    Q.front->next = p->next;
    if(Q.rear == p)
        Q.rear = Q.front;
    free(p);
    return OK;
}

求队头元素

Status GetHead(LinkQueue Q, QElemType &e){
    if(Q.front == Q.rear)
        return ERROR;
    e = Q.front->next->data;
    return OK;
}