數據結構之隊列實現

隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱爲隊尾,進行刪除操作的端稱爲隊頭。

基於數組的循環隊列——C語言描述

基於數組的隊列,一般我們只會使用循環隊列,而不使用非循環。這樣做的目的主要是處於安全考慮,已經空間的開銷。循環隊列的使用使得安全性大幅提高,並降低了內存的開銷以及提高了內存的利用效率。

#include <stdio.h>
#include <stdbool.h>

#define MAXSIZE  20
#define OK 1
#define ERR 0

//創建隊列,基於數組。
typedef struct
{
	int data[MAXSIZE] = { 0 };
	int front;
	int rear;
}Queue;


//初始化隊列
int InitQueue(Queue* q)
{
	q->front = q->rear = 0;
	return OK;
}

//返回當前隊列的長度。
int QueueLength(Queue* q)
{
	return (q->rear - q->front + MAXSIZE) % MAXSIZE;
}

//入隊
int EnQueue(Queue* q, int e)
{
	//入隊需要判斷隊列是否爲滿
	if ((q->rear + 1) % MAXSIZE == q->front)
		return ERR;

	q->data[q->rear] = e;
	q->rear = (q->rear + 1) % MAXSIZE;

	return OK;
}

//出隊
int DeQueue(Queue* q, int* e)
{
	//出隊需要判斷隊列是否爲空
	if (q->rear == q->front)
		return ERR;

	*e = q->data[q->front];
	q->front = (q->front + 1) % MAXSIZE;
	
	return OK;
}

//測試所寫接口,包括邊界條件的測試全部正常。
int main()
{
	Queue sq = { 0 };

	InitQueue(&sq);

	int len = QueueLength(&sq);
	printf("corrent lengths:%d\n", len);

	for (int i = 0; i < 5; i++)
	{
		if (EnQueue(&sq, i))
			printf("Succeseful!\n");
		else
			printf("Queue is full!\n");
	}

	len = QueueLength(&sq);
	printf("corrent lengths:%d\n", len);

	int e = 0;
	for (int i = 0; i < 5; i++)
	{
		if (DeQueue(&sq, &e))
			printf("e of val:%d\n", e);
		else
			printf("Queue is empty!");
	}

	len = QueueLength(&sq);
	printf("corrent lengths:%d\n", len);

	if (DeQueue(&sq, &e))
		printf("e of val:%d\n", e);
	else
		printf("Queue is empty!");

	for (int i = 0; i < 20; i++)
	{
		if (EnQueue(&sq, i))
			printf("Succeseful!\n");
		else
			printf("Queue is full!\n");
	}

	if (EnQueue(&sq, 10))
		printf("Succeseful!\n");
	else
		printf("Queue is full!\n");
	

	return 0;
}

基於鏈表的隊列——C語言描述

鏈表隊列的創建

基於鏈表的隊列。從名字上看,鏈表,鏈表,首先我們的定義一個鏈表,然後限制的操作使其符合隊列的定義那麼這個鏈表就具有了隊列的熟悉。

因此我們先着眼於鏈表。

typedef struct
{
	int data;
	struct QNode* next;
}QNode;

隊列需要2個指針指向這個鏈表,以限制鏈表。

typedef struct
{
	struct QNode* rear, * front;
}LinkQueue;

鏈表隊列的常見操作

初始化鏈表隊列

/********************************************************************************************
我認爲鏈表隊列的初始化函數不是必須,因爲但我們創建一個鏈表隊列時已經初始化
完成了(至少編譯器會這樣做)。
初始化API存在的意義更像是爲了提高可讀性,起提示作用。
如果要定義初始化函數,那麼我個人覺得需要判斷是不是非空隊列,否則胡亂初始化
將會導致內存泄露。
以上僅是個人的一些看法,歡迎交流指錯。
*********************************************************************************************/
int InitLinkQueue(LinkQueue* q)
{
	//判斷隊列是否非空
	if (q->front)
		return ERR;

	q->front = q->rear = NULL;
	return OK;
}

入隊
基於鏈表可認爲長度無限制,硬要說也只是內存限制。

int EnLinkQueue(LinkQueue* q, int e)
{
	//節點創建及初始化
	QNode* p = (QNode*)malloc(sizeof(QNode));
	if (!p)
		return ERR;
	memset(p, 0, sizeof(QNode));
	p->data = e;
	p->next = NULL;

	//判斷是否爲空隊列
	if (!q->front && q->rear == q->front)
	{
		q->front = q->rear = p;
	}
	else
	{
		q->rear->next = p;
		q->rear = p;
	}
	
	return OK;
}

出隊

int DeLinkQueue(LinkQueue* q, int* ReturnVal)
{
	//判斷是否爲空隊列
	if (q->rear == q->front && !q->front)
		return ERR;

	//需要判斷是否只有一個節點,前面已經判斷是否爲空了,這裏不用重複了
	if (q->front == q->rear)
	{
		*ReturnVal = q->front->data;
		free(q->front);
		q->front = q->rear = NULL;
	}
	else
	{
		QNode* tmp = q->front;
		*ReturnVal = q->front->data;
		q->front = q->front->next;
		free(tmp);
		tmp = NULL;
	}

	return OK;
}

判斷是否爲空隊列

int IsEmptyQueue(LinkQueue* q)
{
	return (q->rear == q->front && !q->front) ? OK : ERR;
}

API測試

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define OK 1
#define ERR 0

void DeQueueTest(LinkQueue* q);
void EnQueueTest(LinkQueue* q, int i);
void InitLinkQueueTest(LinkQueue* q);
void IsEmptyQueueTest(LinkQueue* q);


int main()
{
	LinkQueue q = { 0 };

	InitLinkQueueTest(&q);

	for (int i = 1; i < 6; i++)
	{
		EnQueueTest(&q, i);
	}
	printf("\n");

	for (int i = 0; i < 5; i++)
	{
		DeQueueTest(&q);
	}
	putchar('\n');

	IsEmptyQueueTest(&q);

	DeQueueTest(&q);

	return 0;
}


void IsEmptyQueueTest(LinkQueue* q)
{
	if (IsEmptyQueue(q))
		printf("It's an Empty  Queue\n");
	else
		puts("It's not an empty Queue\n");
}

void InitLinkQueueTest(LinkQueue* q)
{
	if (InitLinkQueue(q))
		printf("Init Succeseful!\n\n");
	else
		printf("The queue was existing!\n\n");
}

void EnQueueTest(LinkQueue* q, int i)
{
	if (EnLinkQueue(q, i))
	{
		printf("Succeseful!\t");
		printf("val:%d\n", i);
	}
	else
		printf("Failed!\n");
}

void DeQueueTest(LinkQueue* q)
{
	int val = 0;
	if (DeLinkQueue(q, &val))
	{
		printf("Succeseful!\t");
		printf("val:%d\n", val);
	}
	else
		printf("Failed! The queue is empty!\n");
}

寫鏈表隊列的時候意識到main存在多處重複代碼,完全可以封裝爲一個函數,以此避免代碼的重複性,提高複用性,我乾脆全都封裝算了,這樣main邏輯也清楚。數組那就不改了,將就下。
其他一些操作其實是鏈表的內容就不寫了,比如統計長度,就是鏈表的遍歷。

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