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

}

 

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

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

评论 83
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

好想当只懒羊羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值