今天解决的问题其实没有什么实用意义,但是可以用来练习,让你更加熟悉对于栈和队列的应用。
其实栈和队列的区别就是先进先出和先进后出的区别嘛
我们目前要用两个队列去实现一个栈,也就是要把先进先出变成先进后出。那其实我们在进行pop操作的时候,因为队列要删除第一个元素,而我们想要的效果是只删除最后一个元素(虽然叫做“最后一个元素”,但是对于栈来说其实是栈顶)。那既然我们有俩队列,就完全可以把除了最后一个元素以外的元素用push操作插入到另外一个队列中,当然插入一个原队列就要pop掉一个元素。对于最后一个元素,我们不用插入,直接pop。这样就实现了类似于栈的删除操作。
下面是具体实现的代码(由于C语言没有内置的队列,只能自己把之前实现的队列复制上去)(C++不用自己写)
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct QueueNode//结点
{
struct QueueNode* next;
QDataType val;
}QNode;
typedef struct Queue//保存头尾指针的结构体
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = NULL;
while (pq->phead)
{
cur = pq->phead->next;
free(pq->phead);//我们释放的是这块空间,而不是phead这个名字,因此如果想让phead再继续指向其它空间直接赋给他就可以了
pq->phead = cur;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc failed");
return;
}
newnode->next = NULL;
newnode->val = x;
if (pq->ptail == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;//这一步一定别少了,不然直接把尾结点置为newnode链表就相当于断开了,前面的都找不到了,所以要先把newnode连上,再把尾指针给它
pq->ptail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)//删除队列首结点
{
assert(pq);
if (pq->phead == pq->ptail)
{
if (pq->phead == NULL)//没有结点队列为空
{
perror("Pop Failed");
}
else//只有一个结点
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
}
else//有多个结点
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
if (pq->size == 0)
{
return true;
}
else
{
return false;
}
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
typedef struct {
Queue q1;
Queue q2;
} MyStack;
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);
}
}
int myStackPop(MyStack* obj)//哪一个不为空,就把前size-1的元素移到另外一个队列中,把最后一个元素pop掉
{
Queue* Empty=&obj->q1;
Queue* NonEmpty=&obj->q2;
if(!QueueEmpty(&obj->q1))
{
Empty=&obj->q2;
NonEmpty=&obj->q1;
}
while(QueueSize(NonEmpty)>1)
{
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);
return;
}
/**
* 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.在实现 myStackTop这个函数时,因为我们首先要判断哪一个是空队列,哪一个是非空队列,所以我用了“假设法”,也就是这种方法、
当然也可以用if-else语句,但是觉得很麻烦,这样写一劳永逸。
2.我之前一直对结构体没有太多归属感,其实就是用的不熟练,今天的结构体其实是一层又一层这样去使用的,比如MyStack这个结构体里面,有两个队列,而这两个队列其实是用另外一个包含头尾指针的结构体来表示的,而且我们如果想要插入节点,节点也是结构体,我天,好多结构体。而且今天又清晰了一个以前有点模糊的点,就是结构体的名不能代表他的地址,要想找他的地址必须用&,当然如果本来这个量就是结构体指针的话那就直接用就ok。主要是不要跟数组搞混,因为数组的名字是代表这个数组的起始地址的,不用对数组名用&。
比如一个arr[10]
arr:表示数组首地址
arr[5]:表示这个值
&arr[5]:取这个元素的地址
&arr:不能这样写,这个不对。表示地址直接arr
3.复习malloc的使用
当你需要在程序运行期间创建大小不确定或者需要变化的数据结构,比如链表、树等,而这些数据的大小在编译时无法确定,这时就需要使用 malloc
来获取所需的内存空间。