[데이터 구조 및 알고리즘 분석] C 언어를 사용하여 두 가지 유형의 큐(리드 노드 및 비리드 노드) 체인 스토리지를 구현하고 순환 큐에 대한 설계 아이디어 제공

머리말

  프로그램을 작성할 때 다양한 데이터 구조를 처리해야 하는 경우가 많습니다. 큐는 광범위한 애플리케이션 시나리오의 공통 데이터 구조입니다. 큐의 기본 작업에는 시뮬레이션된 대기 큐, 메시지 큐, 컴퓨터 캐시 등에 사용되는 조인 및 큐 제거가 포함됩니다.

  실제 프로그래밍에서는 다양한 데이터 구조를 사용하여 대기열을 구현할 수 있습니다. 이 기사에서는 주로 선두 노드가 있는 단방향 대기열, 선두 노드가 없는 단방향 대기열 및 순환 대기열을 포함하여 세 가지 다른 대기열 구현 방법을 소개합니다. 이들 큐 구현은 각각 연결리스트, 배열 등 서로 다른 데이터 구조를 사용하며, 구현 내용, 시간 복잡도, 공간 활용 측면에서 서로 다른 특성을 갖는다. 프로그래머의 경우 다양한 대기열 구현 방법을 이해하면 자신의 애플리케이션 시나리오에 적합한 대기열 구현 방법을 더 잘 선택하고 프로그램 효율성을 향상시킬 수 있습니다.

대기열 구현

  큐는 FIFO(선입선출) 데이터 구조로, 기본 작업에는 큐에 넣기(큐의 끝에 요소 삽입) 및 큐에서 빼기(큐의 첫 번째 요소 삭제)가 포함됩니다.

선두 노드 단방향 큐

  • 선두 노드 단방향 큐는 연결 리스트를 이용하여 구현한 큐로, 일반적인 연결 리스트와 달리 선두 노드 단방향 큐는 연결 리스트의 선두에 데이터를 저장하지 않는 노드를 선두 노드로 추가한다. 편의를 위한 연결 리스트 큐 작업.

  • 선행 노드의 단방향 대기열에서 대기열에 넣기 작업은 연결 목록의 끝에 새 요소를 삽입하는 것이며 대기열에서 빼기 작업은 첫 번째 요소의 후속 노드를 새로운 첫 번째 노드로 사용하는 것입니다.

  • Linked List의 특성상 선두 노드의 단방향 큐의 Enqueuing 연산은 O(1) O(1) 이다.O ( 1 ) , 큐 제거 작업에는 전체 연결 목록을 순회해야 하므로 시간 복잡도는O(n) O(n)O ( n ) , 其中nnn은 대기열의 요소 수를 나타냅니다.

  다음은 대기열 삽입 논리를 보여주는 다이어그램을 보여줍니다.
여기에 이미지 설명을 삽입하세요.

  대기열 노드 구조 설계:

// 节点结构体
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");
}

선행 노드가 없는 단방향 대기열

  • 선행 노드가 없는 단방향 큐와 비교하여 선행 노드가 없는 단방향 큐는 헤드 노드를 사용하지 않고 연결 목록의 첫 번째 노드에서 직접 데이터를 저장합니다.

  • 헤드 노드가 없는 단방향 큐에서 enqueuing 작업은 연결된 리스트의 tail에 새 요소를 삽입하는 작업이고, dequeuing 작업은 연결된 리스트의 헤드 노드를 삭제하고 헤드의 후속 노드를 사용하는 작업입니다. 노드를 대기열의 새 헤드 노드로 사용합니다.

  • Dequeue 작업을 수행할 때마다 연결 목록의 헤드 노드를 삭제해야 하므로 헤드 노드 없이 단방향 큐로 구현하면 메모리 할당과 해제가 자주 발생하므로 상대적으로 비효율적입니다.

  삽입 작업의 다이어그램은 다음과 같습니다.
여기에 이미지 설명을 삽입하세요.

  대기열 노드 구조 설계:

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;
}

  헤드 노드를 포함하는 대기열 작업과의 차이점은 주로 대기열에 들어가거나 대기열에서 제거할 때 대기열이 비어 있는지 여부를 확인해야 한다는 점입니다.

순환 대기열

  • 순환 큐는 배열을 사용하여 구현된 큐로 , 일반 배열과 달리 순환 큐의 테일 포인터와 헤드 포인터는 배열의 끝에 도달하면 배열의 시작 부분으로 순환할 수 있습니다.

  • 순환 큐에서는 두 개의 포인터를 사용하여 큐의 헤드와 테일을 각각 가리키거나 포인터와 큐 길이를 사용하여 큐의 헤드와 테일 위치를 유지해야 합니다.

  • 순환 대기열에서 대기열에 넣기 작업은 대기열 끝에 새 요소를 삽입하는 것입니다. 대기열이 가득 차면 대기열 가득 참 오류가 반환됩니다. 대기열에서 빼기 작업은 대기열의 첫 번째 요소를 삭제하고 포인트를 지정하는 것입니다. 다음 요소에 대한 헤드 포인터. .

  • 순환 대기열의 가장 큰 장점은 공간 활용도가 상대적으로 높고 순환 저장을 실현할 수 있으며 작업의 시간 복잡도가 선두 노드가 있는 단방향 대기열과 없는 단방향 대기열보다 효율적이라는 점입니다. 선행 노드는 둘 다 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");
}

요약하다

  선두 노드가 있는 단방향 대기열, 선두 노드가 없는 단방향 대기열 및 순환 대기열은 각각 고유한 장점과 단점이 있으며 다양한 애플리케이션 시나리오에 적합합니다.

  선행 노드의 단방향 대기열은 대기열에 넣기 작업이 자주 필요하고 대기열에서 빼기 작업이 거의 필요한 시나리오에 적합합니다. Linked List의 특성을 활용하여 Enqueuing 작업의 시간 복잡도를 O(1)로 얻을 수 있지만, Dequeuing 작업의 시간 복잡도가 O(n)이라는 단점이 있습니다.

  헤드 노드가 없는 단방향 대기열은 대기열 길이가 짧고 대기열 항목 및 대기열 제거 작업이 자주 필요한 시나리오에 적합합니다. 헤드 노드를 사용하지 않기 때문에 어느 정도 공간을 절약할 수 있지만, Dequeue 작업을 수행하려면 메모리 할당과 해제가 자주 필요하므로 상대적으로 비효율적입니다.

  순환 큐는 큐 길이가 길고 큐에 넣기 및 큐에서 빼기 작업이 빈번한 시나리오에 적합합니다. 링 스토리지 구조는 어레이의 연속적인 스토리지 공간을 최대한 활용하고 효율적인 공간 활용을 달성할 수 있습니다. 동시에, 대기열에 넣기와 대기열에서 빼기 작업의 시간 복잡도는 O(1)로 상대적으로 효율적입니다. 그러나 순환 큐의 경우 사전에 최대 길이가 정의되어 있어야 하며, 큐 길이가 최대 길이를 초과하는 경우 확장 작업이 필요하다. 또한 링 구조의 특수성으로 인해 구현이 상대적으로 복잡합니다.

  따라서 대기열 구현 방법을 선택할 때는 특정 애플리케이션 시나리오에 따른 시간 복잡도, 공간 활용도, 구현 난이도 등의 요소를 종합적으로 고려하고 가장 적합한 대기열 구현 방법을 선택해야 합니다.

추천

출처blog.csdn.net/qq_53960242/article/details/131124873