数据结构与算法6--队列

457 阅读4分钟

队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。 也就是和我们显示排队一样:先进先出

假设我们开辟一个存储大小为5个内存单元的队列,如上图所示。 当front 和 rear指向同一个元素时,队列为空;当入队时,rear向后移动一位,指向新的元素;当出队时,front向下移动一位;当front = 0,rear = 4时,表示队列已满。以上情况貌似都没有问题,但是…… 如果出现最后一种情况,如(d)所示,队列中其他元素都出队了,只能一个元素,这时front和rear又指向了同一个元素,并且队列此时又不为空,而且前面出队的内存空间也不能再利用。该怎么办?

如果我们把队列设计成贪吃蛇效果,首尾相接,循环效果,就没有明确的队尾的位置了,如下图

  1. 当front和rear指向同一个元素,队列为空
  2. a,b,c入队时,front不动,rear依次指向1,2,3 位置,最终指向3的位置
  3. a出队时,rear不动,front指向下一个队首b,但是此时front=1,而不再是从0开始,一边出队一边入队,那么front的位置就会是0,1,2,3,4,5 然后利用取模运算front = (front+1) % max,front又回到0,然后1。 使得每次front的位置可以在队尾之后继续回到标号从0的位置继续往后走,周期循环。同理,rear,新增到rear = 5时,也利用取模运算,新的数据从标号为0开始继续入队,实现循环队列。
  4. 队满时,如下图,当front=rear,我们是无法判断是队满还是队空的, 但是我们已经用循环解决当只有最后一块存储单元有元素而不能再继续入队的问题。

我们可以牺牲一个front前的存储单元,用来保持队尾和队首的距离,来解决最后一个问题:判断队满,(Q.rear+1) % Q.max == Q.front , 如果条件成立,意味着空的下一个位置就是队首front,此时队已满。

循环队列总结

  1. 当front=rear=0,队列为空
  2. 入队,循环取余,rear = (rear + 1)% max
  3. 出队,循环取余,front = (front + 1)% max
  4. 队满,front = (rear + 1)% max

代码如下

/* 定义一个循环队列的顺序存储结构 */
typedef struct
{
    ElementType data[MAXSIZE];
    int front;        /* 头指针 */
    int rear;        /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SQueue;


// 创建一个空队列,front = rear = 0
Status CreateQueue(SQueue *Q){
    Q->front = Q->rear = 0;
    return OK;
}

// 清空队列
Status clearQueue(SQueue *Q){
    Q->front = Q->rear = 0;
    return OK;
}

// 判断队列是否为空
Status isEmptyQueue(SQueue Q){
    if (Q.front == Q.rear) {
        return TRUE;
    }
    
    return FALSE;
}

// 判断队列是否已满
Status isFullQueue(SQueue Q){
    if (Q.front == (Q.rear + 1) % MAXSIZE) {
        return TRUE;
    }
    
    return FALSE;
}

// 返回队列的个数
int queueLength(SQueue Q){
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

// 返回队头元素
Status getHead(SQueue Q, ElementType *data){
    // 如果空队列,没有队头元素,返回error
    if (isEmptyQueue(Q)) {
        return ERROR;
    }
    
    *data = Q.data[Q.front];
    
    return OK;
}

// 入队
Status inQueue(SQueue *Q, ElementType data){
    // 如果队列已满,不能插入新的元素
    if (isFullQueue(*Q)) {
        return ERROR;
    }
    
    // 将元素赋值给队尾
    Q->data[Q->rear] = data;
    
    // 将rear移位,循环取余
    Q->rear = (Q->rear + 1) % MAXSIZE;
    
    return OK;
}

// 出队
Status outQueue(SQueue *Q, ElementType *deleteData){
    // 如果队列为空,返回error
    if (isEmptyQueue(*Q)) {
        return ERROR;
    }
    
    // 删除队头的元素
    *deleteData = Q->data[Q->front];
    
    // front移动一位,循环取余
    Q->front = (Q->front + 1) % MAXSIZE;
    return OK;
}

// 打印队列的元素
Status printQueue(SQueue Q){
    int i = Q.front;
    while ((i + Q.front) != Q.rear) {
        printf("%d ",Q.data[i]);
        i = (i + 1) % MAXSIZE;
    }
    
    printf("\n");
    return OK;
}


int main(int argc, const char * argv[]) {
    // insert code here...
    printf("顺序队列表示与操作实现\n");
    
    SQueue Q;
    
    // 创建队列
    createQueue(&Q);
    printf("初始化队列后,队列空否?%u  (1:空 0:否)\n",isEmptyQueue(Q));
    
    printf("入队:\n");
    int i = 0;
    while (i < 10) {
        inQueue(&Q, (i + 1));
        i++;
    }
    
    // 打印队列
    printQueue(Q);
    
    printf("入队后,队列的长度%d,队列空否?%u  (1:空 0:否)\n\n",queueLength(Q),isEmptyQueue(Q));
    
    // 出队
    ElementType deleDate;
    outQueue(&Q, &deleDate);
    printf("出队的元素:%d\n\n",deleDate);
    
    // 获取队头元素
    ElementType headData;
    if (getHead(Q, &headData)) {
        printf("队头元素:%d\n",headData);
    };
    
    // 清空队列
    clearQueue(&Q);
    
    printf("清空队列后,队列空否?%u  (1:空 0:否)\n\n",isEmptyQueue(Q));
    
    
    return 0;
}

以上操作打印执行结果如下