Bootstrap

数据结构:栈和队列(c语言)

一、栈和队列基本介绍

栈和队列是两种重要的线性结构。从数据结构角度来看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,他们是操作受限制的线性表,因此,可称为限定性的数据结构。但从数据类型角度看,它们是和线性表大不相同的两类重要的抽象数据类型。由于它们广泛应用在各种软件系统中,因此在面向对象的程序设计中,它们是多型数据类型。

二、栈

栈是限定仅在表尾进行插入或者删除操作的线性表。因此,对栈来说,表尾端有特殊含义,称为栈顶,相应的,表头称为栈底。不含元素的空表称为空栈。

栈的修改时按后进先出的原则进行的,因此,栈又称为后进先出的线性表。栈的基本操作除了栈顶进行插入或者删除外,还有栈的初始化、判空、以及取栈顶元素等。

1、栈的表示和实现

和线性表类似,栈也有两种存储表示方法。

顺序栈,即栈的顺序存储结构是利用一组地址连续的存储单元依次放自栈底到栈顶的数据元素,同时设指针top指示栈顶元素在顺序栈中的位置。另一方面,由于栈在使用过程中所需最大空间的大小是很难估计,因此,一般来说,在初始化设空栈时不应限定栈的最大容量,然后再应用过程中,当栈的空间不够使用时再逐段扩大。因此,可以设定两个常量:STACK_INIT_SIZE(存储空间初始化分配量)和STACKINCREMENT(存储空间分配增量)。

顺序栈的定义

typedef struct{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack

其中,stacksize指示栈的当前可用最大容量。栈的初始化操作为:按设定的初始化分配量进行第一次存储分配,base可称为栈底指针,在顺序栈中,它始终指向栈底的位置,若base的值为NULL,则表明栈结构不存在。称top为栈顶指针,其初值指向栈底,即top=base可作为栈空的标记,每当插入心的栈顶元素时,指针top增加1,删除栈顶元素时,指针top减1,因此,非空栈中的栈顶指针始终在栈顶元素的下一个位置上。

基本操作算法描述

(1)构造一个空栈

Status InitStack(SqStack &S){
    //构造一个空栈S
    S.base = (SElemType *)malloc(STACK_INIT_SIZE *sizeof(SElemType));
    if(!S.base) exit(OVERFLOW);//存储分配失败
    S.top = S.base;
    S.stacksize = STACK_INIT_SIZE;
}

(2)获取栈的栈顶元素

Status GetTop(SqStack S,SElemType e){
//若栈不空,则用e返回S的栈顶元素,并返回OK,否则返回ERROR
    if(S.top==S.base) return ERROR;
    e = *(S.top-1);
    return OK;
}

(3)插入元素

Status Push(SqStack &S,SElemType){
//插入元素e为新的栈顶元素
    if(S.top-S.base>=S.stacksize){
//栈满,追加存储空间
    S.base = (SElemType *)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(SElemType));
    if(!S.base) exit(OVERFLOW);//存储分配失败
    S.top = S.base+S.stacksize;
    S.stacksize +=STACKINCREMENT;
    }
    *S.top++=e;
    return OK;
}

(4)删除栈顶元素

Status Pop(SqStack &S,SElemType &e){
//若栈不为空,则删除S的栈顶元素,用e返回其值,并返回Ok,否则返回Error
    if(S.top==S.base) return ERROR;
    e = *--S.top;
    return OK;
}

栈的链式表示:由于栈的操作是线性表操作的特例。则链栈的操作容易实现。

三、队列

1、抽象数据类型队列的定义

和栈相反,队列是一种先进先出的线性表。它只允许在表的一端进行插入,而在另一端删除元素。在队列中,允许插入的一端叫做队尾,允许删除的一端则称为对头。

除了栈和队列之外,还有一种限定的数据结构是双端队列。

双端队列是限定插入和删除操作在表的两端进行的线性表,这两端分别称为端点1和端点2.

2、链队列--队列的链式表示和实现

和线性表类似,队列也有两种存储方式。用链表表示的队列称为链队列。一个链队列显然需要两个分别指示对头和队尾的指针(分别简称头指针和尾指针)才能唯一确定。这里,和线性表的单链表一样,为了操作方便起见,我们也给链队列添加一个头结点,并令头指针指向头结点。由此,空的链队列的判断条件为头指针和尾指针均指向头结点。

链队列的操作为单链表的插入和删除操作的特殊情况,只是需要修改尾指针或者头指针。

单链队列--队列的链式存储结构

typedef struct QNode{
    QElemType data;
    struct QNode *next;
}QNode,*QueuePtr;
typedef struct{
    QueuePtr front;//队头指针
    QueuePtr rear;//队尾指针
}LinkQueue;

基本操作的算法描述

(1)初始化链队列

Status InitQueue(LinkQueue &Q){
//构造一个空队列Q
    Q.front = Q.rear=(QueuePtr)malloc(sizeof(QNode));
    if(Q.front) exit (OVERFLOW);//存储分配失败
    Q.front->next = NUll;
    return OK;
}

(2)销毁队列

Status DestoryQueue(LinkQueue &Q){
//销毁队列Q
    while(Q.front){
    Q.rear = Q.front->next;
    free(Q.free);
    Q.free= Q.rear;
}
    return OK;
}

(3)插入元素

Status EnQueue(LinkQueue &Q,QElemType){
//插入元素e为Q的新的队尾元素
    p = (QueuePtr)malloc(sizeof(QElemType));
    if (!p) exit(OVERFLOW);//存储分配失败
    p->data = e;p->next = NULL;
    Q.rear->next = p;
    Q.rear = p;
    return OK;
}

(4)删除元素

Status DeQueue(LinkQueue &Q,QElemType &e){
//若队列不空,则删除Q的队头元素,用e返回其值,并返回Ok;
    if(Q.front==Q,rear) return ERROR;
    p = Q.front->next;
    e = p->data;
    Q.front->next = p->next;
    if (Q.rear=p) Q.front=Q.rear;
    free(p);
    return OK;
    

}

在上面的算大描述中,删除队列头元素的算法中的特殊情况,一般情况下,删除队列元素时需要修改头结点中的指针,但是当队列中最后一个元素被删除后,队列中的尾指针也丢失了,因此需要对队尾指针重新赋值(指向头结点)。

3、循环队列--队列的顺序表示和实现

和顺序栈类似,在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素之外,尚需设置两个指针front和rear分别指示队列元素以及队尾元素的位置。为了在c语言中描述方便起见,在此我们约定:初始化建立空队列时,令font=rear=0,每当插入新的队列尾元素时,尾指针增加1,每当删除队列头元素时,头指针增加1.因此在非空队列中,头指针始终指向队列头元素,而尾指针始终指向队列尾元素的下一个位置。

假设当前为队列分配的最大空间为6,则当队列到了存储位置的尾部时,则不可再继续插入心的队尾元素了,否则会因为数组越界而使得代码被破坏。然而此时又不合适的像顺序栈那样,进行存储再分配扩大数组空间,因为队列的实际可用空间并未被占满。因此,可以将顺序队列构造成一个环状的空间称为循环队列。在这种结构中,如果仅仅凭借Q.front=Q.rear是无法判定队列空间是空还是满的。因此有两种处理方法:其一是另设一个标志位用来区分队列是空还是满,其二是少用一个元素空间,约定以队列头指针在队列尾指针的下一个位置则作为队列呈现满的状态的标志。

从上述分析可见,在c语言中不能用动态分配的一维数组来实现循环队列。如果用户的应用程序中设有循环队列,则必须为它分配一个最大队列长度,若用户无法预估所用队列的最大长度,则采用链队列。

循环队列--顺序存储结构

#define MAXSIZE 100//最大对列长度
typedef struct{
    QElemType *base;//初始化的动态分配存储空间
    int front;//头指针,若队列不空,指向队列头元素
    int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置
}SQueue;

循环队列的基本操作的算法描述

(1)初始化队列

Status InitQueue(SqQueue &Q){
//构造一个空队列Q
    Q.base = (QElemType *)malloc(MAXSIZE *sizeof(QElemType))
    if(!Q.base) exit(OVERFLOW);//存储分配失败
    Q.front = Q.rear=0;
    return OK;
}

(2)获取队列的长度

int QueueLength(SqQueue &Q){
//返回Q的元素个数,即队列的长度
    return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

(3)插入元素

Status EnQueue(SqQueue &Q,QElemType){
    //插入元素e为Q的新的队尾元素
    if((Q.rear+1)%MAXSIZE==Q.front) return Error;//队列满
    Q.base[Q.rear] = e;
    Q.rear = (Q.rear+1)%MAXSIZE;
    return OK;
}

(4)删除元素

Status DeQueue(SqQueue &Q,QElemType &e){
//若队列不空,则删除Q的队头元素,用e返回其值,并返回ok;
    if(Q.front==Q.rear) return Error;
    e = Q.base[Q.front];
    Q.front = (Q.front+1)%MAXSIZE;
    return OK;
}

 

;