栈与队列(二)

在上一篇文章中,我们介绍了自定义的链式栈结构及其接口的实现方式。这篇文章里,我们来介绍如何实现自定义的顺序队列。

顺序队列结构定义

在顺序队列中,我们采用一维数组进行存储队列元素,为充分利用内存空间,我们采取 循环队列 形式对元素进行组织管理。

图1. 循环队列结构

我们首先给出顺序结构的定义及其接口声明,如下代码所示:

#ifndef _SQQUEUE_H_
#define _SQQUEUE_H_

#include <cstdio>
#include <cstdlib>
#include <cstring>

typedef int POSITION;

struct stQueue {
    size_t MAXNUM;
    size_t length;
    ELEMTYPE *buffer;
    POSITION head, rear;
};

typedef struct stQueue * Psqueue;

Psqueue create(int num);
bool enqueue(Psqueue queue, ELEMTYPE x);
bool dequeue(Psqueue queue);
bool front(Psqueue queue, ELEMTYPE &x);
bool clear(Psqueue queue);
bool length(Psqueue queue);
bool isEmpty(Psqueue queue);
bool isFull(Psqueue queue);
bool destroy(Psqueue &queue);
bool print(Psqueue queue);
#endif

sqqueue.h 头文件中,给出了队列的结构体定义,该结构包含如下结构体成员变量:

  • MAXNUM:表示队列元素缓冲区长度,亦即表示队列最多可容纳元素个数;
  • length:表示队列中当前元素个数,length <= MAXNUM;
  • buffer:表示队列用于存储元素的缓冲区首地址;
  • head:表示队头在缓冲区的下标;
  • rear:表示队尾在缓冲区的下标;

判断循环队列 队满队空 条件分别为:

  • 队满:(rear + 1)%MAXNUM == head
  • 队空:head == rear

或者根据 stqueue 结构体中 lengthMAXNUM的关系进行判断:

  • 队满:length == MAXNUM
  • 队空:length == 0

顺序队列各接口实现

下面我们来依次介绍顺序循环队列的各接口实现。

创建顺序队列-create接口

创建顺序队列操作如下图所示,可以分为两步:

图2. 创建顺序队列
  1. 分配顺序队列结构体空间,并初始化结构体成员;
  2. 分配顺序队列元素存储空间;

分配顺序队列结构体以及分配顺序表元素存储空间,均通过动态内存分配函数 malloc 进行操作即可;此外,初始化结构体成员亦比较简单,length 初始化为 0headrear也均初始化为 0

#include "sqqueue.h"
#include <iostream>
using namespace std;

Psqueue create(int num)
{
    Psqueue queue = (Psqueue) malloc(sizeof(struct stQueue));

    if (queue != NULL)
    {
        queue->buffer = (ELEMTYPE *) malloc(sizeof(ELEMTYPE)*num);
        if (queue->buffer != NULL)
        {
            queue->MAXNUM = num;
            queue->length = 0;
            queue->head = 0;
            queue->rear = 0;
            return queue;
        }

        free(queue);
    }
    
    return NULL;
}

以上代码对应文件 sqqueue.cpp,需要注意的是,初始化 headrear 下标均为 0;此外,当分配 buffer 缓冲区失败时,我们需要将第1步分配的顺序队列结构体空间也释放掉。

顺序队列判空-isEmpty接口

在实现顺序队列 入队(enqueue)出队(dequeue) 操作前,我们先来实现顺序队列的 判空(isEmpty)判满(isFull) 操作。

根据本文前面的讨论,判断队列为空的条件可以是:

head == rearlength == 0

以下为 isEmpty 操作接口的实现:

bool isEmpty(Psqueue queue)
{
    if (queue == NULL)
    {
        return false;
    }
    
    // method 1
    //return queue->length == 0;
    // method 2
    return queue->head == queue->rear;
}

注: 当传递的 queue 指针为空时,需返回为 true,大家可以思考下为什么。

顺序队列判满-isFull接口

顺序队列判满操作的条件是:

(rear + 1)%MAXNUM == headlength == MAXNUM

两个条件是有所区别的,第一个条件下,实际上队列元素缓冲区还剩余 1 个元素存储空间;第二个条件下,缓冲区所有元素存储空间都被充分利用。

以下是 isFull 操作接口的实现:

bool isFull(Psqueue queue)
{
    if (queue == NULL)
    {
        return true;
    }
    
    // method 1
    //return queue->length == queue->MAXNUM;
    // method 2
    return (queue->rear+1)%queue->MAXNUM == queue->head;
}

顺序队列入队-enqueue操作

在顺序队列为非满时,顺序队列才可进行入队操作。

此外,由 图1图2 可知 rear指向的缓冲区当前为队尾,且该位置尚未存储元素。因此,元素入队时,先将元素存储在 rear 指向的缓冲区中,然后再改变 rear 指向新队尾。

bool enqueue(Psqueue queue, ELEMTYPE x)
{
   if (queue == NULL)
   {
       return false;
   }
   
   if (isFull(queue))
   {
       return false;
   }
   
   queue->buffer[queue->rear] = x;
   queue->rear = (queue->rear + 1) % queue->MAXNUM;
   queue->length = queue->length + 1;

   return true;
}

元素入队后,改变 rear 队尾下标时,需考虑 rear + 1 >= MAXNUM 的情况(可通过 % 取模运算实现)。

顺序队列出队-dequeue操作

与入队操作相反,当顺序队列为非空时,才能进行元素出队操作。

元素出队后,需要更新 head 队头下标,往后移动一个位置,同时也需要考虑当 head 已经在缓冲区尾部时进行 +1 操作需要将 head 重新移动到缓冲区头部(可通过 % 取模运算实现)。

bool dequeue(Psqueue queue)
{
    if (queue == NULL)
    {
        return false;
    }
    
    if (isEmpty(queue))
    {
        return false;
    }
    
    queue->length = queue->length - 1;
    queue->head = (queue->head + 1) % queue->MAXNUM;
    
    return true;
}

取队头元素-front操作

当顺序队列不为空时,可以取当前队列队头元素。取队头元素并不影响当前顺序队列状态。

首先,来看看 front 接口操作声明:

bool front(Psqueue queue, ELEMTYPE &x);

front 操作中我们使用了C++语言的 引用类型 存放顺序队列队头元素。当 front 操作的返回值为 true 时,ELEMTYPE 类型的引用变量才被赋值为顺序队列队头元素。

以下是 front 接口操作的实现:

bool front(Psqueue queue, ELEMTYPE &x)
{
    if (queue == NULL)
    {
        return false;
    }
    
    if (isEmpty(queue))
    {
        return false;
    }
    
    x = queue->buffer[queue->head];

    return true;
}

完成上述几个接口后,应该可以编写测试代码对其进行测试了(尽可能早的对项目进行测试),由于篇幅的原因,这里就不在赘述。

清空队列-clear接口

由于是顺序队列,因此清空队列只需要对队列的 headrear 下标进行更新即可,以下给出 clear 接口操作的实现:

bool clear(Psqueue queue)
{
    if (queue == NULL)
    {
        return false;
    }
    
    queue->head = 0;
    queue->rear = 0;
    queue->length = 0;

    return true;
}

队列长度-length接口

由于在顺序队列结构体中,我们使用 length 保存当前队列中的元素个数,因此求队列长度的操作反而变得简单直接了。

唯一需要注意的是,如果当顺序队列指针 queue 为空时,length 接口返回值为 -1

int length(Psqueue queue)
{
    if (queue == NULL)
    {
        return -1;
    }
    
    return queue->length;
}

销毁队列-destroy接口

销毁队列需要做与创建队列相反的事情:

  1. 释放队列元素缓冲区;
  2. 释放队列结构体所在内存空间;
bool destroy(Psqueue &queue)
{
    if (queue == NULL)
    {
        return false;
    }
    
    free((queue)->buffer);
    free(queue);
    queue = NULL;

    return true;
}

destroy 接口中(queue 类型为Psqueue引用类型),完成相关内存空间释放操作后,我们还将 queue 指针置为空,避免其变成野指针。

打印队列元素-print接口

最后我们给出打印队列所有元素的接口,为了直观方便,我们以如下格式显示队列元素及队列状态:

a b c d e f g h i j k l * * * 
H                       R     

* * * d e f g h i j k l * * * 
      H                 R     

q * * d e f g h i j k l m n p 
  R   H 

其中,HR 分别表示队列的队头指针和队尾指针;*号表示当前缓冲区未存储队列元素。

以下是 print 接口操作的实现:

bool print(Psqueue queue)
{
    POSITION p;
    char *index = NULL;

    int flag = 0;
    if (queue == NULL)
    {
        return true;
    }
    
    p = 0;

#ifdef DEBUG
    index = (char *) malloc(sizeof(char)*queue->MAXNUM);

    if (index != NULL)
    {
        memset(index, ' ', queue->MAXNUM);
        index[queue->rear] = 'R';
        index[queue->head] = 'H';
    }
#endif

    if (queue->rear < queue->head)
    {
        flag = 1;
    }

    for (p = 0; p < queue->MAXNUM; p++)
    {
        if (flag && p >= queue->rear && p < queue->head)
        {
            printf("* ");
        }
        else if (!flag && (p < queue->head || p >= queue->rear))
        {
            printf("* ");
        }
        else
        {
            printf("%-2c", queue->buffer[p]);
        }
    }

    printf("\n");

#ifdef DEBUG
    if (index != NULL)
    {
        for (p = 0; p < queue->MAXNUM; p++)
        {
            printf("%-2c", index[p]);
        }
    }
    printf("\n");
#endif
    
    if (index != NULL)
    {
        free(index);
        index = NULL;
    }

    return true;
}

测试代码

完成顺序队列的所有接口后,我们给出其对应的测试文件 main.cpp。 在该文件中,我们对 createenqueuedequeueisEmptytopprintdestroy 等接口进行了直接测试,而其他接口则进行了间接的测试。

#include "sqqueue.h"

int main ()
{
    Psqueue queue;
    ELEMTYPE x;

    int i;
    queue = create(15);

    if (queue != NULL)
    {
        for (i = 0; i < 12; i++)
        {
            enqueue(queue, 'a'+i);
        }
        print(queue);

        for (i = 0; i < 3; i++)
        {
            dequeue(queue);
        }
        print(queue);

        for (i = 0; i <= 3; i++)
        {
            enqueue(queue, '1' + i);
        }
        print(queue);

        while (!isEmpty(queue))
        {
            dequeue(queue);
        }
        print(queue);

        while (!isFull(queue))
        {
            enqueue(queue, 'X');
        }
        print(queue);

        destroy(queue);
        print(queue);
    }

    return 0;
}

对项目进行编译后,程序运行结果如下所示:

[localhost@queue xgqin]$g++ -o sqQueueTest main.cpp sqqueue.cpp -DDEBUG -g
[localhost@queue xgqin]$ ./sqQueueTest 
a b c d e f g h i j k l * * * 
H                       R     
* * * d e f g h i j k l * * * 
      H                 R     
4 * * d e f g h i j k l 1 2 3 
  R   H                       
* * * * * * * * * * * * * * * 
  H                           
* X X X X X X X X X X X X X X 
R H   

至此,我们完成了自定义顺序循环队列的定义及接口实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 栈 栈的英文单词是Stack,它代表一种特殊的线性表,这种线性表只能在固定一端(通常认为是线性表的尾端)进行插入,...
    Jack921阅读 1,492评论 0 5
  • 栈 栈: 限定仅在表尾进行插入和删除操作的线性表; 后进先出(LIFO)。 在表尾进行操作,表尾是栈顶;最新进栈的...
    IAM四十二阅读 877评论 0 2
  • 栈是限定仅在表尾进行插入和删除操作的线性表。 队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。 一...
    开心糖果的夏天阅读 412评论 0 4
  • #链接原文: http://www.jianshu.com/p/55e262b272b0 1.下载渠道下载渠道分为...
    jacky123阅读 832评论 0 2
  • 那夜如斯 风过山坳的时候 把尘土撵走 脚步声比风声还急 啪嗒啪嗒地越来越远 越来越远… 却越来越急 越来越急… 突...
    某啦阅读 173评论 0 1