数据结构之队列(C语言版)

队列(Queue)

队列:是只允许在一端进行插入操作,而在另一端进行删除操作。(可以是表头,也可以是表尾)

我们可以看到,队列其实也是线性表的一种,队列的意思其实就和他的名字一样,他的存储特点就像一个一堆数据排队一样,先排到队里面的数据就先出来(First In First Out),简称FIFO。允许插入的一段为表尾,允许删除的一端称为表头。

operation //常用操作
    InitQueue(*Q) //初始化操作,建立一个空队列Q。
    DestroyQueue(*Q) //若队列Q存在,则销毁它。
    ClearQueue(*Q) //将队列Q清空。
    QueueEmpty(Q) //若队列Q为空,返回true,否则返回false。
    GetHead(Q,*e) //若队列Q存在且非空,用e返回队列Q的队头元素。
    EnQueue(*Q,e) //若队列Q存在,插入新元素e到队列Q中并成为队尾元素。
    DeQueue(*Q,*e) //删除队列Q中队头元素,并用e返回其值。
    QueueLength(Q) //返回队列Q的元素个数。

队列的顺序存储结构

因为队列也是线性表的一种,所以如果队列要存储 n 个元素,就要创建一个长度为 n+1 的数组。

我们的入队列操作就是在队尾添加一个元素很明显,其时间复杂度为O(1);但是出队列就不同了,在这里可以想到两种方法。方法一:执行出队列操作时,删除队头元素,并且把后面的元素向前移动,他的时间复杂度为O(n),我们可以发现,他并不是一个好方法,他的时间复杂度太高了,所以我们有了方法二:只删除队头操作,不进行移动,诶~这时间复杂度就好看多了O(1)

但是我们又遇到了一个新的问题,那就是,我们的元素总要进进出出吧,当我们的队尾位置是数组的最后一个位置时,就无法进行进队列的操作了,但是,我们的队头不一定就是数组的起始位置啊,可能排在老后面呢,这样的话,就存在空间的浪费了。

解决这个问题的办法其实很简单,就是吧这个数组从一条线,看成一个圆,里面的下标是循环的,假设有一个大小为5的数列,那么我们可以把它看成:0,1,2,3,4,0,1,2,3,4,0······这样,就很完美的解决了空间浪费的问题。

还有一个小问题,如果是循环队列,要怎样确定它存满了呢?假设我们把队头取名为(front),队尾的下一个位置为(rear),则当 front == rear 时,表示队列为空。这里又两种方法:
①创建一个 flag 变量,有元素时为1,没有元素时为0,当 front == rear 时,如果 flag 为1,则队列满;否则队列空。
②在队尾留一个空位置,意思是存满时其实只存了数组大小减1个元素,这时,存满的标识即为 (rear+1)%arrayLength == front (这里的arrayLength指数组的大小)

我们着重介绍方法二,队列的结构为:

#define OK 1
#define ERROR 0
#define TURE 1
#define FALSE 0
typedef int Status;
/*Status是函数的类型,其值是函数结果状态代码,如OK等*/
typedef int QelemType;
/* 这是循环队列的顺序存储结构 */
typedef struct{
    QElemType data[MAXSIZE];
	int front;
    int rear;
}SqQueue;

初始化

Status InitQueue(SqQueue *Q){
    Q->front = 0;
    Q->rear = 0;
    return OK;
}

求长度

int QueueLength(SqQueue Q){ // 返回队列的长度
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

入队列

Status EnQueue(SqQueue *Q,QElemType e){
    if((Q->rear + 1) % MAXSIZE == Q->front) // 先判断队列是否已满
        return ERROR;
    Q->data[Q->rear] = e;
    Q->rear - (Q->rear+1) % MAXSIZE; // rear指针后移
   	return OK;
}

出队列

Status DeQueue(SqQueue *Q,QElemType *e){
	if(Q->front == Q->rear)
        return ERROR
	*e = Q->data[Q->front];
    Q->front = (Q-front + 1) % MAXSIZE;
    return OK;
}

他们的时间复杂度都为O(1),同样顺序存储结构的缺点就是他们的长度在一开始就已经确定了,如果数据过多,也无法在后期增加长度了。所以,它的好兄弟链式存储结构,他来了。

队列的链式存储结构

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出,我们把它简称为链队列。在链队列中, front 指向队列的头节点, rear 指向终端结点,当队列为空时, front 和 rear 都指向头节点。(注意头节点与头指针之间的异同)

#define OK 1
#define ERROR 0
#define TURE 1
#define FALSE 0
typedef int Status;
typedef int QElemType;

typedef struct QNode{
    QElemType data;
    struct QNode *next;
}QNode,*QueuePtr;

typedef struct {
	QueuePtr front,rear; /* 定义头尾指针 */
}LinkQueue;

入队列操作

Status EnQueue(LinkQueue *Q,QElemtype e){
    QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
    if(!S)
        exit(OVERFLOW);
    s->data = e; 
    s->next = NULL;
    Q->rear->next = s;
    Q->rear = s;
    return OK;
}

出队列操作

Status DeQueue(LinkQueue *Q,QElemType *e){
    QueuePtr p;
    if(Q->front == Q->rear)
        return ERROR;
    p = Q->front->next;
    *e = p->data;
    Q->front->next = p->next;
    if(Q->rear == p)
        Q->raer = Q->front;
    free(p);
    return OK;
}

可以看出,它们的时间复杂度都为O(1)。

总结

首先,队列的性质我们要清楚,就是一端进,另一端出。

其次,对比它的两种存储结构。我们可以发现,这两种存储结构的基本操作的时间复杂度都为O(1),他们的主要区别就是存储空间的不同,对于顺序存储结构,他在一开始就固定了存储空间大小的,所以它的最多存放的数据是固定多的,但是链式存储结构则在理论上可以存储无限个数据,但因为每个结点需要存放下一个结点的地址,所以存储相同数据使用的大小要比顺序存储空间多。我们应视情况而考虑用哪一种存储结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值