數據結構和算法(6)隊列的操做和實現

數據結構和算法(1)線性表實現git

數據結構和算法(2)單向循環鏈表的建立插入刪除實現github

數據結構和算法(3)雙向鏈表與雙向循環鏈表的實現面試

數據結構和算法(4)鏈表相關面試題算法

數據結構和算法(5)棧和隊列的操做和實現shell

數據結構和算法(6)隊列的操做和實現數組

@[TOC]數據結構

1. 數據結構和算法(6)隊列的操做和實現

代碼下載

本篇博客代碼下載:數據結構和算法

1.1 隊列簡介

隊列是一種先進先出(First In First Out)的線性表,也就是FIFO。一般,稱進數據的一端爲 "隊尾",出數據的一端爲 "隊頭",數據元素進隊列的過程稱爲 "入隊",出隊列的過程稱爲 "出隊"。post

與棧結構不一樣的是,隊列的兩端都"開口",要求數據只能從一端進,從另外一端出,以下圖 所示:單元測試

若是對棧結構不熟悉能夠參考我以前的博客:「數據結構和算法(五)棧和隊列的操做和實現」

隊列的存儲結構
不只如此,隊列中數據的進出要遵循 "先進先出" 的原則,即最早進隊列的數據元素,一樣要最早出隊列。拿圖 1 中的隊列來講,從數據在隊列中的存儲狀態能夠分析出,元素 1 最早進隊,其次是元素 2,最後是元素 3。此時若是將元素 3 出隊,根據隊列 "先進先出" 的特色,元素 1 要先出隊列,元素 2 再出隊列,最後才輪到元素 3 出隊列。

棧和隊列不要混淆,棧結構是一端封口,特色是"先進後出";而隊列的兩端全是開口,特色是"先進先出"。

所以,數據從表的一端進,從另外一端出,且遵循 "先進先出" 原則的線性存儲結構就是隊列

隊列存儲結構的實現有如下兩種方式:

  1. 順序隊列:在順序表的基礎上實現的隊列結構;
  2. 鏈隊列:在鏈表的基礎上實現的隊列結構; 二者的區別僅是順序表和鏈表的區別,即在實際的物理空間中,數據集中存儲的隊列是順序隊列,分散存儲的隊列是鏈隊列。

實際生活中,隊列的應用隨處可見,好比排隊買 XXX、醫院的掛號系統等,採用的都是隊列的結構。

拿排隊買票來講,全部的人排成一隊,先到者排的就靠前,後到者只能從隊尾排隊等待,隊中的每一個人都必須等到本身前面的全部人所有買票成功並從隊頭出隊後,才輪到本身買票。這就不是典型的隊列結構嗎?

明白了什麼是隊列,接下來開始學習順序隊列和鏈隊列的基本實現和注意事項。

1.2 隊列順序存儲

因爲順序隊列的底層使用的是數組,所以需預先申請一塊足夠大的內存空間初始化順序隊列。除此以外,爲了知足順序隊列中數據從隊尾進,隊頭出且先進先出的要求,咱們還須要定義兩個指針(top 和 rear)分別用於指向順序隊列中的隊頭元素和隊尾元素,以下圖 所示:

順序隊列的實現
因爲順序隊列初始狀態沒有存儲任何元素,所以 top 指針和 rear 指針重合,且因爲順序隊列底層實現靠的是數組,所以 toprear 其實是兩個變量,它的值分別是隊頭元素和隊尾元素所在數組位置的下標。

當有數據元素進隊列時,對應的實現操做是將其存儲在指針 rear 指向的數組位置,而後 rear+1;當須要隊頭元素出隊時,僅需作 top+1 操做。

舉個栗子:

若是咱們要將 {1,2,3,4} 用順序隊列存儲的實現,

元素1 進隊的過程以下:

元素1 入隊

元素4入隊過程:

元素4入隊

那麼咱們接下來要將1,2,3,4這四個元素出隊。出隊過程以下:

元素1出隊

元素4出隊:

元素4出隊

咱們先看一下一個簡單的順序隊列操做代碼:

#include <stdio.h>

 int enQueue(int *a,int rear,int data){
	 a[rear]=data;
	 rear++;
	 return rear;
 }
void deQueue(int *a,int front,int rear){
//若是 front==rear,表示隊列爲空
	while (front!=rear) {
		printf("出隊元素:%d\n",a[front]);
		 front++;
	 }
 }
 
int main() {
	int a[100];
	int front,rear;
	//設置隊頭指針和隊尾指針,當隊列中沒有元素時,隊頭和隊尾指向同一塊地址
	front=rear=0;
	//入隊
	rear=enQueue(a, rear, 1);
	rear=enQueue(a, rear, 2);
	rear=enQueue(a, rear, 3);
	rear=enQueue(a, rear, 4);
	//出隊
	deQueue(a, front, rear);
	return 0;
 }

複製代碼

輸出結構爲:

出隊元素:1
出隊元素:2
出隊元素:3
出隊元素:4
複製代碼

上面這種順序存儲隊列會存在一些問題,如假溢出問題,以下圖:

順序隊列假溢出問題
如上圖,咱們先將A,B,C三個元素依次入隊,而後將A,B 出隊,而後又入隊了D,E,元素,這個時候rear隊尾指針指向了數組的最後,實際上咱們還有兩個空間能夠利用,這個時候隊列並無滿,可是因爲rear指向了最後,也就是順序隊列總體發生了後移,這樣形成的影響是:

  • 順序隊列以前的數組存儲空間將沒法再被使用,形成了空間浪費;也就是假溢出。
  • 另外如若是順序表申請的空間不足夠大,則直接形成程序中數組溢出,產生溢出錯誤;

爲了解決上面問題,咱們有一種比較優的解決辦法,就是用循環隊列來解決假溢出問題。

接下來將介紹循環隊列。

1.2.1 循環隊列

循環隊列就是,當隊尾指針移動到數組末尾時,下次再有元素入隊時,能夠將隊尾指針從新移到數組前面沒有元素的位置。

循環隊列操做以下圖:

循環隊列解決假溢出問題

如上圖: (a) 咱們用Q.front == Q.rear表示隊列爲空。 當咱們依次入隊a,b,c三個元素後,如上圖(b)所示。 接下來,咱們將元素a 從隊頭出隊,如上圖(c)所示。 而後,咱們又依次入隊了d ,e ,f, g這個時候實際上隊列已經存滿了,單咱們發現 Q.front == Q.rear,如上圖(d1)所示。可是咱們前面(a)中用Q.front == Q.rear表示隊列爲空,可是隊列滿了的時候咱們Q.front == Q.rear就沒法區分究竟是隊列爲空仍是滿了。爲了解決這個問題,咱們通常採用犧牲一個存儲空間的方式。也就如上圖(d2) 咱們用Q.front = Q.rear + 1表示堆滿,就是犧牲一個存儲單元不存放數據。

在循環隊列中咱們判斷隊列爲空,隊列爲滿的條件以下:

  1. 隊列爲滿: (Q.rear+1)%maxSize==Q.front
  2. 隊列爲空: Q.rear==Q.front
  3. 隊列中有效的數據的個數: (Q.rear+maxSize-Q.front)%maxSize

1.2.2 循環隊列的代碼實現

  • 循環隊列的順序存儲結構
/* 循環隊列的順序存儲結構 */
typedef struct KQueue {
    KQueueElementType data[MAXSIZE];
    int front;        /* 頭指針 */
    int rear;        /* 尾指針,若隊列不空,指向隊列尾元素的下一個位置 */
}KQueue;
複製代碼

1.2.2.1 初始化

//1. 初始化一個隊列
KStatus initQueue(KQueue *Q) {
    Q->front = Q->rear = 0;
    return OK;
}
複製代碼

1.2.2.2 隊列清空

//2. 將隊列清空
KStatus clearQueue(KQueue *Q) {
    Q->front = Q->rear = 0;
    return OK;
}
複製代碼

1.2.2.3 隊列判空

//3. 隊列判空
KStatus isEmpty(KQueue Q) {
    return Q.front == Q.rear ;
}
複製代碼

1.2.2.4 隊列是否滿了

//4. 隊列是否滿了
KStatus isFull(KQueue Q) {
    return Q.front == (Q.rear + 1 ) % MAXSIZE;
}

複製代碼

1.2.2.5 查詢隊列長度

//5. 查詢隊列長度
int getLength(KQueue Q) {
    return (Q.rear - Q.front + MAXSIZE)%MAXSIZE;
}
複製代碼

1.2.2.6 獲取隊頭元素

//6. 獲取隊頭元素
//若隊列不空,則用e返回Q的隊頭元素,並返回OK,不然返回ERROR;
KStatus getHead(KQueue Q, KQueueElementType *e) {
    //判斷是否隊列爲空
    if (isEmpty(Q)) {
        return ERROR;
    }
    //取出元素值
    *e = Q.data[Q.front];
    return OK;
}
複製代碼

1.2.2.7 入隊

//7. 入隊
// 若隊列未滿,則插入元素e爲新隊尾元素
KStatus enQueue(KQueue *Q, KQueueElementType e) {
    //判斷隊列是否滿了
    if (isFull(*Q)) {
        return ERROR;
    }
    //將元素e賦值給隊尾
    Q->data[Q->rear] = e;
    
    //rear指針向後移動一位,若到最後則轉到數組頭部
    Q->rear = (Q->rear + 1) % MAXSIZE;
    
    return OK;
}
複製代碼

1.2.2.8 出隊

//8. 出隊
//若隊列不空,則刪除Q中隊頭的元素,用e返回值
KStatus deQueue(KQueue *Q, KQueueElementType *e) {
    if (isEmpty(*Q)) return ERROR;
    //從隊頭取出元素賦值給e
    *e = Q->data[Q->front];
    
    //front指針向後移動一位,刪除對頭元素
    Q->front = (Q->front + 1) % MAXSIZE;
    
    return OK;
}
複製代碼

1.2.2.9 遍歷隊列

//9. 遍歷隊列
KStatus traverseQueue(KQueue Q) {
    int i = Q.front;
    while ((i + Q.front) != Q.rear) {
        //從隊頭遍歷到隊尾,依次輸出元素,i表示當前已經輸出到第幾個元素了
        //(i + Q.front) != Q.rear 表示 已經遍歷到了隊尾了,
        //因爲咱們不能修改front和rear指向,因此須要一個臨時變量記錄當前位置
        printf("%d ", Q.data[i]);
        i = (i + 1) % MAXSIZE;
    }
    printf("\n");
    
    return OK;
}

複製代碼

1.2.2.10 單元測試

//10. 單元測試
void test() {
    printf("循環隊列操做單元測試\n");
    KStatus j;
    int i=0;
    KQueueElementType d;
    KQueue Q;
    initQueue(&Q);
    printf("初始化隊列後,隊列空否?%u(1:空 0:否)\n",isEmpty(Q));
    
    printf("入隊:\n");
    while (i < 10) {
        enQueue(&Q, i);
        i++;
    }
    traverseQueue(Q);
    printf("隊列長度爲: %d\n",getLength(Q));
    printf("如今隊列空否?%u(1:空 0:否)\n",isEmpty(Q));
    printf("出隊:\n");
   
   //出隊
    deQueue(&Q, &d);
    printf("出隊的元素:%d\n",d);
    traverseQueue(Q);

    //獲取隊頭
    j=getHead(Q,&d);
    if(j)
        printf("如今隊頭元素爲: %d\n",d);
    clearQueue(&Q);
    printf("清空隊列後, 隊列空否?%u(1:空 0:否)\n",isEmpty(Q));

}
複製代碼

1.2.2.11 完整代碼

//
// main.c
// 010_Queue
//
// Created by 孔雨露 on 2020/4/18.
// Copyright © 2020 Apple. All rights reserved.
//

#include <stdio.h>
#include "stdlib.h"
#include "math.h"
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存儲空間初始分配量 */

typedef int KStatus;
typedef int KQueueElementType; /* KQueueElementType類型根據實際狀況而定,這裏假設爲int */

/* 循環隊列的順序存儲結構 */
typedef struct KQueue {
    KQueueElementType data[MAXSIZE];
    int front;        /* 頭指針 */
    int rear;        /* 尾指針,若隊列不空,指向隊列尾元素的下一個位置 */
}KQueue;

//1. 初始化一個隊列
KStatus initQueue(KQueue *Q) {
    Q->front = Q->rear = 0;
    return OK;
}

//2. 將隊列清空
KStatus clearQueue(KQueue *Q) {
    Q->front = Q->rear = 0;
    return OK;
}

//3. 隊列判空
KStatus isEmpty(KQueue Q) {
    return Q.front == Q.rear ;
}

//4. 隊列是否滿了
KStatus isFull(KQueue Q) {
    return Q.front == (Q.rear + 1 ) % MAXSIZE;
}

//5. 查詢隊列長度
int getLength(KQueue Q) {
    return (Q.rear - Q.front + MAXSIZE)%MAXSIZE;
}

//6. 獲取隊頭元素
//若隊列不空,則用e返回Q的隊頭元素,並返回OK,不然返回ERROR;
KStatus getHead(KQueue Q, KQueueElementType *e) {
    //判斷是否隊列爲空
    if (isEmpty(Q)) {
        return ERROR;
    }
    //取出元素值
    *e = Q.data[Q.front];
    return OK;
}

//7. 入隊
// 若隊列未滿,則插入元素e爲新隊尾元素
KStatus enQueue(KQueue *Q, KQueueElementType e) {
    //判斷隊列是否滿了
    if (isFull(*Q)) {
        return ERROR;
    }
    //將元素e賦值給隊尾
    Q->data[Q->rear] = e;
    
    //rear指針向後移動一位,若到最後則轉到數組頭部
    Q->rear = (Q->rear + 1) % MAXSIZE;
    
    return OK;
}

//8. 出隊
//若隊列不空,則刪除Q中隊頭的元素,用e返回值
KStatus deQueue(KQueue *Q, KQueueElementType *e) {
    if (isEmpty(*Q)) return ERROR;
    //從隊頭取出元素賦值給e
    *e = Q->data[Q->front];
    
    //front指針向後移動一位,刪除對頭元素
    Q->front = (Q->front + 1) % MAXSIZE;
    
    return OK;
}

//9. 遍歷隊列
KStatus traverseQueue(KQueue Q) {
    int i = Q.front;
    while ((i + Q.front) != Q.rear) {
        //從隊頭遍歷到隊尾,依次輸出元素,i表示當前已經輸出到第幾個元素了
        //(i + Q.front) != Q.rear 表示 已經遍歷到了隊尾了,
        //因爲咱們不能修改front和rear指向,因此須要一個臨時變量記錄當前位置
        printf("%d ", Q.data[i]);
        i = (i + 1) % MAXSIZE;
    }
    printf("\n");
    
    return OK;
}


//10. 單元測試
void test() {
    printf("循環隊列操做單元測試\n");
    KStatus j;
    int i=0;
    KQueueElementType d;
    KQueue Q;
    initQueue(&Q);
    printf("初始化隊列後,隊列空否?%u(1:空 0:否)\n",isEmpty(Q));
    
    printf("入隊:\n");
    while (i < 10) {
        enQueue(&Q, i);
        i++;
    }
    traverseQueue(Q);
    printf("隊列長度爲: %d\n",getLength(Q));
    printf("如今隊列空否?%u(1:空 0:否)\n",isEmpty(Q));
    printf("出隊:\n");
   
   //出隊
    deQueue(&Q, &d);
    printf("出隊的元素:%d\n",d);
    traverseQueue(Q);

    //獲取隊頭
    j=getHead(Q,&d);
    if(j)
        printf("如今隊頭元素爲: %d\n",d);
    clearQueue(&Q);
    printf("清空隊列後, 隊列空否?%u(1:空 0:否)\n",isEmpty(Q));

}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    test();
    return 0;
}

複製代碼
  • 單元測試,輸出結果:
Hello, World!
循環隊列操做單元測試
初始化隊列後,隊列空否?1(1:空 0:否)
入隊:
0  1  2  3  4  5  6  7  8  9  
隊列長度爲: 10
如今隊列空否?0(1:空 0:否)
出隊:
出隊的元素:0
1  2  3  4  5  6  7  8  
如今隊頭元素爲: 1
清空隊列後, 隊列空否?1(1:空 0:否)
Program ended with exit code: 0
複製代碼

1.3 隊列鏈式存儲

鏈式隊列的實現思想同順序隊列相似,只需建立兩個指針(命名爲 top 和 rear)分別指向鏈表中隊列的隊頭元素和隊尾元素,如圖下圖1 所示:

圖 1 鏈式隊列的初始狀態

圖 1 所示爲鏈式隊列的初始狀態,此時隊列中沒有存儲任何數據元素,所以 top 和 rear 指針都同時指向頭節點。

下面咱們來說解一下 鏈式隊列入隊,出隊操做,鏈式隊列入隊,出隊操做基本跟單鏈表類似,若是不熟悉鏈表的操做能夠參考我以前的鏈表相關的博客:

  1. "數據結構和算法(一)線性表實現"
  2. 數據結構和算法(二)單鏈表與單向循環鏈表的實現

基本就是入隊就是在鏈表尾部結點插入一個元素,出隊就是在鏈表頭結點刪除一個結點。

  • 鏈式隊列入隊操做: 以下圖2 表示鏈式隊列依次入隊{1,2,3} 三個元素:

圖 2 {1,2,3} 入鏈式隊列

如上圖所示,在鏈隊隊列中,當有新的數據元素入隊,只需進行如下 3 步操做:

  1. 將該數據元素用節點包裹,例如新節點名稱爲 elem;
  2. 與 rear 指針指向的節點創建邏輯關係,即執行 rear->next=elem;
  3. 最後移動 rear 指針指向該新節點,即 rear=elem;
  • 鏈式隊列出隊操做: 當鏈式隊列中,有數據元素須要出隊時,按照 "先進先出" 的原則,只需將存儲該數據的節點以及它以前入隊的元素節點按照原則依次出隊便可。出隊過程就是從鏈表頭依次刪除首結點的過程, 如今咱們須要將上圖2中的1,2 兩個元素出隊,其操做過程 以下圖3所示:

圖 3 鏈式隊列中數據元素出隊

如上圖3所示,咱們能夠知道,在鏈式隊列中隊頭元素出隊,須要作如下 3 步操做:

  1. 經過 top 指針直接找到隊頭節點,建立一個新指針 p 指向此即將出隊的節點;
  2. 將 p 節點(即要出隊的隊頭節點)從鏈表中摘除;
  3. 釋放節點 p,回收其所佔的內存空間;

1.3.2 隊列鏈式存儲的代碼實現

  • 鏈式隊列的結構定義
複製代碼

1.3.2.1 初始化

複製代碼

1.3.2.2 隊列清空

複製代碼

1.3.2.3 隊列判空

複製代碼

1.3.2.4 隊列是否滿了

複製代碼

1.3.2.5 查詢隊列長度

複製代碼

1.3.2.6 獲取隊頭元素

複製代碼

1.3.2.7 入隊

複製代碼

1.3.2.8 出隊

複製代碼

1.3.2.9 遍歷隊列

複製代碼

1.3.2.10 單元測試

複製代碼

1.3.2.11 完整代碼

複製代碼
  • 單元測試,輸出結果:
複製代碼
相關文章
相關標籤/搜索