面试题·栈和队列的相互实现·详解-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
这个函数释放了栈所使用的所有内存。

改进建议

  1. 避免不必要的元素移动:在pop和top操作中,只需要移动除了栈顶元素之外的其他元素。
  2. 减少指针重置:在交换数组后,不需要总是重置所有的前端和后端指针。
  3. 考虑使用真正的链式队列:如果你想要一个链式栈,你应该使用链式队列(通过节点连接)而不是数组。链式实现会更灵活,并且在处理大量数据时可能更高效。
  4. 错误处理:在分配内存时,应该检查malloc是否成功,并在失败时返回错误或进行其他处理。
  5. 边界检查:在pop和top操作中,应该检查栈是否为空,以避免访问空指针或未定义的内存。
  6. 优化内存使用:如果你知道栈的大小上限,可以使用固定大小的数组。但是,如果你想要一个可以动态增长的栈,那么链式实现可能更合适。

下面是优化后的代码

在这个版本中,我们使用了一个指针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);  
    }  
}
上一篇:python Z-score标准化-sklearn库实现Z-score标准化


下一篇:【面试】Java虚拟机的生命周期