数据结构之栈与队列

一、栈

(一)栈的结构

用数组作为栈的结构存储数据,使用结构体用于约束数组,在结构体中定义两个指针指向栈的栈顶top和栈底base,要求只能从栈顶入栈和出栈(只能从栈的一端操作);定义一个整型变量stacksize用于表示当前栈的容量(可以存放数据的最大数量)。

typedef int SElemType;
typedef int Status;
typedef struct{
    SElemType *base;//栈底
    SElemType *top;//栈顶
    int stacksize;//栈容量
}SqStack;

(二)初始化栈

Status InitSqStack(SqStack *S),传入参数为结构体指针(需要为结构体申请空间,所以需要传指针),在堆区动态分配一块连续内存作为栈的存储空间,并让栈顶指针和栈底指针都指向该空间的起始位置,表示当前栈为空

//初始化
Status InitSqStack(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;
}

(三)入栈

Status Push(SqStack *S,SElemType e),将数据e入栈:先判断栈是否满了,如果是满栈则需要使用realloc对其扩容。realloc扩容如果后面有足够的空间可以实现原地扩展(直接在后面扩容),如果后面空间不够则需要另外开辟一段更大的空间,逐字节拷贝过去并自动释放旧内存。所以扩容后需要更新栈顶指针与容量大小。最后将入栈元素赋值给栈顶指针,赋值后更新栈顶指针(指向入站元素的下一个位置)。

//入栈
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;//先赋值后移动top指针
    return OK;
}

(四)出栈

Status Pop(SqStack *S,SElemType *e),出栈并将出栈数据存入e中:判断是否为空栈,非空则先将栈顶指针移动到栈顶元素,再将其存入e。

//出栈
Status Pop(SqStack *S,SElemType *e){
    if(S->base == S->top) return ERROR; //空栈无元素
    e=*--S->top; //先将栈顶指针移动到栈顶元素,再将其赋给e
    return OK;
}

(五)查看栈顶指针

Status GetTop(SqStack *S,SElemType *e),查看栈顶指针并存入e:先判断是否为空栈,若非空则将栈顶指针的前一个元素(栈顶元素)的数据赋给e。

//查看栈顶元素
Status GetTop(SqStack *S,SElemType *e){
    if(S->base == S->top) return ERROR; //空栈无元素
    e=*(S->top -1); //先将栈顶指针的前一个元素赋给e(不改变栈顶指针)
    return OK;
}

(六)判空

比较栈底指针和栈顶指针是否指向同一位置,是则为空栈,否则非空。

//判空
Status isEmpty(SqStack *S){
    return  S->base == S->top;
}

(七)清空

移动栈顶指针,使其和栈底指针指向同一位置(不需要处理数据),则表示已清空。

//清空
void ClearLine(SqStack *S){
    while(S->top != S->base) --S->top;
}

完整测试代码:

#include <stdio.h>
#include <stdlib.h>
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
#define OK 1
#define ERROR 0
#define OVERFLOW -1
typedef int SElemType;
typedef int Status;
typedef struct{
    SElemType *base;//栈底
    SElemType *top;//栈顶
    int stacksize;//栈容量
}SqStack;

//初始化
Status InitSqStack(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;
}

//入栈
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;//先赋值后移动top指针
    return OK;
}

//出栈
Status Pop(SqStack *S,SElemType *e){
    if(S->base == S->top) return ERROR; //空栈无元素
    *e=*--S->top; //先将栈顶指针移动到栈顶元素,再将其赋给e
    return OK;
}

//查看栈顶元素
Status GetTop(SqStack *S,SElemType *e){
    if(S->base == S->top) return ERROR; //空栈无元素
    *e=*(S->top -1); //先将栈顶指针的前一个元素赋给e(不改变栈顶指针)
    return OK;
}

//判空
Status isEmpty(SqStack *S){
    return  S->base == S->top;
}

//清空
void ClearLine(SqStack *S){
    while(S->top != S->base) --S->top;
}

int main(){
    SqStack S;
    int e=0;
    InitSqStack(&S);
    Push(&S,1);
    Push(&S,2);
    Push(&S,3);
    Push(&S,4);
    printf("依次入栈:");
    for(int i=0;S.base+i<S.top;i++){
        printf("%d ",*(S.base+i));
    }
    Pop(&S,&e);//出栈
    printf("\n出栈:%d\n",e);
    printf("出栈顺序:");
    for(int i=1;S.top-i>=S.base;i++){
        printf("%d ",*(S.top-i));
    }
    if(isEmpty(&S)) printf("\n栈空\n");
    else {
        printf("\n栈非空,需要清空\n");
        ClearLine(&S);
        if(isEmpty(&S)) printf("已清空!\n");
    }
}

二、队列

(一)队列的结构

循环队列,使用数组存储数据,使用结构体进行约束。结构体中有两个整型变量分别用于表示队头与队尾的下标,一个指针指向存储数据的数组首地址。

#define MAXSIZE 100
typedef int ElemType;
typedef int Status;
typedef struct{
    ElemType *base; //指针指向数组
    int front;//队头下标
    int rear;//队尾下标
}SqQueue;

(二)初始化

在堆区动态分配一块连续内存作为队列的存储空间,并将队头指针front和队尾指针rear都初始化为 0,表示这是一个空队列

Status InitQueue(SqQueue *Q){
    Q->base=(ElemType *)malloc(MAXSIZE*sizeof(ElemType));
    if(!Q->base) exit(OVERFLOW);
    Q->front=Q->rear=0;
    return OK;
}

(三)入队

先检查队列是否已满(检查队尾下标的下一个位置是否等于队头下标,使用取模运算%MAXSIZE实现指针的循环移动)。将新元素e存入队尾rear,此时rear的值为下一个空闲位置(即队尾)。将队尾下标rear加一,使用取模运算实现循环:当rear到达数组末尾时,自动回到数组开头,更新后的rear表示新的空闲位置的下标。

//入队
Status EnQueue(SqQueue *Q,ElemType e){
    if((Q->rear+1)%MAXSIZE == Q->front) return ERROR;//队满
    Q->base[Q->rear]=e;  //入队赋值
    Q->rear=(Q->rear+1)%MAXSIZE;  //更新队尾下标
    return OK;
}

(四)出队

先检查队列是否为空(检查队头下标是否等于队尾下标)。将队头指针front所指向的元素值取出并保存到e中。将队头下标front加一,使用取模运算%MAXSIZE实现循环:当front到达数组末尾时,自动回到数组开头,更新后的front表示新的队头位置。

//出队
Status DeQueue(SqQueue *Q,ElemType *e){
    if(Q->front == Q->rear) return ERROR;//队空
    *e=Q->base[Q->front];
    Q->front=(Q->front+1)%MAXSIZE;
    return OK;
}

(五)查看队首元素

先检查队列是否为空(检查队头下标是否等于队尾下标)。将队头指针front所指向的元素值取出并保存到e中。由于只是查看元素而不删除它,因此队头指针front保持不变,不需要移动。

//查看队首元素
Status GetFront(SqQueue *Q,ElemType *e){
    if(Q->front == Q->rear) return ERROR;//队空
    *e=Q->base[Q->front];
    return OK;
}

(六)判断队列是否为空

//判断队列是否为空
Status isEmpty(SqQueue Q){
    return Q.front==Q.rear;
}

顺序队列测试代码:

#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define OVERFLOW -1

//队列的基本操作
#define MAXSIZE 100
typedef int ElemType;
typedef int Status;
typedef struct{
    ElemType *base; //指针指向数组
    int front;//队头下标
    int rear;//队尾下标
}SqQueue;

//初始化
Status InitQueue(SqQueue *Q){
    Q->base=(ElemType *)malloc(MAXSIZE*sizeof(ElemType));
    if(!Q->base) exit(OVERFLOW);
    Q->front=Q->rear=0;
    return OK;
}

//入队
Status EnQueue(SqQueue *Q,ElemType e){
    if((Q->rear+1)%MAXSIZE == Q->front) return ERROR;//队满
    Q->base[Q->rear]=e;  //入队赋值
    Q->rear=(Q->rear+1)%MAXSIZE;  //更新队尾下标
    return OK;
}

//出队
Status DeQueue(SqQueue *Q,ElemType *e){
    if(Q->front == Q->rear) return ERROR;//队空
    *e=Q->base[Q->front]; 
    Q->front=(Q->front+1)%MAXSIZE;
    return OK;
}

//查看队首元素
Status GetFront(SqQueue *Q,ElemType *e){
    if(Q->front == Q->rear) return ERROR;//队空
    *e=Q->base[Q->front];
    return OK;
}

//判断队列是否为空
Status isEmpty(SqQueue Q){
    return Q.front==Q.rear;
}

int main(){
    SqQueue Q;
    int e=0;
    InitQueue(&Q);
    EnQueue(&Q,1);
    EnQueue(&Q,2);
    EnQueue(&Q,3);
    printf("队列元素:");
    for(int i=0;i<Q.rear;i++){
        printf("%d ",Q.base[i]);
    }
    DeQueue(&Q,&e);
    printf("\n出队:%d",e);
    if(isEmpty(Q)) printf("\n队空\n");
    else {
        printf("\n队非空\n");
    }
    return 0;
}

链队列测试代码:

#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define OVERFLOW -1

//链表队列的基本操作
typedef int ElemType;
typedef int Status;
typedef struct QNode{  //链队列结点
    ElemType 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 EnQueue(LinkQueue *Q,ElemType e){
    QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
    if(!p) exit(OVERFLOW);
    p->data=e;
    p->next=NULL;
    Q->rear->next=p;
    Q->rear=p;
}

//出队
Status DeQueue(LinkQueue *Q,ElemType *e){
    if(Q->front == Q->rear) return ERROR;//空队
    QueuePtr p=Q->front->next;//队头结点
    *e=p->data;
    Q->front->next=p->next;
    //如果需要删除的结点为队尾结点,Q->front->next=p->next=NULL
    if(p == Q->rear) Q->rear=Q->front;  //置空
    free(p);
    return OK;
}

//查看队首元素
Status GetFront(LinkQueue *Q,ElemType *e){
    if(Q->front == Q->rear) return ERROR;//空队
    *e=Q->front->next->data;
    return OK;
}

int main(){
    LinkQueue Q;
    int e=0;
    InitQueue(&Q);
    for (int i = 1; i <= 5; i++) {
        printf("入队: %d\n", i);
        EnQueue(&Q, i);
    }
    GetFront(&Q, &e);
    printf("队首元素: %d\n", e);
    for (int i = 0; i < 3; i++) {
        DeQueue(&Q, &e);
        printf("出队: %d\n", e);
    }
    return 0;
}

链队列与顺序队列有什么区别?各自的优缺点?

(1)链队列与顺序队列的本质区别在于存储方式:

顺序队列使用连续的内存空间(数组)存储元素,通过队头指针front和队尾指针rear的下标移动实现入队出队。

链队列则使用不连续的链表结点存储元素,通过front指针指向头结点、rear指针指向尾结点实现入队出队。

(2)顺序队列的优点是访问速度快(连续存储)、实现简单、无指针额外开销;缺点是队列容量固定(静态数组)或需要动态扩容(动态数组),且为了区分队空和队满通常需要牺牲一个存储单元,存在空间浪费。

(3)链队列的优点是容量动态增长,不会因队列满而溢出(除非内存耗尽),插入删除操作只需修改指针无需移动数据;缺点是每个结点需要额外的指针域存储空间,访问速度相对较慢(不连续存储),且频繁的malloc/free可能导致内存碎片。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值