当前位置: 首页 > article >正文

Leetcode:栈和队列的互相实现

目录

一、用两个队列实现栈

题目:

分析:

代码实现:

 二、用两个栈实现队列

题目:

 分析:

代码:

总结:核心就在于先进先出和后进先出之间的一个灵活变换,两个栈能够先进先出,而两个队列能够后进先出 


一、用两个队列实现栈

题目:

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=O83Ahttps://leetcode.cn/problems/implement-stack-using-queues/description/

 

分析:

首先,我们明确队列有几个接口,初始化、入队、出队、取队头、取队尾、判空、返回有效元素个数、销毁。

题目要求只使用队列标准操作实现栈的四种操作,push、pop、top、empty,实际要求我们主要要能够实现栈存储元素后进先出的特点即可。

那如何利用两个队列实现栈的后进先出呢?

 

代码实现:

(注释在代码中哦,一些错误的代码我给注释掉的不用管)

//两个队列,一个装元素,另一个用来倒

typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
    QDataType data;
}QNode;

//定义一个包含队头和队尾的结构体
typedef struct Queue
{
	QNode* front;
	QNode* rear;
    int size;//标记队列中元素个数
}Queue;

void QueueInit(Queue* q)
{
	assert(q);
	/*QNode* tem = (QNode*)malloc(sizeof(QNode));
	if (tem == NULL)
	{
		perror("malloc");
		return;
	}*/
	q->front = q->rear = NULL;
	//q->front->next = NULL;
	//q->front->data = 0;
	q->size = 0;
}

//通过尾插在队尾入队列
void QueuePush(Queue* q,QDataType x)
{
	assert(q);
	QNode* tem = (QNode*)malloc(sizeof(QNode));
	if (tem == NULL)
	{
		assert("malloc");
		return;
	}
    
	if (q->front == NULL)//第一个节点
	{
		q->front = q->rear = tem;
		q->front->data = x;
        tem->next=NULL;//别忘了tem的next置空
	}
	else//第二及以后节点
	{
		q->rear->next = tem;
		q->rear = tem;
		q->rear->next = NULL;
		q->rear->data = x;
	}
	q->size++;

    // tem->next = NULL;
    // tem->data = x;
    // if (q->rear == NULL) {
    //     q->front = q->rear = tem;
    // } else {
    //     q->rear->next = tem;
    //     q->rear = tem; // 更新tail的指向
    // }
 
    // q->size++; // push一下节点个数++

}

//通过头删实现在队头出队列
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->front);//头指针不为空才存在删
	QNode* tem = q->front;
	q->front = q->front->next;
	if (q->front == NULL)
		q->rear = NULL;//避免尾指针变成野指针
	free(tem);
	q->size--;
}

//获取队列头部元素
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->front);
	return q->front->data;
}

//获取队列尾部元素
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->rear);
	return q->rear->data;
}

//获取队列中有效元素个数
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* q)
{
	if (q->size == 0)
		return 1;
	else
		return 0;
}

//销毁队列
void QueueDestroy(Queue* q)
{
    assert(q);
    while(q->front)
    {
        QNode* tem=q->front;
        q->front=q->front->next;
        free(tem);
    }
    q->rear=NULL;
    q->size=0;
    //assert(pq);

	// QNode* cur = pq->front;
	// while (cur)
	// {
	// 	QNode* next = cur->next;
	// 	free(cur);

	// 	cur = next;
	// }

	// pq->front = pq->rear = NULL;
	// pq->size = 0;
}

//上面都是队列的接口,因为此处是用C语言实现,所以得写一下
//接下来才是栈的实现

typedef struct {
    Queue q1;//定义两个队列
    Queue q2;
} MyStack;

// typedef struct Stack
// {
//     int* arr;
//     int top;
//     int capacity;
// }

//初始化
//在本题由于栈的4种操作的实现完全依赖于队列,对栈的初始化就是对两个队列的初始化
MyStack* myStackCreate() {
    MyStack* obj=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&obj->q1);//->优先级高于&
    QueueInit(&obj->q2);
    return obj;
}

//压栈,把元素往不为空的队列压
void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    QueuePush(&obj->q1,x);
    else//
    QueuePush(&obj->q2,x);
}

//出栈
//关键在于找到空队列,将不为空队列的前size-1个元素导到空队列,剩下的那1个元素则头删,从而实现栈的后进先出
int myStackPop(MyStack* obj) {
    //假定q1为空队列
    Queue* Empty=&obj->q1;
    Queue* No_Empty=&obj->q2;
    if(!QueueEmpty(Empty))//假定错误则调换
    {
        Empty=&obj->q2;
        No_Empty=&obj->q1;
    }
    //找到了空队列和不为空队列,接下来实现不为空队列的前size-1个元素导到空队列
    while(QueueSize(No_Empty)>1)
    {
        int tem=QueueFront(No_Empty);
        QueuePush(Empty,tem);//先将非空队列的元素压到空队列
        QueuePop(No_Empty);//再头头删掉它
    }
    //不为空队列的最后一个元素删除和返回
    int tem=QueueFront(No_Empty);
    QueuePop(No_Empty);
    return tem;
}

//取栈顶元素
int myStackTop(MyStack* obj) {
    // //假定q1为空队列
    // Queue* Empty=&obj->q1;
    // Queue* No_Empty=&obj->q2;
    // if(!QueueEmpty(Empty))//假定错误则调换
    // {
    //     Empty=&obj->q2;
    //     No_Empty=&obj->q1;
    // }
    // //找到了空队列和不为空队列,接下来实现不为空队列的前size-1个元素导到空队列
    // while(QueueSize(No_Empty)>1)
    // {
    //     int tem=QueueFront(No_Empty);
    //     QueuePush(Empty,tem);
    //     QueuePop(No_Empty);
    // }
    // //不为空队列的最后一个元素返回
    // int tem=QueueBack(No_Empty);
    // //QueuePop(No_Empty);
    // return tem;
    
    if(!QueueEmpty(&obj->q1))
    return QueueBack(&obj->q1);
    else
    return QueueBack(&obj->q2);
}

//判空
bool myStackEmpty(MyStack* obj) {
    if(QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2))
    return true;
    else
    return false;
    //return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

//销毁栈
//即销毁掉两个队列
void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    free(obj);
}

 

 二、用两个栈实现队列

题目:

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=O83Ahttps://leetcode.cn/problems/implement-queue-using-stacks/description/

 

 分析:

与上面的思路类似,不同之处在于栈的的特点是后进先出,要实现队列的先进先出的话,除了要将后进的size-1个元素导到另一个栈,将最先进的元素通过出栈pop删除,为了以后能够继续重复此操作,还要再将导过去的元素再导回来。

注意在本题实现队列的出队pop时,最关键的就在于还要导回去!!!!

代码:

typedef int STDataType;
typedef struct Stack
{
	STDataType* arr;
	int top;//栈中元素个数
	int capacity;//栈容量
}Stack;

//扩容函数
void Exp(Stack* p)
{
	if (p->capacity == p->top)
	{
		int new_capacity = p->capacity == 0 ? 4 : p->capacity * 2;//三目运算
		STDataType* tem = (STDataType*)realloc(p->arr, new_capacity * sizeof(STDataType));//注意是p->arr,不是p
		if (tem == NULL)//对返回值判断
		{
			perror("realloc");
			exit(1);
		}
		p->capacity = new_capacity;
		p->arr = tem;
	}
}

//初始化栈
void InitStack(Stack* p)
{
	assert(p);
	p->arr = NULL;
	p->top = 0;
	p->capacity = 0;
}

//入栈
void PushStack(Stack* p, STDataType x)
{
	assert(p);
	Exp(p);
	p->arr[p->top] = x;//元素入栈顶
	p->top++;
}

//出栈
void PopStack(Stack* p)
{
	assert(p);
	assert(p->top > 0);//有元素才能出
	p->top--;
}

//取栈顶元素
STDataType TopStack(Stack* p)
{
	assert(p);
	assert(p->top > 0);
	return p->arr[p->top - 1];//top相当于size,而数组下标是从0开始的
}

//获取栈中有效元素个数
int SizeStack(Stack* p)
{
	assert(p);
	return p->top;
}

//判空
bool EmptyStack(Stack* p)
{
	assert(p);
	//确实栈中元素为0则为true,否则为false
	return !p->top;
}

void DestroyStack(Stack* p)
{
	assert(p);
	if (p->arr)//arr非空指针时才存在释放问题
	{
		free(p->arr);//释放掉动态申请的内存
		p->arr = NULL;//置空
		p->capacity = p->top = 0;
	}
}

//以上都是栈的接口
//接下来才是用栈实现队列

typedef struct {
    Stack s1;//定义两个栈
    Stack s2;
} MyQueue;

//对队列的初始化即对两个栈的初始化
MyQueue* myQueueCreate() {
    MyQueue* tem=(MyQueue*)malloc(sizeof(MyQueue));
    InitStack(&tem->s1);
    InitStack(&tem->s2);
    return tem;//不要忘了返回!!!
}

//同样,始终保持一个栈为空,而另一个不为空,往不为空的栈压
void myQueuePush(MyQueue* obj, int x) {
    if(!EmptyStack(&obj->s1))
    PushStack(&obj->s1,x);
    else
    PushStack(&obj->s2,x);
}

//队列的头删
//
int myQueuePop(MyQueue* obj) {
    assert(obj);
    //assert((!EmptyStack(&obj->s1) || !EmptyStack(&obj->s2));//两个栈都为空就不存在后面问题
    //假设
    Stack* Empty=&obj->s1;
    Stack* No_Empty=&obj->s2;
    if(!EmptyStack(Empty))//假设错误调换
    {
        Empty=&obj->s2;
        No_Empty=&obj->s1;
    }
    //实现头删
    //1.找到先进的元素
    while(SizeStack(No_Empty)>1)
    {
        int tem=TopStack(No_Empty);//取栈顶元素
        PopStack(No_Empty);//删除栈顶元素
        PushStack(Empty,tem);//压入另一个空栈
    }
    //2。删除
    int tem=TopStack(No_Empty);
    PopStack(No_Empty);
    //3.将在另一个栈中的元素导回去,以便下次头删
    while(!EmptyStack(Empty))
    {
    int tem=TopStack(Empty);
    PopStack(Empty);
    PushStack(No_Empty,tem);
    }
    return tem;
}

//返回队列开头元素
int myQueuePeek(MyQueue* obj) {
    assert(obj);
    //assert((!EmptyStack(&obj->s1) || !EmptyStack(&obj->s2));//两个栈都为空就不存在后面问题
    if(!EmptyStack(&obj->s1))
    return (&obj->s1)->arr[0];
    else
    return (&obj->s2)->arr[0];
}

//判空
bool myQueueEmpty(MyQueue* obj) {
    return EmptyStack(&obj->s1) && EmptyStack(&obj->s2);
}

void myQueueFree(MyQueue* obj) {
    assert(obj);//销毁栈即销毁队列
    DestroyStack(&obj->s1);
    DestroyStack(&obj->s2);

}

 

总结:核心就在于先进先出和后进先出之间的一个灵活变换,两个栈能够先进先出,而两个队列能够后进先出 

 (栈和队列还不是很清楚的铁铁可以看我前面博客哦)


http://www.kler.cn/a/405013.html

相关文章:

  • 第6篇 寻找最大数___ARM C语言程序<二>
  • Kafka Offset 自动提交和手动提交 - 漏消费与重复消费
  • Compose Navigation快速入门
  • 电解车间铜业机器人剥片技术是现代铜冶炼过程中自动化和智能化的重要体现
  • IntelliJ+SpringBoot项目实战(十二)--设计项目多模块依赖关系和跨模块调用服务和接口
  • 极客时间《Redis核心技术与实战》开篇词 知识点总结
  • Vue 中的透传,插槽,依赖注入
  • Linux-服务器辨别实体机OR虚拟机
  • 使用ENSP实现DHCP+动态路由
  • 逆向攻防世界CTF系列40-ReverseMe-120
  • 【Mac】安装 Python3
  • SpringMVC案例学习(二)--表白墙/图书管理系统1.0版本
  • 基于web的教务系统的实现(springboot框架 mysql jpa freemarker)
  • 小程序-使用 iconfont 图标库报错:Failed to load font
  • React的hook✅
  • CSV文件数据导入hive
  • 开发中使用UML的流程_02 CIM-1:定义业务流程
  • Docker 安装单机版mysql 并持久化数据
  • 【GNU】addr2line
  • 大前端的发展过程
  • 图像处理 之 凸包和最小外围轮廓生成
  • 开发体育赛事直播平台防止数据泄露的技术安全方案
  • Redis性能优化的18招
  • 掌握Golang中的数据竞争检测:runtime/race包全面教程
  • 探索Linux内核中的Runqueue:从O(n)到O(1)的演进与负载均衡应用
  • 卷积神经网络(CNN)中的权重(weights)和偏置项(bias)