草庐IT

【数据结构】用队列实现栈

小陶来咯 2023-08-22 原文

💯💯💯

本篇总结利用队列如何实现栈的相关操作,不难观察,栈和队列是可以相互转化的,需要好好总结它们的特性,构造出一个恰当的结构来实现即可,所以本篇难点不在代码思维,而是对结构的理解。


⏰1.用队列实现栈

做该种题要记住两个关键点:
一个队列用来插入数据。一个队列用来存数据

1.空队列用来导数据
2.非空队列用来插入数据

我们知道队列的特点:先进先出,只能在一端插入,一端删除。
而栈的特点是:先进后出,后进先出。

比如要让两个队列实现栈的功能,就拿出栈来说,如果要应该将栈顶元素5拿走。
但在队列1中,因为只能在队头进行删除,所以只能拿走元素1.
那该怎么办呢?

要想pop掉队列中的元素5,我们得想办法将它搞成队头元素才行,这样才可以删除掉它。而如果队列2是空队列的话,那么我们这样做:将元素5之前的4个元素都放到队列2中,队列1中就只剩下元素5了,那元素5就相当于队头元素,就可以进行pop操作最终将其删除掉了,删除后队列1又变成空队列了。
而队列2就是非空队列。而重复这样的操作就可以实现删除栈顶元素的操作了:首先将非空队列中前n-1个元素导入空队列中,然后再pop掉非空队列中的元素。

我们如果想要进行压栈操作,将元素6,7压入栈顶,那该如何插入呢?

其实只要在非空队列中尾插元素即可,也就是正常的入队即可
那我们来按照空与非空来区别两个队列,因为两个不同队列有着不同的功能:
非空队列:用来插入数据
空队列:用来导数据,存数据

首先我们需要将队列的数据结构写出来,然后定义两个队列。

//利用单链表来实现队列
typedef int QData;
typedef struct QNode
{
	struct QNode* next;
	int data;
}QNode;
//因为队列的数据结构操作需要找尾,这就需要传多个参数了,很麻烦,所以我们再分装个结构体将多个数据变成一个
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;
void QueueInit(Queue *pq);//初始化队列
void QueueDestroy(Queue *pq);//销毁队列
void QueuePush(Queue*pq ,QData x);//入队,从队尾插入一个数据,尾删
void QueuePop(Queue *pq);//出队,从队头删除数据,头删
bool QueueEmpty(Queue *pq);//判断队列是否为空
int QueueSize(Queue*pq);//获得队列有效数据个数大小
QData QueueFront(Queue*pq);//获取队头数据
QData QueueBack(Queue*pq);//获取队尾数据

void QueueInit(Queue* pq)//初始化队列
{
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)//销毁队列
{
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QData x)//入队,从队尾插入一个数据,尾删
{
	assert(pq);
/*	QNode* cur = pq->head;
	*/QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc");
	}
	newnode->data=x;
	newnode->next = NULL;
	if (pq->head == NULL)
	{
		//赋值
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		//更新tail的位置
		pq->tail = newnode;
	}
	pq->size++;

}
void QueuePop(Queue* pq)//出队,从队头删除数据,头删
{
	assert(pq);
	//头删之前需要判断链队列是否为空
	assert(pq->head!=NULL);
	//改变头指针的指向
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
     //当headtail都指向一起是,把head指向的空间释放,那tail就变成野指针了
	//所以还需要考虑最后一个结点,需要将tail和head一起置空
	if (pq->head==NULL)//只管头删,最后再处理。
	{
		
		pq->tail=NULL;
	}
	pq->size--;
}
//链表哨兵卫里面可以存放大小吗?
//不能--可能不是int类型--如果我们像求一个链表的大小通常需要遍历链表

bool QueueEmpty(Queue* pq)//判断队列是否为空----主要size的作用
{
	assert(pq);
	return pq->size == 0;
	//return pq->head=pq->tailk=NULL;
}
int QueueSize(Queue* pq)//获得队列有效数据个数大小
{
	assert(pq);
	return pq->size;
}

QData QueueFront(Queue* pq)//获取队头数据
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

QData QueueBack(Queue* pq)//获取队尾数据
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

定义两个队列q1,q2


typedef struct //该重命名方式为匿名结构体,没有名字但有效
{
	Queue q1;
	Queue q2;
} MyStack;

然后要获得指向MyStack的指针


MyStack* myStackCreate() //注意这里最后要返回指向栈的指针,并且空间是可使用的,所以必须动态开辟空间,如果只是由栈上开辟空间,那么函数结束,空间就被销毁,而在堆上申请的空间不会被销毁,所以需要用malloc给这个结构体开辟空间
{
  
}

🕑"出栈"

Pop就需要导数据了,将非空的数据导入空队列中,这样才可以删除最后一个元素。
但一开始并不知道谁是空的谁是非空,所以我们需要先讨论下
所以步骤就是:
1.先判断哪个队列是非空,哪个队列是空。
2.将非空队列前n-1个元素导入空队列
3.删除非空队列元素


int myStackPop(MyStack* obj) 
{
	Queue* empty = &obj->q1;//假设一开始q1是空队列
	Queue* nonempty = &obj->q2;//假设q2是非空队列
	if (!QueueEmpty(empty))//如果假设错误那q1就是非空队列,q2是空队列
	{
		empty = &obj->q2;
		nonempty = &obj->q1;
	}
	//这样我们就不用管谁是空谁是非空了,empty就是空,nonempty就是非空
	//将非空队列前n-1个元素导入空队列中华
	//【导数据】
	while (QueueSize(nonempty) - 1 > 0)
	{
		QueuePush(empty, QueueFront(nonempty));
		       //插入空队列   //获取队头元素
		QueuePop(nonempty);//将数据pop掉
	}
	//走到这时说明非空队列就剩最后一个元素了,即栈顶元素
	//要求返回栈顶元素,所以我们需要保存下来
	int top = QueueFront(nonempty);
	QueuePop(nonempty);//删除最后一个元素
	return top;
}

🕓"压栈"

往非空队列里插入,哪个队列不为空就往哪里插,两个都为空,随便插入一个即可

void myStackPush(MyStack* obj, int x) 
{
	assert(obj);
	if (!QueueEmpty(&obj->q1))//如果q1不为空
	{
		QueuePush(&obj->q1, x);//就将数据插入q1中
	}
	else//如果q1为空,则q2不为空,就往q2中插入数据
	{
		QueuePush(&obj->q2, x);//或者都为空,随便进入一个
	}
}

🕕"获取栈顶数据"

取栈顶数据,其实就是非空队尾数据
但仍然不知道谁是非空谁是空,所以需要讨论下

int myStackTop(MyStack* obj) {
	assert(obj);
	
	if (!QueueEmpty(&obj->q1))//如果q1不为空
	{
		return QueueBack(&obj->q1);//队尾数据即栈顶数据
	}
	else//如果q1为空,则q2不为空,
	{
		return QueueBack(&obj->q2);//队尾数据即栈顶数据
	}
	
}

🕖"判断是否为空"

怎么算空呢?当然当两个队列都为空时才是真正的空。


bool myStackEmpty(MyStack* obj) 
{
	assert(obj);
	return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

🕗"销毁栈"

如何销毁用队列实现的栈呢?
这要求我们需要搞清楚这个栈的结构到底是什么样子:

栈是由两个队列构成,而队列又是由结点和一个size构成。结点又是由一个data数据和一个指针构成。

我们应该先从里面开始释放,不然先释放外面的里面的空间就找不到了,所以从里到外释放。先释放两个队列,然后再释放栈的空间。


void myStackFree(MyStack* obj) 
{
	assert(obj);
	QueueDestroy(&obj->q1);
	QueueDestroy(&obj->q2);
	free(obj);
	obj = NULL;
}

⏰2.整体代码



typedef int QData;
typedef struct QNode
{
	struct QNode* next;
	int data;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;
void QueueInit(Queue *pq);//初始化队列
void QueueDestroy(Queue *pq);//销毁队列
void QueuePush(Queue*pq ,QData x);//入队,从队尾插入一个数据,尾删
void QueuePop(Queue *pq);//出队,从队头删除数据,头删
bool QueueEmpty(Queue *pq);//判断队列是否为空
int QueueSize(Queue*pq);//获得队列有效数据个数大小
QData QueueFront(Queue*pq);//获取队头数据
QData QueueBack(Queue*pq);//获取队尾数据

void QueueInit(Queue* pq)//初始化队列
{
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)//销毁队列
{
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QData x)//入队,从队尾插入一个数据,尾删
{
	assert(pq);
/*	QNode* cur = pq->head;
	*/QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc");
	}
	newnode->data=x;
	newnode->next = NULL;
	if (pq->head == NULL)
	{
		//赋值
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		//更新tail的位置
		pq->tail = newnode;
	}
	pq->size++;

}
void QueuePop(Queue* pq)//出队,从队头删除数据,头删
{
	assert(pq);
	//头删之前需要判断链队列是否为空
	assert(pq->head!=NULL);
	//改变头指针的指向
	
	
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
     //当headtail都指向一起是,把head指向的空间释放,那tail就变成野指针了
	//所以还需要考虑最后一个结点,需要将tail和head一起置空
	if (pq->head==NULL)//只管头删,最后再处理。
	{
		pq->tail=NULL;
	}
	pq->size--;

}

bool QueueEmpty(Queue* pq)//判断队列是否为空----主要size的作用
{
	assert(pq);
	return pq->size == 0;
	//return pq->head=pq->tailk=NULL;
}
int QueueSize(Queue* pq)//获得队列有效数据个数大小
{
	assert(pq);
	return pq->size;
}

QData QueueFront(Queue* pq)//获取队头数据
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

QData QueueBack(Queue* pq)//获取队尾数据
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

//柔性数组与顺序表的区别
typedef struct {
    Queue q1;
    Queue q2;
} MyStack;//用两个队列实现的栈
//匿名定义结构体

MyStack* myStackCreate()//发现没有传入参数并且返回值是指向栈的指针说明里面是用malloc开辟的内存而不是在栈上开辟的
 {
     MyStack*st=(MyStack*)malloc(sizeof(MyStack));//
     if(st==NULL)
     {
         perror("malloc");
     }
    //需要对两个队列进行初始化
    QueueInit(&st->q1);
    QueueInit(&st->q2);
     return st;
}

void myStackPush(MyStack* obj, int x)//压栈怎么压呢?直接在不为空的队列后面尾插即可
{
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}

int myStackPop(MyStack* obj)//出栈怎么出呢? 需要将不为空的队列前面的数据导入为空的队列中,再将最后一个数据删除掉,在删除之前保存删除的数据。//但不知道谁是空谁不是空所以需要判断一下
{
   Queue *empty=&obj->q1;//假设q1是空队列
   Queue *nonempty=&obj->q2;//假设q2是非空队列
   if(!QueueEmpty(&obj->q1))
   {
       empty=&obj->q2;
       nonempty=&obj->q1;
   }
   //现在不需要知道谁是空谁是非空了
   //将非空队列数据导入空队列中
   while(QueueSize(nonempty)-1>0)
   {
       QueuePush(empty,QueueFront(nonempty));
       QueuePop(nonempty);
   }
   //删除最后一个数据之前需要保存
   int top=QueueFront(nonempty);
   QueuePop(nonempty);
   return top;
}

int myStackTop(MyStack* obj)//返回栈顶元素--对应队列就是让非空队列中最后一个元素
{
      //首先需要判断谁是非空
    //判断完后让返回非空最后一个元素
    if(!QueueEmpty(&obj->q1))
    {
       return QueueBack(&obj->q1);
    }
    else
    {
       return QueueBack(&obj->q2);
    }
}

bool myStackEmpty(MyStack* obj) //判断是否为空,只要两个队列都为空就为空
{
  return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) 
{
   //从里到外释放,不能从外到里释放
   QueueDestroy(&obj->q1);
   QueueDestroy(&obj->q2);
   free(obj);
}

/**
 * Your MyStack struct will be instantiated and called as such:
 * MyStack* obj = myStackCreate();
 * myStackPush(obj, x);
 
 * int param_2 = myStackPop(obj);
 
 * int param_3 = myStackTop(obj);
 
 * bool param_4 = myStackEmpty(obj);
 
 * myStackFree(obj);
*/

有关【数据结构】用队列实现栈的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  4. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  5. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  6. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  7. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  10. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

随机推荐