[データ構造とアルゴリズム分析] C 言語を使用して 2 種類のキュー (リード ノードと非リード ノード) チェーン ストレージを実装し、循環キューの設計アイデアを与える

序文

  プログラムを作成するとき、多くの場合、さまざまなデータ構造を処理する必要があります。キューは、幅広いアプリケーション シナリオで使用される一般的なデータ構造です。キューの基本操作には、シミュレートされた待機キュー、メッセージ キュー、コンピューター キャッシュなどで使用される結合とキュー解除が含まれます。

  実際のプログラミングでは、さまざまなデータ構造を使用してキューを実装できます。この記事では主に、先頭ノードのある一方向キュー、先頭ノードのない一方向キュー、循環キューの 3 つの異なるキュー実装方法を紹介します。これらのキューの実装は、それぞれリンク リストや配列などの異なるデータ構造を使用し、実装の詳細、時間の計算量、スペースの使用率の点で異なる特性を持っています。プログラマーにとって、さまざまなキューの実装方法を理解すると、独自のアプリケーション シナリオに適したキューの実装方法をより適切に選択でき、プログラムの効率が向上します。

キューの実装

  キューは先入れ先出し (FIFO) データ構造であり、その基本操作には、エンキュー (キューの最後に要素を挿入する) とデキュー (キューの最初の要素を削除する) が含まれます。

先頭ノード片方向キュー

  • 先頭ノード一方向キューは、リンクリストを用いて実装されるキューであり、通常のリンクリストとは異なり、リンクリストの先頭にデータを格納しないノードを先頭ノードとして追加します。便宜上、リンクされたリストを使用します。キュー操作。

  • 先頭ノードの一方向キューでは、エンキュー操作はリンク リストの末尾に新しい要素を挿入することであり、デキュー操作は最初の要素の後続ノードを新しい最初のノードとして使用することです。

  • リンクリストの特性上、先頭ノードの一方向キューのエンキュー動作はO(1)O(1)O ( 1 )であり、デキュー操作にはリンクされたリスト全体を走査する必要があるため、その時間計算量はO (n) O(n)O(n),其中 n n n はキュー内の要素の数を表します。

  以下に、キュー挿入のロジックを示す図を示します。
ここに画像の説明を挿入します

  キューノード構造の設計:

// 节点结构体
struct queueNode
{
    
    
	// 数据元素
	int elem;
	// 指向下一个元素
	struct queueNode *next;
};

struct queue
{
    
    
	// 尾部
	struct queueNode *rear;
	// 头部
	struct queueNode *front;
	// 长度
	int lenth;
};

  コード:

/********************** 含头结点 ***************/
// 队列初始化
struct queue*queueInit()
{
    
    
	// 申请空间并且初始化
	struct queue*res = (struct queue*)malloc(sizeof(struct queue));
	// 初始化一个虚拟节点
	struct queueNode*node = (struct queueNode*)malloc(sizeof(struct queueNode));
	res->front = node;
	res->rear = node;
	res->lenth = 0;
	return res;
}

// 入队列
void pushQueueNode(struct queue*queue, int elem)
{
    
    
	// 新建一个节点
	struct queueNode*node = (struct queueNode*)malloc(sizeof(struct queueNode));
	node->elem = elem;
	node->next = NULL;

	// 添加到尾部
	queue->rear->next = node;
	queue->rear = node;
	queue->lenth++;
}

// 出队列
bool pullQueueNode(struct queue*queue, int&elem)
{
    
    
	// 判断队列是否为空
	if (queueIsEmpty(queue))
		return true;

	// 移除队列中的首元素
	struct queueNode*p = queue->front->next;
	elem = p->elem;
	queue->front->next = p->next;
	queue->lenth--;
	// 队列中只有一个元素应该改变队尾指针
	if (queue->rear == p)
		queue->rear = queue->front;
	free(p);
	return false;
}

// 判断队列是否空
bool queueIsEmpty(struct queue*queue)
{
    
    
	return queue->lenth == 0;
}

// 打印队列
void outPutQueue(struct queue*queue)
{
    
    
	// 判断队列是否为空
	if (queue->lenth == 0)
		return;
	struct queueNode*p = queue->front->next;
	// 遍历队列并打印
	printf("queue:");
	while (p)
	{
    
    
		printf("%d ",p->elem);
		p = p->next;
	}
	printf("\n");
}

先頭ノードのない一方向キュー

  • 先頭ノードのある一方向キューと比較して、先頭ノードのない一方向キューは先頭ノードを使用せず、リンク リストの最初のノードから直接データを格納します。

  • ヘッド ノードのない一方向キューでは、エンキュー操作はリンク リストの末尾に新しい要素を挿入し、デキュー操作はリンク リストのヘッド ノードを削除し、ヘッドの後続ノードを使用します。ノードをキューの新しいヘッド ノードとして使用します。

  • 各デキュー操作ではリンク リストのヘッド ノードを削除する必要があるため、ヘッド ノードのない一方向キューでの実装は頻繁にメモリの割り当てと解放を行うことになり、比較的非効率的です。

  その挿入操作の図は次のとおりです。
ここに画像の説明を挿入します

  キューノード構造の設計:

struct queueNode
{
    
    
	// 数据元素
	int elem;
	// 指向下一个元素
	struct queueNode *next;
};

  コード:

/********************** 不含头结点 ***************/
// 队列初始化
struct queue*vQueueInit()
{
    
    
	// 申请空间并且赋值
	struct queue*res = (struct queue*)malloc(sizeof(struct queue));
	res->lenth = 0;
	res->front = res->rear = NULL;
	return res;
}

// 打印队列
void vOutPutQueue(struct queue*queue)
{
    
    
	printf("queue:");
	struct queueNode*p = queue->front;
	// 遍历打印
	while (p)
	{
    
    
		printf("%d ",p->elem);
		p = p->next;
	}
	printf("\n");
}

// 入队列
void vPushQueueNode(struct queue*queue, int elem)
{
    
    
	// 申请一个节点
	struct queueNode *p = (struct queueNode*)malloc(sizeof(struct queueNode));
	p->elem = elem;
	p->next = NULL;
	// 队列为空时入队列
	if (queue->lenth == 0) 
	{
    
    
		queue->front = p;
		queue->rear = p;
	}
	else
	{
    
    
		// 先将节点接到队列上
		queue->rear->next = p;
		// 再移动队列尾指针
		queue->rear = p;
	}
	queue->lenth++;
}

// 出队列
bool vPullQueueNode(struct queue*queue, int&elem)
{
    
    
	// 判断队列是否为空
	if (vQueueIsEmpty(queue))
		return false;

	// 获取队列首元素 并返回
	struct queueNode*p = queue->front;
	elem = p->elem;
	queue->front = p->next;
	queue->lenth--;

	return true;
}

// 判断队列是否空
bool vQueueIsEmpty(struct queue*queue)
{
    
    
	return queue->lenth == 0;
}

  ヘッド ノードを含むキューの操作との違いは、主に、キューに入るときとキューから出すときにキューが空かどうかを判断する必要があることにあります。

循環キュー

  • 循環キューは、配列を使用して実装されたキューです。通常の配列とは異なり、循環キューの末尾ポインタと先頭ポインタは、配列の終わりに達すると配列の先頭に循環できます。

  • 循環キューでは、2 つのポインターを使用してキューの先頭と末尾をそれぞれ指すか、ポインターとキューの長さを使用してキューの先頭と末尾の位置を維持する必要があります。

  • 循環キューでは、エンキュー操作は、キューの最後に新しい要素を挿入することです。キューがいっぱいの場合は、キューフル エラーが返されます。デキュー操作は、キューの最初の要素を削除してポイントを削除します。次の要素への head ポインタ。

  • 循環キューの主な利点は、スペース使用率が比較的高く、循環ストレージを実現でき、操作の時間計算量が先頭ノードのある一方向キューや先頭ノードのない一方向キューよりも効率的であることです。先頭ノード、両方ともO ( 1 ) O ( 1)( 1 )

  キュー構造の設計:

// 队列的最大长度
#define QUEUEMAXSIZE 10
// 队列结构体
struct queueCirculate
{
    
    
	// 存储数据
	int data[QUEUEMAXSIZE];
	// 记录数量
	int count;
	// 记录队列队头
	int front;
	// 记录队列队尾
	int rear;
};

  キューノード構造の設計:

******************** 循环队列 ******************/
// 初始化循环队列
struct queueCirculate*queueCirculateInit(void)
{
    
    
	// 申请空间
	struct queueCirculate*res = (struct queueCirculate*)malloc(sizeof(struct queueCirculate));
	// 初始化队列的数量 队头队尾的位置
	res->count = 0;
	res->front = 0;
	res->rear = 0;

	return res;
}

// 入队列
bool pushQueueCirculate(struct queueCirculate*queue,int elem)
{
    
    
	// 判断队列是否已满
	if (queue->count == QUEUEMAXSIZE)
		return false;
	// 入队列
	queue->data[queue->rear] = elem;
	// 改变队尾指针的位置 以及 队列的数量
	queue->count++;
	if (queue->count < QUEUEMAXSIZE)
		queue->rear = (queue->rear + 1) % QUEUEMAXSIZE;
	return true;
}

// 出队列
bool pullQueueCirculate(struct queueCirculate*queue, int&elem)
{
    
    
	// 判断队列是否为空
	if (queue->count == 0)
		return false;
	// 出队列
	elem = queue->data[queue->front];
	// 移动队头指针 并且 改变队列元素数量
	queue->front = (queue->front + 1) % QUEUEMAXSIZE;
	queue->count--;
	return true;
}

// 队列是否为空
bool queueCirculateIsEmpty(struct queueCirculate*queue)
{
    
    
	return queue->count == 0;
}

// 打印队列
void outPutQueueCirculate(struct queueCirculate*queue)
{
    
    
	int count = queue->count;
	printf("queue: ");
	for (int i = 0; i < count; ++i)
	{
    
    
		int temp = -1;
		pullQueueCirculate(queue, temp);
		printf("%d ", temp);
	}
	printf("\n");
}

要約する

  先頭ノードのある一方向キュー、先頭ノードのない一方向キュー、および循環キューにはそれぞれ独自の長所と短所があり、さまざまなアプリケーション シナリオに適しています。

  先頭ノードの一方向キューは、頻繁なエンキュー操作と少数のデキュー操作が必要なシナリオに適しています。リンク リストの特性を利用して、エンキュー操作の時間計算量 O(1) を実現できますが、デキュー操作の時間計算量が O(n) になるという欠点があります。

  ヘッド ノードのない一方向キューは、キューの長さが短く、キューのエントリとキューからの取り出し操作を頻繁に行う必要があるシナリオに適しています。ヘッド ノードが使用されないため、スペースをいくらか節約できますが、デキュー操作ではメモリの割り当てと解放を頻繁に行う必要があり、比較的非効率的です。

  循環キューは、キューの長さが長く、エンキューおよびデキュー操作が頻繁に行われるシナリオに適しています。リングストレージ構造により、アレイの連続したストレージスペースを最大限に活用し、スペースの効率的な利用を実現します。同時に、エンキューおよびデキュー操作の時間計算量は O(1) であり、比較的効率的です。ただし、循環キューにはあらかじめ最大長が定められている必要があり、キュー長が最大長を超える場合には拡張操作が必要となります。さらに、リング構造の特殊性により、実装は比較的複雑です。

  したがって、キューの実装方法を選択する場合は、時間の複雑さ、スペースの使用率、特定のアプリケーション シナリオに基づいた実装の難易度などの要素を総合的に考慮し、最適なキューの実装方法を選択する必要があります。

おすすめ

転載: blog.csdn.net/qq_53960242/article/details/131124873