數據結構編程筆記十:第三章 棧和隊列 循環隊列的實現

上次我們一起看了鏈隊列的實現,這次一起來看看隊列的順序存儲方式實現——循環隊列。

還是老規矩:

程序在碼雲上可以下載。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git

隊列的ADT描述請參考上一篇文章《數據結構編程筆記九:第三章 棧和隊列 鏈隊列的實現》:
http://blog.csdn.net/u014576141/article/details/77418397

循環隊列和鏈隊列的區別主要是存儲結構上的區別:循環隊列採用順序存儲結構,鏈隊列採用鏈式存儲結構。兩者在操作上也有所差別:循環隊列的存儲空間是固定的,不允許擴容,所以入隊操作要先判斷隊列是否滿纔可以入隊,但是鏈隊列不需要判斷隊列是否滿就可以做入隊操作,且鏈隊列就是通過逐個申請結點的方式來實現隊列的。

循環隊列的長度是預先設定且不允許在運行過程中改變的,這個長度的設置很有學問,設置的小了隊列空間不夠用,很多結點無法入隊,設置的大了,則會造成空間浪費。

一起來看看循環隊列的實現:

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>引入頭文件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

#include <stdio.h>   //使用了標準庫函數 printf(),scanf()
#include <stdlib.h>  //使用了動態內存分配函數 malloc(),free()

//>>>>>>>>>>>>>>>>>>>>>>>>>>>自定義符號常量<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 

#define OVERFLOW -2         //內存溢出錯誤常量
#define OK 1                //表示操作正確的常量 
#define ERROR 0             //表示操作錯誤的常量
#define MAXQSIZE 100        //隊列的最大長度
#define TRUE 1              //表示邏輯爲真的常量 
#define FALSE 0             //表示邏輯爲假的常量

//>>>>>>>>>>>>>>>>>>>>>>>>>>>自定義數據類型<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

typedef int Status;         //用typedef給int起個別名,也便於程序的維護 
typedef float QElemType;    //用typedef給float起個別名,也便於程序的維護
typedef struct {            //循環隊列的C語言描述 

    QElemType *base;        //初始化動態分配存儲空間
    int front;              //頭指針,若隊列不空,指向隊頭元素 
    int rear;               //尾指針,若隊列不空,指向隊尾元素的下一個位置 
}SqQueue; 

//------------------------循環隊列的主要操作--------------------------

//>>>>>>>>>>>>>>>>>>>>>>>>1.初始化循環隊列<<<<<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:InitQueue_Sq
    參數:SqQueue &Q 循環隊列引用 
    返回值:狀態碼,操作成功返回OK 
    作用:構建一個空隊列 Q
*/
Status InitQueue_Sq(SqQueue &Q) {

    //申請內存空間,若失敗則提示並退出程序
    //if(!(Q.base = (QElemType *)malloc(MAXQSIZE * sizeof(QElemType))))
    //相當於以下兩行代碼:
    //Q.base = (QElemType *)malloc(MAXQSIZE * sizeof(QElemType))
    //if(!Q.base)  <=>  if(Q.base == NULL)
    if(!(Q.base = (QElemType *)malloc(MAXQSIZE * sizeof(QElemType)))){
        printf("內存分配失敗,程序即將退出!\n");
        exit(OVERFLOW);
    }//if

    //由於剛申請的空間沒有元素入隊,所以隊列爲空 
    //Q.front == Q.rear是隊列爲空的標誌 
    Q.front = Q.rear = 0;

    //操作成功 
    printf("循環隊列已成功創建!\n");
    return OK;
}//InitQueue_Sq

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>2.銷燬循環隊列<<<<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:DestoryQueue_Sq
    參數:SqQueue &Q 循環隊列引用 
    返回值:狀態碼,操作成功返回OK 
    作用:銷燬循環隊列 Q
*/
Status DestoryQueue_Sq(SqQueue &Q) {

    //循環隊列採用連續的存儲空間,直接找到首地址釋放空間即可 
    if(Q.base) { 
        free(Q.base);
    }//if

    //指針置空,釋放掉指針變量本身佔用的空間 
    Q.base = NULL;

    //隊頭和隊尾指針歸零,隊列爲空 
    Q.front = Q.rear = 0;

    //操作成功 
    printf("循環隊列已成功銷燬!\n");
    return OK;
}//DestoryQueue_Sq

//>>>>>>>>>>>>>>>>>>>>>>3.判斷循環隊列是否爲空<<<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:QueueEmpty_Sq
    參數:SqQueue Q 循環隊列Q 
    返回值:狀態碼,隊列爲空返回TRUE,否則返回FALSE 
    作用:判斷循環隊列 Q是否爲空 
*/
Status QueueEmpty_Sq(SqQueue Q) {

    //Q.rear == Q.front是隊列爲空的標誌 
    if(Q.rear == Q.front) { 
        return TRUE; 
    }//if 
    else {
        return FALSE;
    }//else  
}//QueueEmpty_Sq

//>>>>>>>>>>>>>>>>>>>>>>>4.獲取循環隊列的長度<<<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:QueueLength_Sq
    參數:SqQueue Q 循環隊列Q 
    返回值:隊列Q中的數據元素個數,即隊列長度
    作用:獲取隊列長度 
*/
int QueueLength_Sq(SqQueue Q) {

    //注意:不可以直接用(Q.rear - Q.front) % MAXQSIZE表示隊列長度,
    //因爲如果Q.rear - Q.front所得數字是負數或者小於MAXQSIZE,
    //則所得結果是不正確的。所以一定要先加上一個MAXQSIZE再做模運算 
    return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE; 
}//QueueLength_Sq

//>>>>>>>>>>>>>>>>>5.在循環隊列中插入元素(入隊)<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:EnQueue_Sq
    參數:SqQueue Q 循環隊列Q
          QElemType e 被插入元素e 
    返回值:狀態碼,操作成功返回OK,否則返回ERROR 
    作用:插入元素e爲Q的新的隊尾元素e  
*/
Status EnQueue_Sq(SqQueue &Q, QElemType e) {

    //由於循環隊列使用順序存儲結構,插入時需要判斷隊列是否滿
    //判斷隊列滿的標誌:(Q.rear + 1) % MAXQSIZE == Q.front
    if((Q.rear + 1) % MAXQSIZE == Q.front) {  //隊列滿

        //操作失敗 
        return ERROR;
    }//if

    //把e插入到隊尾 
    Q.base[Q.rear] = e;

    //每插入一個新隊尾元素,尾指針增一
    Q.rear = (Q.rear + 1) % MAXQSIZE;

    //操作成功 
    return OK; 
}//EnQueue_Sq

//>>>>>>>>>>>>>>>>>>6.在循環隊列中刪除元素(出隊)<<<<<<<<<<<<<<<<<<<

/*
    函數:DeQueue_Sq
    參數:SqQueue Q 循環隊列Q
          QElemType &e 帶回被刪除的元素e 
    返回值:狀態碼,操作成功返回OK,否則返回ERROR 
    作用:隊列不空,則刪除Q的隊頭元素,用e返回其值
*/
Status DeQueue_Sq(SqQueue &Q, QElemType &e) {

    //在空隊列中執行出隊操作沒有意義,所以要先判斷隊列是否爲空 
    //if(QueueEmpty_Sq(Q)) <=> if(QueueEmpty_Sq(Q) == TRUE)
    if(QueueEmpty_Sq(Q)) { //隊列空 

        //操作失敗 
        return ERROR;
    }//if

    //保存被刪除元素的值 
    e = Q.base[Q.front];

    //每當刪除隊頭元素時,頭指針增1 
    Q.front = (Q.front + 1) % MAXQSIZE;

    //操作成功 
    return OK; 
}//DeQueue_Sq

//>>>>>>>>>>>>>>>>>>>>>7.置空循環隊列<<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:ClearQueue_Sq
    參數:SqQueue &Q 循環隊列引用 
    返回值:狀態碼,操作成功返回OK,否則返回ERROR 
    作用:將Q清空 
*/
Status ClearQueue_Sq(SqQueue &Q) {

    //隊頭隊尾指針清零 
    Q.front = Q.rear = 0;

    //操作成功    
    printf("鏈隊列已清空,長度爲:%d\n", QueueLength_Sq(Q));
    return OK; 
}//ClearQueue_Sq

//>>>>>>>>>>>>>>>>>>>>>>>8.遍歷整個循環隊列<<<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:Print
    參數:ElemType e 被訪問的元素 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:訪問元素e的函數,通過修改該函數可以修改元素訪問方式,
          該函數使用時需要配合遍歷函數一起使用。 
*/
Status Print(QElemType e){

    //採用控制檯輸出的方式訪問每個元素 
    printf("%6.2f    ", e);

    //操作成功 
    return OK;
}//Print

/*
    函數:QueueTraverse_Sq
    參數:SqQueue Q 循環鏈隊列Q 
          Status (* visit)(QElemType) 函數指針,指向元素訪問函數。 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:調用元素訪問函數按出隊順序完成循環隊列的遍歷,但並未真正執行出隊操作 
*/
Status QueueTraverse_Sq(SqQueue Q, Status (* visit)(QElemType)) {

    //依次訪問每個元素 
    for(int i = 0; i < QueueLength_Sq(Q); i++) {

        //一旦訪問失敗則遍歷失敗
        //if(visit(Q.base[i]) != NULL)
        if(!visit(Q.base[i])) {
            return ERROR;
        }//if 
    }//for 

    //操作成功 
    return OK; 
}//QueueTraverse_Sq

//>>>>>>>>>>>>>>>>>>>>>9.獲取隊頭元素的值<<<<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函數:GetHead_Sq
    參數:SqQueue Q 循環鏈隊列Q 
          QElemType &e 帶回隊頭元素的值 
    返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR 
    作用:獲取隊頭元素的值並由e帶回該值 
*/
Status GetHead_Sq(SqQueue Q, QElemType &e) {

    //如果隊列爲空,則獲取不到隊頭元素的值,所以要先判斷隊列是否空
    //if(QueueEmpty_Sq(Q)) <=> if(QueueEmpty_Sq(Q) == TRUE) 
    if(QueueEmpty_Sq(Q)) { //隊列空

        //無法獲取隊頭元素,操作失敗 
        return ERROR;
    }//if

    //獲取隊頭元素的值並保存到e 
    e = Q.base[Q.front];

    //操作成功 
    return OK; 
}//GetHead_Sq

//--------------------------主函數---------------------------------- 
int main(int argc,char *argv[]){

    //循環隊列Q 
    SqQueue Q;

    //臨時變量e,用於保存待插入元素,被刪除元素和隊頭元素 
    QElemType e;

    //n用於接收從鍵盤輸入的隊列長度 
    int n;

    printf("\n----------------------------------循環隊列-----------------------------------\n"); 

    printf("->初始化循環隊列\n");
    InitQueue_Sq(Q);
    printf("此時隊列長度爲:%d\n", QueueLength_Sq(Q));

    printf("->測試入隊操作");
    printf("您想輸入幾個元素:");
    scanf("%d", &n);
    printf("請輸入所有元素,用空格隔開\n");
    for(int i = 0; i < n; i++){
        scanf("%f", &e);
        EnQueue_Sq(Q, e);
    }//for
    printf("入隊後的結果爲\n");
    QueueTraverse_Sq(Q, Print);
    printf("\n此時隊列長度爲:%d\n", QueueLength_Sq(Q));

    printf("->測試出隊和獲取隊頭操作\n");
    GetHead_Sq(Q, e);
    printf("隊頭元素爲%6.2f\n", e);
    while(!QueueEmpty_Sq(Q)){
        DeQueue_Sq(Q, e);
        printf("元素%6.2f出隊  \n", e);
    }//while
    printf("\n此時隊列長度爲:%d\n", QueueLength_Sq(Q));

    printf("->測試銷燬隊列操作");
    DestoryQueue_Sq(Q);

    return 0;
}//main

輸入的測試數據和對應輸出:

----------------------------------循環隊列-----------------------------------
->初始化循環隊列
循環隊列已成功創建!
此時隊列長度爲:0
->測試入隊操作您想輸入幾個元素:10
請輸入所有元素,用空格隔開
2 4 8 9 5 7 6 2 4 1
入隊後的結果爲
  2.00      4.00      8.00      9.00      5.00      7.00      6.00      2.00
  4.00      1.00    
此時隊列長度爲:10
->測試出隊和獲取隊頭操作
隊頭元素爲  2.00
元素  2.00出隊
元素  4.00出隊
元素  8.00出隊
元素  9.00出隊
元素  5.00出隊
元素  7.00出隊
元素  6.00出隊
元素  2.00出隊
元素  4.00出隊
元素  1.00出隊

此時隊列長度爲:0
->測試銷燬隊列操作循環隊列已成功銷燬!

--------------------------------
Process exited with return value 0
Press any key to continue . . .

總結:
1.循環隊列空的標誌:Q.rear == Q.front
2.循環隊列滿的標誌:(Q.rear + 1) % MAXQSIZE == Q.front
3.循環隊列的長度:(Q.rear - Q.front + MAXQSIZE) % MAXQSIZE

循環隊列中最重要的就是這三個標誌,考研也喜歡考(主要是選擇題),一定要牢記。

下次的文章會介紹順序定長串的實現以及基於順序串的兩種模式匹配算法的實現。希望大家繼續關注我的博客,再見!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章