循环队列的8种基本运算

NOTICE: 代码测试没问题,直接复制就能跑!

环境:

Visual Stdio Code

 

阅读这篇文章之前你必须要记住一件事:Q.rear 指向的不是队尾元素,而是队尾元素的下一个位置!!

 

循环队列中经常遇到以下几种问题:

1.已知 Q.front, Q.rear 求元素个数 count;

2.已知 Q.front, count 求 Q.rear;

3.已知 Q.rear, count 求 Q.front。

公式如下:

1. count = (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;

2. Q.rear = (Q.front + count) % MAXQSIZE;

3. Q.front = (Q.rear - count + MAXQSIZE) % MAXQSIZE。

 

  我学习的时候还碰到了一个问题,书上(严蔚敏版《数据结构》)的循环队列存在一个问题:只能存储 MAXQSIZE - 1 个元素,他这样做的目的是:为了辨别队满和队空(可以看一下这二者的条件)。而为了存储 MAXQSIZE 个元素,我在结构体 SqQueue 中增加了一个 count 来存储当前队列的元素个数,这样队满的判断条件就成了:Q.count == MAXQSIZE ,队空的判断条件为:Q.count == 0 还能充分利用开辟出来的内存空间。

  改代码虽然很快乐,但是出了 BUG 就不太快乐了!我写的 BUG 是:当队满时,打印队列元素会出现空白,即没有打印任何元素,因为 QueueTraverse() 中 while 的终止条件为 Q.rear == i (i = Q.front)  因为当存储完第 MAXQSIZE 个元素时,Q.rear 会指向第一个元素,即:Q.front 指向的元素,这样以来压根没有进入循环,我之后又加了 if 判断,可还是行不通,当然了,除了这一点以外的所有代码都没有任何问题。

  如果哪位大佬有想法的话请一定要评论或者私信我一下,万分感谢!

源码如下:

#include
#include

#define MAXQSIZE 6   // 最大队列长度
#define OK 1
#define ERROR 0

typedef int Status;
typedef int QElemType;

typedef struct
{
    QElemType *base;      // 初始化的动态分配存储空间
    int front;            // 指向队头元素的指针
    int rear;             // 指向队尾元素的下一个位置的指针
    int count;            // 存放当前元素个数
}SqQueue;

Status InitQueue(SqQueue &Q)
{   // 初始化
    Q.base = (QElemType *)malloc(MAXQSIZE *sizeof(QElemType));
    if(!Q.base)  return ERROR;
    Q.front = Q.rear = 0;
    Q.count = 0;

    return OK;
}//InitQueue

int QueueLength(SqQueue &Q)
{   // 输出队列长度
    // return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;   // 利用取模得到队列长度,
    // 例如: Q.rear 指向第 7 个元素,Q.front 指向第 1 个元素,MAXQSIZE 为10,那么 return 的结果为:(7-1+10)%10 = 6
    return Q.count;
}//QueueLength

/* 注意: 按照书上那种写法写时,Q.front == Q.rear 为队空的判断条件,
(Q.rear + 1) % MAXQSIZE == Q.front 为队满的判断条件
如果只用 Q.front == Q.rear 作为队空、满的判断条件,就会产生二义性 */

/* 因为入队的判断条件为 (Q.rear + 1) %MAXQSIZE == Q.front
所以当 Q.rear = 5 的时候就会默认队满,而此时 逻辑位序 5 的位置是空的,
也就是说:留了一个空位来作为区分队满和队空的依据 
*/

Status EnQueue(SqQueue &Q, QElemType e)
{   // 将 e 入队
    if(Q.count == MAXQSIZE) return ERROR; // 队列满,之所以 Q.rear+1 是因为:逻辑位序与物理位序相差1
    Q.base[Q.rear] = e;
    Q.rear = (Q.rear + 1) % MAXQSIZE;   /* 之所以是循环队列就是这里的原因了。
    如果不用取模运算,假设 MAXQSIZE = 5, Q.rear = 4,此时如果再插入就会产生假溢出,如果写成取模形式的,就会将 Q.rear 置为0 */
    Q.count ++;

    return OK;
}//EnQueue

QElemType DeQueue(SqQueue &Q, QElemType &e)
{   // 用 e 返回删除掉的队头元素的值
    if(Q.count == 0)  return ERROR;  // 队空
    e = Q.base[Q.front];
    Q.front = (Q.front + 1) % MAXQSIZE;
    Q.count --;

    return e;
}//DeQueue

Status QueueEmpty(SqQueue &Q)
{   // 判断队列是否为空
    return Q.count == 0;
}//QueueEmpty

Status GetHead(SqQueue &Q, QElemType &e)
{   // 输出队头元素
    if(Q.count == 0)  return ERROR;    // 队空
    e = Q.base[Q.front];

    return OK;
}//GetHead

Status ClearQueue(SqQueue &Q)
{   // 清空
    Q.front = Q.rear = 0;
    Q.count = 0;

    return OK;
}//ClearQueue

Status QueueTraverse(SqQueue &Q)
{   // 打印队列元素
    int i = Q.front;    // 当入队 MAXSIZE 个元素后,Q.front 与 Q.rear 都指向队头元素,所以 +1 让他们有区分度,更好的打印
    if(Q.count == 0) return ERROR;  // 队空
    while(Q.rear != i)
    {
        printf("%d\t", Q.base[i]);
        i = (i + 1) % MAXQSIZE;
    }
    return OK;
}//QueueTraverse

Status DestroyQueue(SqQueue &Q)
{   // 销毁
    if(Q.base)
        free(Q.base);
    Q.base = NULL;
    Q.front = Q.rear = 0;
    free(&Q);   // 释放 Q 地址指向的内存空间

    return OK;
}//DestroyQueue

int main()
{
    int choice = 1;
    SqQueue Q;
    QElemType e;
    printf("请输入序号进行相应的操作:\n");
    while(choice)
    {    
        printf("\n1.InitQueue   2.QueueLength   3.QueueTraverse   4.GetHead   5.ClearQueue \n6.EnQueue   7.DeQueue   8.QueueEmpty   9.DestroyQueue   0.exit\n");
        scanf("%d", &choice);
        switch (choice)
        {
            case 1:
                if(InitQueue(Q))
                    printf("\n初始化队列成功!\n");
                else
                    printf("\n初始化失败!\n");
                break;
            case 2:
                if(QueueEmpty(Q))
                    printf("\n队列为空\n");
                else
                    printf("\n队列的长度为:%d\n", QueueLength(Q));
                break;
            case 3:
                if(QueueEmpty(Q))
                    printf("\n队列为空\n");
                else
                {
                    printf("\n队列元素为:\n");
                    QueueTraverse(Q);
                }
                break;
            case 4:
                if(!QueueEmpty(Q))
                {
                    GetHead(Q, e);
                    printf("\n队列的队头元素为:%d\n", e);
                }
                else
                    printf("\n队列为空\n");
                break;
            case 5:
                if(QueueEmpty(Q))
                    printf("\n队列为空!\n");
                else if(ClearQueue(Q))
                    printf("\n成功清空队列!\n");
                break;
            case 6:
                printf("\n请输入想要插入的的元素:\n");
                scanf("%d", &e);
                if(EnQueue(Q, e))
                    printf("\n插入成功!\n");
                else
                    printf("\n插入失败!队列已满!\n");
                break;
            case 7:
                if(QueueEmpty(Q))
                    printf("\n队列为空!\n");
                else
                    printf("\n删除的队头元素为:%d\n", DeQueue(Q, e));
                break;
            case 8:
                if(QueueEmpty(Q))
                    printf("\n队列为空!\n");
                else
                    printf("\n队列不为空!\n");
                break;
            case 9:
                if(DestroyQueue(Q))
                    printf("\n销毁成功\n");
                else
                    printf("\n销毁失败!\n");
                break;
            case 0:
                choice = 0;
                break;
            default:
                printf("\n程序出错!\n");
                break;
        }
    }
    DestroyQueue(Q);
    return 0;

}

THE END!

你可能感兴趣的:(数据结构)