数据结构-队列 c语言使用链表和数组分别实现
1.数据结构-队列 c语言使用链表和数组分别实现
2.手把手教你使用C语言实现堆栈数据结构算法-两种方式(链表+数组)队列
定义
队列(queue)是一种遵循先入后到规则的线性数据结构,将队列头部称为“队首”,尾部称为“队尾”,把元素加入队尾称为“入队”,删除队首元素称为“出队”。
队列实现
基于链表的实现
将链表的头节点和尾结点分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。
/* 基于链表实现的队列 */
typedef struct
{
ListNode *front, *rear;
int queSize;
} LinkedListQueue;
/* 构造函数 */
LinkedListQueue *newLinkedListQueue()
{
LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue));
queue->front = NULL;
queue->rear = NULL;
queue->queSize = 0;
return queue;
}
/* 析构函数 */
void delLinkedListQueue(LinkedListQueue *queue)
{
// 释放所有节点
while (queue->front != NULL)
{
ListNode *tmp = queue->front;
queue->front = queue->front->next;
free(tmp);
}
// 释放 queue 结构体
free(queue);
}
/* 获取队列的长度 */
int size(LinkedListQueue *queue)
{
return queue->queSize;
}
/* 判断队列是否为空 */
bool empty(LinkedListQueue *queue)
{
return (size(queue) == 0);
}
/* 入队 */
void push(LinkedListQueue *queue, int num)
{
// 尾节点处添加 node
ListNode *node = newListNode(num);
// 如果队列为空,则令头、尾节点都指向该节点
if (queue->front == NULL)
{
queue->front = node;
queue->rear = node;
}
// 如果队列不为空,则将该节点添加到尾节点后
else
{
queue->rear->next = node;
queue->rear = node;
}
queue->queSize++;
}
/* 访问队首元素 */
int peek(LinkedListQueue *queue)
{
assert(size(queue) && queue->front); // 队列为空时,不能访问队首元素,assert是宏,用于断言,断言失败会终止程序
return queue->front->val;
}
/* 出队 */
int pop(LinkedListQueue *queue)
{
int num = peek(queue);
ListNode *tmp = queue->front;
queue->front = queue->front->next;
free(tmp);
queue->queSize--;
return num;
}
/* 打印队列 */
void printLinkedListQueue(LinkedListQueue *queue)
{
int *arr = malloc(sizeof(int) * queue->queSize);
// 拷贝链表中的数据到数组
int i;
ListNode *node;
for (i = 0, node = queue->front; i < queue->queSize; i++)
{
arr[i] = node->val;
node = node->next;
}
printArray(arr, queue->queSize);
free(arr);
}
基于数组实现
在数组中删除首元素的时间复杂度为O(N),导致出队效率低,可以用一个巧妙方法避免这个问题。
使用一个变量front
指向队首元素的索引,并维护一个变量size
用于记录队列长度,则定义rear=front+size
,计算出rear
指向队尾元素的下个位置,则数组中包含元素的有效区间为[front,rear-1]
,这时,出入队操作只需进行一次操作,时间复杂度均为O(1).
有一个问题:在不断入队和出队的过程中,front和rear都在向右移动,当他们到达数组尾部就无法继续移动了,为了解决这个问题,可以将数组视为首尾相接的“环形数组”,
实现思路:我们需要将front或rear再越过数组尾部时,直接回到数组头部继续遍历,这种周期性规律可以通过“取余操作”来实现
/* 基于环形数组实现的队列 */
typedef struct
{
int *nums; // 用于存储队列元素的数组
int front; // 队首指针,指向队首元素
int queSize; // 队列长度,即队列中元素的个数
int queCapacity; // 队列容量,即数组长度
} ArrayQueue;
/* 构造函数 */
ArrayQueue *newArrayQueue(int capacity)
{
ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));
// 初始化数组
queue->queCapacity = capacity;
queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity);
queue->front = queue->queSize = 0;
return queue;
}
/* 析构函数 */
void delArrayQueue(ArrayQueue *queue)
{
free(queue->nums);
free(queue);
}
/* 获取队列的容量 */
int capacity(ArrayQueue *queue)
{
return queue->queCapacity;
}
/* 获取队列的长度 */
int size(ArrayQueue *queue)
{
return queue->queSize;
}
/* 判断队列是否为空 */
bool empty(ArrayQueue *queue)
{
return queue->queSize == 0;
}
/* 访问队首元素 */
int peek(ArrayQueue *queue)
{
assert(size(queue) != 0);
return queue->nums[queue->front];
}
/* 入队 */
void push(ArrayQueue *queue, int num)
{
if (size(queue) == capacity(queue))
{
printf("队列已满\r\n");
return;
}
// 计算队尾指针,指向队尾索引 + 1
// 通过取余操作实现 rear 越过数组尾部后回到头部
int rear = (queue->front + queue->queSize) % queue->queCapacity;
// 将 num 添加至队尾
queue->nums[rear] = num;
queue->queSize++;
}
/* 出队 */
int pop(ArrayQueue *queue)
{
int num = peek(queue);
// 队首指针向后移动一位,若越过尾部,则返回到数组头部
queue->front = (queue->front + 1) % queue->queCapacity;
queue->queSize--;
return num;
}
典型应用
- 淘宝订单
- 代办事项:先来后到功能