数据结构--队列

一.队列的定义

        和栈一样,队列是一种特殊的线性结构,同时对于不同类型的队列结构,有不同的限制。我们首先来看一下基本的队列是什么。跟栈一样,我们可以联想生活的情景,比如生活中排队,遵循的原则是“先进先出”/“后进后出”,这同时也和栈区分开来了。当然,既然是一种数据结构,那么老样子,各种队列类型都有顺序存储结构和链式存储结构。

二.单端队列

2.1 顺序单端队列:

        对于单端队列,其实也就是强调的遵循“先进先出”/“后进后出”的队列。要操作顺序结构的单端队列,可以定义队首“指针”与队尾“指针”(并非真正的指针,之前实现顺序表提过),队首负责出队,队尾负责入队(详见代码)。指的一提的是,对于队尾入队时,其实这个过程就是之前实现顺序栈入栈的操作,因此它也有两种写法。这个类型的队列很好理解,也比较好实现,不过它存在一个问题,就是“假溢出”的问题,队首不断出队,但队尾一直是往后面的数组空间添加,那么队首空出来的位置就浪费了,为解决这个问题,引出了循环队列的方法。

#include<stdio.h>
#include<stdlib.h>
#define maxsize 10

typedef struct{
    int* data;
    int f,r;//队首“指针”与队尾“指针”
}Squeue;

//初始化
Squeue initQueue()
{
    Squeue q;
    q.data=(int*)malloc(sizeof(int)*maxsize);
    if(q.data==0)
    {
        printf("空间分配失败\n");
    }
    else
    {
        q.f=q.r=0;//这里r是指向真正队尾元素的下一个位置 
        //另一种写法:
        // q.f=0;
        // q.r=-1;//这里r是指向真正队尾元素的位置
    }
    return q;
}

//入队
void Enqueue(Squeue *q,int k)
{
    //判满
    if(q->r==maxsize)//if(q->r==maxsize-1)//这里会存在“假溢出”的问题,因此引出循环队列
    {
        printf("队满,不能入队\n");
        return;
    }
    q->data[q->r]=k;
    q->r++;
    //q->data[q->r++]=k;
    //另一种写法对应:
    // q->r++;
    // q->data[q->r]=k;
    // //q->data[++q->r]=k

}

//出队
void Dequeue(Squeue *q)
{
    //判空
    if(q->f==q->r)//另一种写法对应:if(q->f=q->r+1)
    {
        printf("队空,不能出队\n");
        return;        
    }
    printf("队首%d出队\n",q->data[q->f]);//也是得到队首元素
    q->f++;
}
int main()
{
    Squeue q=initQueue();
    Enqueue(&q,1);
    Enqueue(&q,2);
    Enqueue(&q,3);
    Enqueue(&q,4);
    Dequeue(&q);
    Dequeue(&q);
    Dequeue(&q);
    return 0;
}

2.2 循环队列:

        循环队列是为解决顺序存储结构数组假溢出问题的改善结构,其实也很简单,就是把数组首尾通过数学取余的方法,逻辑上连接起来。具体实现过程如下:

#include<stdio.h>
#include<stdlib.h>
#define maxsize 10

typedef struct{
    int* data;
    int f,r;//队首“指针”与队尾“指针”
    //int flag //定义一个出队与入队标记,辅助判满判空。0为出队,1为入队
}Squeue;

//初始化
Squeue initQueue()
{
    Squeue q;
    q.data=(int*)malloc(sizeof(int)*maxsize);
    if(q.data==0)
    {
        printf("空间分配失败\n");
    }
    else
    {
        q.f=q.r=0;
    }
    return q;
}

//入队
void Enqueue(Squeue *q,int k)
{
    //判满:通过牺牲一个存储空间来判满
    if((q->r+1)%maxsize==q->f)//if(q->f==q->r&&q->flag==1)
    {
        printf("队满,不能入队\n");
        return;
    }
    q->data[q->r]=k;
    q->r=(q->r+1)%maxsize;
    //q->flag==1;

}

//出队
void Dequeue(Squeue *q)
{
    //判空
    if(q->f==q->r)//if(q->f==q->r&&q->flag==0)
    {
        printf("队空,不能出队\n");
        return;        
    }
    printf("队首%d出队\n",q->data[q->f]);//也是得到队首元素
    q->f=(q->f+1)%maxsize;
    //q->flag==0;

}
int main()
{
    Squeue q=initQueue();
    Enqueue(&q,1);
    Enqueue(&q,2);
    Enqueue(&q,3);
    Enqueue(&q,4);
    Dequeue(&q);
    Dequeue(&q);
    Dequeue(&q);
    return 0;
}

2.3 链式单端队列:

        链式存储结构的单端队列,是基于带头结点的单链表和一个尾指针实现的。头结点的好处不再多说,这里主要是通过头结点可以快速确定出队的元素,而尾指针就负责不断的入队。要注意的是,在进行出队操作时,队尾指针刚好为首元结点时,要更新尾指针的位置,将它移到头结点,不然会造成尾指针丢失的问题。

#include<stdio.h>
#include<stdlib.h>
//基于带头结点的单链表+尾指针
typedef struct Qnode{
    int data;
    struct Qnode* next;
}QNode;

typedef struct Queue{
    QNode* f;
    QNode* r;
}LinkQ;

//初始化
LinkQ initLinkQ()
{
    LinkQ q;
    q.f=(QNode*)malloc(sizeof(QNode));
    if(q.f==NULL)
    {
        printf("空间分配失败\n");
    }
    else
    {
        q.f->next=NULL;
        q.r=q.f;
    }
    return q;
}

//入队
LinkQ Enqueue(LinkQ q,int k)
{
    QNode* s=(QNode*)malloc(sizeof(QNode));
    if(s==NULL)
    {
        printf("空间分配失败,不能入队\n");
    }
    else
    {
        s->data=k;
        s->next=NULL;
        q.r->next=s;
        q.r=s;
    }
    return q;
}
//出队
LinkQ Dequeue(LinkQ q)
{
    if(q.r==q.f)//if(q.f->next==NULL)
    {
        printf("空间分配失败,不能出队\n");
        return q;
    }
    QNode* p=q.f->next;
    q.f->next=p->next;
    //队尾指针刚好为首元结点时,需要将它移到头结点
    if(q.r==p)
    {
        q.r=q.f;
    }
    printf("队首%d出队\n",p->data);//也是得到队首元素
    free(p);
    p=NULL;

}
int main()
{
    LinkQ q=initLinkQ();
    q=Enqueue(q,1);
    q=Enqueue(q,2);
    q=Enqueue(q,3);
    q=Enqueue(q,4);
    q=Dequeue(q);
    q=Dequeue(q);
    q=Dequeue(q);
    return 0;
}

三.双端队列

        队列结构还有一种特别灵活队列类型,那就是双端队列了,它允许我们可以从左右两端进行入队出队,唯一有特征的就是前端入队的元素一定排在后端入队元素的前面。

3.1 顺序双端队列:

        在实现顺序存储结构的双端队列,就犯难了。我们都知道,数组的位置和大小都是提前设置好的,那我们如何确定入队元素到数组哪个位置呢?这是我们就可以想到上面解决顺序单端队列“假溢出”的思路,我们如果像上面那样,讲数组“圈起来”,是不是就可以确定位置了。实际上确实是这样的,具体代码可以看下面;这里简单说一下代码的细节:假设我们前后端入队的操作都是一样的,以先插值后移"指针",那么必然会出现覆盖现象,先移动"指针"后插值就会出现空值现象。因此两端的入队方式要不同,同时通过size去记录元素个数,解决判满判空问题。其他就是关于左右两端“”指针是边界出现负数的解决了。

#include<stdio.h>
#include<stdlib.h>
#define maxsize 10
//基于循环数组实现顺序双端队列
//全局变量代替结构体
int *q=NULL;//指针模拟数组
int l,r;//左端右端
int size;

//初始化
void initQueue()
{
    q=(int *)malloc(sizeof(int)*maxsize);
    if(q==NULL)
    {
        printf("空间分配失败\n");
        return;
    }
    size=0;
    l=r=0;
}

//左边入队
void insert_left(int k)
{
    if(size==maxsize)
    {
        printf("队满,不能入队\n");
        return;
    }
    //先放数据后移指针
    q[l]=k;
    l=(l-1+maxsize)%maxsize;
    size++;
}

//左边出列
void delet_left()
{
    if(size==0)
    {
        printf("队空,不能出列\n");
        return;
    }
    int x=q[(l+1)%maxsize];
    printf("%d\n",x);
    l=(l+1)%maxsize;
    size--;
}
//右边入队
void insert_right(int k)
{
    if(size==maxsize)
    {
        printf("队满,不能入队\n");
        return;
    }
    //先移动指针,再添加数据
    r=(r+1)%maxsize;
    q[r]=k;
    size++;
}

//右边出队
void delet_right()
{
    if(size==0)
    {
        printf("队空,不能出列\n");
        return;
    }
    int x=q[r];
    printf("%d\n",x);
    r=(r-1+maxsize)%maxsize;
    size--;
}

//输出函数
void printff()
{
    int i=(l+1)%maxsize;
    while(i!=r)
    {
        printf("%d",q[i]);
        i=(i+1)%maxsize;
    }
    printf("%d\n",q[r]);
}
int main()
{
    initQueue();
    insert_left(1);
    insert_left(2);
    insert_left(3);

    insert_right(4);
    insert_right(8);

    printff();

    delet_left();

    delet_right();
    delet_right();
    

    printff();
    return 0;
}

3.2 链式双端队列:

        链式双端队列我们选择用带头结点的双向链表实现,熟练双向链表的操作,下面的代码也是比较简单的。这里唯一要注意的是,如果按照双向链表正常操作去进行入队出队时,会因为头结点无数据,我们在从左端将头结点右边的结点数据出队时,功能不实现。解决方法也很简单,就是给头结点存数据。比如代码中左端进行入队时,头结点插入入队数据,同时申请一个新的左端结点,将左指针移到新的结点,这个结点时无值的。这类似我们之前顺序栈入栈先插值后移指针的操作。

#include<stdio.h>
#include<stdlib.h>

//基于带头结点的双向链表实现链式双端队列
typedef struct linkQ{
    int data;
    struct linkQ* pre;
    struct linkQ* next;
}Node;

Node *l=NULL;
Node *r=NULL;

//初始化
void initqueue()
{
    l=(Node*)malloc(sizeof(Node));
    if(l==NULL)
    {
        printf("空间分配失败\n");
        return;
    }
    l->next=NULL;
    l->pre=NULL;
    r=l;
}

//左端入队
void insert_left(int k)
{
    Node* s=(Node*)malloc(sizeof(Node));
    if(s==NULL)
    {
        printf("空间分配失败,不能入队\n");
        return;
    }
    l->data=k;
    s->pre=l->pre;
    l->pre=s;
    s->next=l;
    l=s;

}

//左端出队
void delet_left()
{
    if(l==r)
    {
        printf("队空,不能出队\n");
        return;
    }
    int x=l->next->data;
    printf("%d出队\n",x);
    Node* p=l;
    l=p->next;
    l->pre=p->pre;
    free(p);
    p=NULL;
}

//右端入队
void insert_right(int k)
{
    Node* s=(Node*)malloc(sizeof(Node));
    if(s==NULL)
    {
        printf("空间分配失败,不能入队\n");
        return;
    }
    s->data=k;
    s->next=r->next;
    s->pre=r;
    r->next=s;
    r=s;
}

//右端出队
void delet_right()
{
    if(l==r)
    {
        printf("队空,不能出队\n");
        return;
    }
    int x=r->data;
    printf("%d出队\n",x);
    Node* p=r;
    r=p->pre;
    r->next=p->next;
    free(p);
    p=NULL;
}

//输出函数
void printff()
{
    Node *p=l->next;
    while(p!=NULL)
    {
        printf("%d",p->data);
        p=p->next;
    }
    printf("\n");
}
int main()
{
    initqueue();
    insert_left(1);
    insert_left(2);
    insert_left(3);

    insert_right(4);
    insert_right(8);

    printff();

    delet_left();

    delet_right();
    delet_right();
    

    printff();
    return 0;
}

四.优先队列

        最后提一下,关于队列还有一个优先队列的概念,这经常出现在算法题中。它就是在队列原则的基础上,加上一个优先级的性质。好比生活中军人优先--respec!这常常与堆联系起来。这里不做详细探讨,后面进行算法专栏总结时再细讲。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值