A. 用队列实现栈
用队列实现栈
实现代码如下
看着是队列,其实实际实现更接近数组模拟
typedef struct {
int* queue1; // 第一个队列
int* queue2; // 第二个队列
int size; // 栈的大小
int front1, rear1, front2, rear2; // 两个队列的首尾指针
} MyStack;
//初始化栈
MyStack* myStackCreate() {
MyStack* stack = (MyStack*)malloc(sizeof(MyStack));
stack->queue1 = (int*)malloc(sizeof(int) * 100);
stack->queue2 = (int*)malloc(sizeof(int) * 100);
stack->size = 0;
stack->front1 = stack->rear1 = 0;
stack->front2 = stack->rear2 = 0;
return stack;
}
//将元素 x 压入栈顶
void myStackPush(MyStack* obj, int x) {
obj->queue1[obj->rear1++] = x; // 将元素加入 queue1 的尾部
obj->size++;
}
//移除并返回栈顶元素
int myStackPop(MyStack* obj) {
int i;
// 将 queue1 中的元素(除了最后一个)转移到 queue2 中
for (i = 0; i < obj->size - 1; i++) {
obj->queue2[obj->rear2++] = obj->queue1[obj->front1++];
}
int top = obj->queue1[obj->front1++]; // 获取栈顶元素
obj->size--;
// 交换 queue1 和 queue2,为下次操作做准备
int* temp = obj->queue1;
obj->queue1 = obj->queue2;
obj->queue2 = temp;
obj->front1 = 0;
obj->rear1 = obj->size;
obj->front2 = 0;
obj->rear2 = 0;
return top;
}
//返回栈顶元素
int myStackTop(MyStack* obj) {
int i;
// 将 queue1 中的元素转移到 queue2 中
for (i = 0; i < obj->size; i++) {
obj->queue2[obj->rear2++] = obj->queue1[obj->front1++];
}
int top = obj->queue2[obj->rear2 - 1]; // 获取栈顶元素
// 交换 queue1 和 queue2,为下次操作做准备
int* temp = obj->queue1;
obj->queue1 = obj->queue2;
obj->queue2 = temp;
obj->front1 = 0;
obj->rear1 = obj->size;
obj->front2 = 0;
obj->rear2 = 0;
return top;
}
//如果栈是空的,返回 true;否则,返回 false
bool myStackEmpty(MyStack* obj) {
return obj->size == 0;
}
void myStackFree(MyStack* obj) {
free(obj->queue1);
free(obj->queue2);
free(obj);
}
虽然注释已经够详细了,但是我们还是来逐步分析一下
代码实际上并没有使用链式队列,而是使用了两个固定大小的数组来模拟栈的行为。
首先,我们定义了一个结构体MyStack,它包含两个整数数组queue1和queue2(其实在这里,它们更像栈,因为数组是按照后进先出的顺序使用的),以及它们的前后端指针和栈的大小。
myStackCreate
这个函数用于初始化栈。它分配了MyStack结构体的内存,并为两个数组分配了内存。然后,它初始化了所有的指针和大小为0。
myStackPush
这个函数用于将元素压入栈顶。它简单地将元素添加到queue1的尾部,并更新rear1指针和栈的大小。
myStackPop
这个函数用于移除并返回栈顶元素。但是,这里有一个问题:它实际上将整个queue1(除了最后一个元素)转移到了queue2,然后返回了queue1的最后一个元素。之后,它交换了两个数组,并重置了所有的指针。这样做在每次pop操作时都非常低效,因为它涉及到大量元素的移动。
一个更高效的方法是,每次pop时,只需要记录queue1的最后一个元素(即栈顶元素),然后将其余元素(如果有的话)从queue1转移到queue2,并交换两个队列的引用。但是,在交换后,不需要重置所有的指针,因为queue2的front2和rear2已经指向了正确的位置。
myStackTop
这个函数与myStackPop非常相似,但是它返回栈顶元素而不移除它。然而,与myStackPop一样,它也涉及到了不必要的元素移动和数组交换。
myStackEmpty
这个函数检查栈是否为空,通过检查栈的大小是否为0来实现。
myStackFree
这个函数释放了栈所使用的所有内存。
改进建议
- 避免不必要的元素移动:在pop和top操作中,只需要移动除了栈顶元素之外的其他元素。
- 减少指针重置:在交换数组后,不需要总是重置所有的前端和后端指针。
- 考虑使用真正的链式队列:如果你想要一个链式栈,你应该使用链式队列(通过节点连接)而不是数组。链式实现会更灵活,并且在处理大量数据时可能更高效。
- 错误处理:在分配内存时,应该检查malloc是否成功,并在失败时返回错误或进行其他处理。
- 边界检查:在pop和top操作中,应该检查栈是否为空,以避免访问空指针或未定义的内存。
- 优化内存使用:如果你知道栈的大小上限,可以使用固定大小的数组。但是,如果你想要一个可以动态增长的栈,那么链式实现可能更合适。
下面是优化后的代码
在这个版本中,我们使用了一个指针queue和两个布尔值(但实际上我们只需要一个,因为!isQueue1就等同于isQueue2)来追踪当前正在使用的队列。当栈满时,我们尝试扩容,并将元素从queue2(如果它包含元素)复制到queue1。我们还添加了一个检查,以便在queue1使用了超过一半的空间时,将元素从queue1复制到queue2,并切换队列,以保持两个队列之间的负载均衡。
我们还在myStackFree函数中释放了内存,并处理了malloc和realloc可能返回NULL的情况。
#include <stdlib.h>
#include <stdbool.h>
#define INITIAL_CAPACITY 100
typedef struct {
int* queue;
int capacity;
int top;
bool isQueue1; // 表示当前正在使用queue1还是queue2
} MyStack;
MyStack* myStackCreate() {
MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
if (!obj) return NULL;
obj->queue = (int*)malloc(INITIAL_CAPACITY * sizeof(int));
if (!obj->queue) {
free(obj);
return NULL;
}
obj->capacity = INITIAL_CAPACITY;
obj->top = -1; // 栈顶指针初始化为-1,表示空栈
obj->isQueue1 = true; // 初始时使用queue1
return obj;
}
void myStackPush(MyStack* obj, int x) {
if (obj->top == obj->capacity - 1) { // 栈满,扩容
int newCapacity = obj->capacity * 2;
int* newQueue = (int*)realloc(obj->isQueue1 ? obj->queue : (obj->queue - INITIAL_CAPACITY), newCapacity * sizeof(int));
if (!newQueue) return; // 扩容失败
obj->queue = newQueue + (obj->isQueue1 ? 0 : INITIAL_CAPACITY); // 调整指针,确保obj->queue始终指向当前使用的队列
obj->capacity = newCapacity;
// 如果之前使用queue2,现在切换回queue1,则需要复制数据
if (!obj->isQueue1) {
for (int i = 0; i <= obj->top; i++) {
obj->queue[i] = obj->queue[i + INITIAL_CAPACITY];
}
obj->isQueue1 = true; // 切换回queue1
}
}
obj->queue[++obj->top] = x; // 压栈
}
int myStackPop(MyStack* obj) {
if (obj->top == -1) return -1; // 栈空
int x = obj->queue[obj->top--]; // 弹出栈顶元素
// 如果queue1已经使用了超过一半的空间,且queue2是空的,则考虑将queue1的元素移到queue2,并切换队列
if (obj->isQueue1 && obj->top > obj->capacity / 4) {
for (int i = 0; i <= obj->top; i++) {
obj->queue[i + INITIAL_CAPACITY] = obj->queue[i];
}
obj->isQueue1 = false; // 切换到queue2
obj->top += INITIAL_CAPACITY; // 更新top指针
}
return x;
}
int myStackTop(MyStack* obj) {
if (obj->top == -1) return -1; // 栈空
return obj->queue[obj->top]; // 返回栈顶元素
}
bool myStackEmpty(MyStack* obj) {
return obj->top == -1; // 检查栈是否为空
}
void myStackFree(MyStack* obj) {
if (obj) {
free(obj->queue - (obj->isQueue1 ? 0 : INITIAL_CAPACITY)); // 释放队列内存
free(obj);
}
}
B. 用栈实现队列
用栈实现队列
那用链栈来实现一下看看
// 链表节点定义
typedef struct Node {
int data;
struct Node* next;
} Node;
// 栈结构定义
typedef struct Stack {
Node* top;
int size;
} Stack;
// MyQueue结构体定义,包含两个栈
typedef struct MyQueue {
Stack* stack1;
Stack* stack2;
} MyQueue;
// 创建新的节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (!newNode) {
perror("Memory allocation failed");
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 初始化栈
Stack* createStack() {
Stack* stack = (Stack*)malloc(sizeof(Stack));
if (!stack) {
perror("Memory allocation failed");
exit(EXIT_FAILURE);
}
stack->top = NULL;
stack->size = 0;
return stack;
}
// 压栈操作
void push(Stack* stack, int data) {
Node* newNode = createNode(data);
newNode->next = stack->top;
stack->top = newNode;
stack->size++;
}
// 出栈操作
int pop(Stack* stack) {
if (stack->size == 0) {
printf("Stack is empty.\n");
exit(EXIT_FAILURE);
}
Node* temp = stack->top;
int data = temp->data;
stack->top = temp->next;
free(temp);
stack->size--;
return data;
}
// 查看栈顶元素
int peek(Stack* stack) {
if (stack->size == 0) {
printf("Stack is empty.\n");
exit(EXIT_FAILURE);
}
return stack->top->data;
}
// 判断栈是否为空
int isEmpty(Stack* stack) {
return stack->size == 0;
}
// 释放栈内存
void freeStack(Stack* stack) {
Node* current = stack->top;
Node* temp;
while (current != NULL) {
temp = current;
current = current->next;
free(temp);
}
free(stack);
}
MyQueue* myQueueCreate() {
MyQueue* queue = (MyQueue*)malloc(sizeof(MyQueue));
if (!queue) {
perror("Memory allocation failed for MyQueue");
exit(EXIT_FAILURE);
}
queue->stack1 = createStack();
queue->stack2 = createStack();
return queue;
}
void myQueuePush(MyQueue* queue, int data) {
push(queue->stack1, data);
}
int myQueuePop(MyQueue* queue) {
if (isEmpty(queue->stack2)) {
while (!isEmpty(queue->stack1)) {
push(queue->stack2, pop(queue->stack1));
}
}
if (isEmpty(queue->stack2)) {
return -1; // 队列为空,无法弹出元素
}
return pop(queue->stack2);
}
int myQueuePeek(MyQueue* queue) {
if (isEmpty(queue->stack2)) {
while (!isEmpty(queue->stack1)) {
push(queue->stack2, pop(queue->stack1));
}
}
if (isEmpty(queue->stack2)) {
return -1; // 队列为空,无法查看队首元素
}
return peek(queue->stack2);
}
bool myQueueEmpty(MyQueue* queue) {
return isEmpty(queue->stack1) && isEmpty(queue->stack2);
}
void myQueueFree(MyQueue* queue) {
freeStack(queue->stack1);
freeStack(queue->stack2);
free(queue);
}
这段代码实现了一个基于两个栈的队列结构 MyQueue
。下面我们来逐步解释这段代码:
-
链表节点定义:
Node
结构体定义了链表节点, 包含一个整数值data
和指向下一个节点的指针next
。
-
栈结构定义:
Stack
结构体定义了一个栈, 包含指向栈顶元素的指针top
和栈的大小size
。
-
MyQueue
结构体定义:MyQueue
结构体包含两个Stack
指针,stack1
和stack2
, 用于实现队列的功能。
-
创建新的节点:
createNode
函数用于创建一个新的链表节点, 并分配内存。如果内存分配失败, 则输出错误信息并退出程序。
-
初始化栈:
createStack
函数用于创建一个新的栈, 将栈顶指针设为NULL
, 并将栈的大小设为 0。如果内存分配失败, 则输出错误信息并退出程序。
-
压栈操作:
push
函数用于将一个元素压入栈, 首先创建一个新节点, 然后将新节点的next
指针指向当前栈顶元素, 最后更新栈顶指针和栈的大小。
-
出栈操作:
pop
函数用于从栈中弹出一个元素, 首先检查栈是否为空, 如果为空则输出错误信息并退出程序。然后获取栈顶元素的数据, 更新栈顶指针, 释放栈顶元素的内存, 并更新栈的大小。
-
查看栈顶元素:
peek
函数用于查看栈顶元素, 首先检查栈是否为空, 如果为空则输出错误信息并退出程序。然后返回栈顶元素的数据。
-
判断栈是否为空:
isEmpty
函数用于判断栈是否为空, 返回栈的大小是否为 0。
-
释放栈内存:
freeStack
函数用于释放栈占用的内存, 遍历链表并逐个释放每个节点的内存, 最后释放栈本身的内存。
-
创建
MyQueue
:myQueueCreate
函数用于创建一个新的MyQueue
对象, 分配内存并初始化两个Stack
对象。如果内存分配失败, 则输出错误信息并退出程序。
-
入队操作:
myQueuePush
函数用于将一个元素入队, 直接将元素压入stack1
即可。
-
出队操作:
myQueuePop
函数用于从队列中出队一个元素。首先检查stack2
是否为空, 如果为空则将stack1
中的元素全部弹出并压入stack2
。然后从stack2
中弹出并返回队首元素。如果两个栈都为空, 则返回 -1 表示队列为空。
-
查看队首元素:
myQueuePeek
函数用于查看队首元素。与出队操作类似, 首先检查stack2
是否为空, 如果为空则将stack1
中的元素全部弹出并压入stack2
。然后返回stack2
的栈顶元素。如果两个栈都为空, 则返回 -1 表示队列为空。
-
判断队列是否为空:
myQueueEmpty
函数用于判断队列是否为空, 返回stack1
和stack2
是否同时为空。
-
释放
MyQueue
内存:myQueueFree
函数用于释放MyQueue
对象占用的内存, 首先释放stack1
和stack2
的内存, 然后释放MyQueue
对象本身的内存。
那么到这里,本篇文章就结束了,这两题虽然没有实用的意义,但是用来理解栈和队列还是非常不错的
期待与你的下次相见!!!
下期预告~二叉树(上)-堆