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