面试题·栈和队列的相互实现·详解

news2024/12/22 20:42:53

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

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。下面我们来逐步解释这段代码:

  1. 链表节点定义

    • Node 结构体定义了链表节点, 包含一个整数值 data 和指向下一个节点的指针 next
  2. 栈结构定义

    • Stack 结构体定义了一个栈, 包含指向栈顶元素的指针 top 和栈的大小 size
  3. MyQueue 结构体定义

    • MyQueue 结构体包含两个 Stack 指针, stack1stack2, 用于实现队列的功能。
  4. 创建新的节点:

    • createNode 函数用于创建一个新的链表节点, 并分配内存。如果内存分配失败, 则输出错误信息并退出程序。
  5. 初始化栈:

    • createStack 函数用于创建一个新的栈, 将栈顶指针设为 NULL, 并将栈的大小设为 0。如果内存分配失败, 则输出错误信息并退出程序。
  6. 压栈操作:

    • push 函数用于将一个元素压入栈, 首先创建一个新节点, 然后将新节点的 next 指针指向当前栈顶元素, 最后更新栈顶指针和栈的大小。
  7. 出栈操作:

    • pop 函数用于从栈中弹出一个元素, 首先检查栈是否为空, 如果为空则输出错误信息并退出程序。然后获取栈顶元素的数据, 更新栈顶指针, 释放栈顶元素的内存, 并更新栈的大小。
  8. 查看栈顶元素:

    • peek 函数用于查看栈顶元素, 首先检查栈是否为空, 如果为空则输出错误信息并退出程序。然后返回栈顶元素的数据。
  9. 判断栈是否为空:

    • isEmpty 函数用于判断栈是否为空, 返回栈的大小是否为 0。
  10. 释放栈内存:

    • freeStack 函数用于释放栈占用的内存, 遍历链表并逐个释放每个节点的内存, 最后释放栈本身的内存。
  11. 创建 MyQueue:

    • myQueueCreate 函数用于创建一个新的 MyQueue 对象, 分配内存并初始化两个 Stack 对象。如果内存分配失败, 则输出错误信息并退出程序。
  12. 入队操作:

    • myQueuePush 函数用于将一个元素入队, 直接将元素压入 stack1 即可。
  13. 出队操作:

    • myQueuePop 函数用于从队列中出队一个元素。首先检查 stack2 是否为空, 如果为空则将 stack1 中的元素全部弹出并压入 stack2。然后从 stack2 中弹出并返回队首元素。如果两个栈都为空, 则返回 -1 表示队列为空。
  14. 查看队首元素:

    • myQueuePeek 函数用于查看队首元素。与出队操作类似, 首先检查 stack2 是否为空, 如果为空则将 stack1 中的元素全部弹出并压入 stack2。然后返回 stack2 的栈顶元素。如果两个栈都为空, 则返回 -1 表示队列为空。
  15. 判断队列是否为空:

    • myQueueEmpty 函数用于判断队列是否为空, 返回 stack1stack2 是否同时为空。
  16. 释放 MyQueue 内存:

    • myQueueFree 函数用于释放 MyQueue 对象占用的内存, 首先释放 stack1stack2 的内存, 然后释放 MyQueue 对象本身的内存。

那么到这里,本篇文章就结束了,这两题虽然没有实用的意义,但是用来理解栈和队列还是非常不错的

期待与你的下次相见!!!
下期预告~二叉树(上)-堆

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1698510.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gfast:基于全新Go Frame 2.3+Vue3+Element Plus构建的全栈前后端分离管理系统

gfast&#xff1a;基于全新Go Frame 2.3Vue3Element Plus构建的全栈前后端分离管理系统 随着信息技术的飞速发展和数字化转型的深入&#xff0c;后台管理系统在企业信息化建设中扮演着越来越重要的角色。为了满足市场对于高效、灵活、安全后台管理系统的需求&#xff0c;gfast应…

ICQ 将于 6 月关闭,这是一种奇怪的方式,发现它在 2024 年仍然活跃

你知道ICQ还活着吗&#xff1f;不过&#xff0c;不要太兴奋;它将永远消失。 还记得ICQ吗&#xff1f;如果你这样做了&#xff0c;你可能会记得它是AOL在1998年购买的Messenger客户端&#xff0c;就在Yahoo Instant Messager和MSN Messenger加入竞争的时候。然后Skype出现了&…

网络模型-BFD与网络协议联动

一、BFD:双向转发检测 双向转发检测BFD(Bidirectional Forwarding Detection)是一种全网统一的检测机制&#xff0c;用于快速检测、监控网络中链路或者IP路由的转发连通状况。 1、BFD优点: 对相邻转发引擎之间的通道提供轻负荷、快速故障检测。这些故障包括接口数据链路&#…

g-h Filter 详细讲解

g-h 过滤器 g-h 滤波器百科介绍。 之前的翻译大家&#xff0c;我看都没什么阅读量&#xff0c;可能大家都不是很想看&#xff08;估计也是我英文太水&#xff09;。那么这篇博客我就先暂停直接翻译原文&#xff0c;而是直接说一下自己的理解。 本文章背后的书的详细介绍可以…

单片机原理及技术(二)—— AT89S51单片机(一)(C51编程)

目录 一、AT89S51单片机的片内硬件结构 二、AT89S51的引脚功能 2.1 电源及时钟引脚 2.2 控制引脚 2.3 并行 I/O口引脚 三、AT89S51的CPU 3.1 运算器 3.1.1 算术逻辑单元&#xff08;ALU&#xff09; 3.1.2 累加器A 3.1.3 程序状态字寄存器&#xff08;PSW&#xff09…

Linux-应用编程学习笔记(三、文件属性和目录)

一、文件类型 1、普通文件&#xff08;ls -l 文件&#xff0c;权限前边第一个"-"代表普通文件&#xff1b;stat 文件&#xff09; 文本文件&#xff1a;ASCII字符 二进制文件&#xff1a;数字0/1 2、目录文件&#xff08;‘’d&#xff09;&#xff1a;文件夹 3…

声压级越大,STIPA 越好,公共广播就越清晰吗?

在公共广播中&#xff0c;有些朋友经常问到是不是声压越大&#xff0c;广播清晰度就越高&#xff0c;下面我从搜集了一些专业技术资料&#xff0c;供大家参考。 一、声压级越大&#xff0c;STIPA 越好吗&#xff1f; 不完全是。最初&#xff0c;人们认为当声压级达到 60 dBA 以…

STL源码刨析:序列式容器之vector

目录 1.序列式容器和关联式容器 2.vector的定义和结构 3.vector的构造函数和析构函数的实现 4.vector的数据结构以及实现源码 5.vector的元素操作 前言 本系列将重点对STL中的容器进行讲解&#xff0c;而在容器的分类中&#xff0c;我们将容器分为序列式容器和关联式容器。本章…

氢燃料电池汽车行业发展

文章目录 前言 市场分布 整车销售 发动机配套 氢气供应 发展动能 参考文献 前言 见《氢燃料电池技术综述》 见《燃料电池工作原理详解》 见《燃料电池发电系统详解》 见《燃料电池电动汽车详解》 市场分布 纵观全球的燃料电池汽车市场&#xff0c;截至2022年底&#xff…

关于Iterator 和ListIterator的详解

1.Iterator Iterator的定义如下&#xff1a; public interface Iterator<E> {} Iterator是一个接口&#xff0c;它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。Iterator提供的API接口如下&#xff1a; forEachRemaining(Consumer<? super E> act…

阿里架构师整理:100套Java经典实战项目+源码!拿走玩去,练不会我直接退出IT

技术学习的目的是进行项目开发&#xff0c;但是很多同学苦于自学没有项目练手&#xff0c;被面试官问到项目经验&#xff0c;项目就成了自己的短板。小编特地收集了阿里架构师整理的java实战项目来满足大家的需求&#xff0c;让大家在实战中不断成长&#xff01; 话不多说了&…

软件web化的趋势

引言 在信息技术飞速发展的今天&#xff0c;软件Web化已成为一个不可忽视的趋势。所谓软件Web化&#xff0c;即将传统的桌面应用软件转变为基于Web的应用程序&#xff0c;使用户能够通过浏览器进行访问和使用。传统软件通常需要在用户的计算机上进行安装和运行&#xff0c;而W…

浅谈网络通信(1)

文章目录 一、认识一些网络基础概念1.1、ip地址1.2、端口号1.3、协议1.4、协议分层1.5、协议分层的2种方式1.5.1、OSI七层模型1.5.2、TCP/IP五层模型[!]1.5.2.1、TCP/IP五层协议各层的含义及功能 二、网络中数据传输的基本流程——封装、分用2.1、封装2.2、分用2.2.1、5元组 三…

中科蓝讯AB32VG1中文寄存器说明GPIO端口操作

1 GPIO管理 1.1 GPIO通用控制寄存器 寄存器 1- 1 GPIOA&#xff1a;端口 A 数据寄存器 位寄存器名模式缺省描述31:8---未使用7:0GPIOA写0x00PAx 数据。当 PAx 用作 GPIO 时有效 0&#xff1a;读取时PAx为输入低电平状态&#xff0c;写入时PAx为输出低电平; 1&#xff1a;PAx…

Exel 求某行数最大值

方法1 MAX&#xff08; 选中比较数回车

visual studio code生成代码模板

编写需要生成代码片段的代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"wid…

基于深度学习的入侵检测系统综述文献概述

好长时间不发博客了&#xff0c;不是因为我摆烂了&#xff0c;是我换研究方向了&#xff0c;以后我就要搞科研了。使用博客记录我的科研故事&#xff0c;邀诸君共同见证我的科研之路。 1、研究方向的背景是什么&#xff1f; &#xff08;1&#xff09;互联网发展迅速&#xff…

基础IO用户缓冲区 、inode、硬软链接【Linux】

文章目录 用户缓冲区磁盘磁盘分区EXT2文件系统的存储方案 inode软链接硬链接 用户缓冲区 代码一&#xff1a; 1 #include<stdio.h>2 #include<unistd.h>3 #include<string.h> 4 int main()5 {6 const char * fstr &…

从XPS迁移到IP Integrator

从XPS迁移到IP Integrator 概述 AMD Vivado™设计套件IP集成器可让您将包含AMD的设计缝合在一起 IP或您的自定义IP在相对较短的时间内&#xff0c;在GUI环境中工作。 就像在Xilinx Platform Studio中一样&#xff0c;您可以快速创建嵌入式处理器设计&#xff08;使用&#xff0…

中兴通讯助力中国移动,推动SPN AI节能技术于23省规模部署

SPN作为中国移动自主创新的新一代综合承载网络&#xff0c;相比PTN设备&#xff0c;SPN的单机容量及性能有大幅提升&#xff0c;整机功耗也相应变大。在当前国家双碳政策的目标下&#xff0c;SPN设备的节能降耗也日益成为中国移动关注的焦点。因此&#xff0c;中国移动选择与中…