😻前言
😝上一章我们用队列实现了一个栈(-> 传送门 <-),而这一章就带大家用栈实现一个队列。
😜用队列实现一个栈,用的是两个队列,其出栈操作可以说是最麻烦的一步,它通过倒数据的方式最后完成出栈。而用栈实现一个队列,很明显也是需要两个栈来完成的,其出队操作其实也与倒数据的方式有关,不过两个实现方法有所不同。
🤪用队列实现栈,是通过队列的 先进先出 的性质来实现栈的 后进先出 的性质;而用栈实现队列,是通过栈的 后进先出 的性质来实现队列的 先进先出 的性质,大家别弄混淆了。
如何用栈实现队列?
那么如何用栈实现队列呢?肯定是需要两个栈来完成的。
用两个栈,每一个栈都是数据后进先出,我们仔细思考队列的先进先出这一性质,如何来操作这两个栈才能达到这样的一个性质?
我们可以这样操作:规定一个栈为(入数据的栈),一个栈为(出数据的栈)。一开始两个栈都为空,当我们要入队列的时候,只需要在那个入数据的栈中将数据入栈即可。当我们要出队列的时候,由于队列的性质是先进先出,这时单凭那个入数据的栈实现不了此性质的功能,因此那个出数据的栈(当前为空栈)就起作用了,具体操作为:将那个入数据的栈里面的数据依次出栈,并入入出数据的栈中,这样,最先进入入数据的栈的数据就到了出数据的栈的栈顶位置,随后在此栈出栈即可。整个过程的确实现了队列先进先出这一性质的功能,这也是最好的解决方案了 。当然,后面如果一直出队列直到出数据的栈里没有数据可出了,而此时还想执行出队列操作,那么再向入数据的栈中倒数据过来就可以了(再次执行上面的操作)。注意:无论如何,入队列的数据始终都是 入入 入数据的栈中。
此外,用栈实现一个队列,还需要有判空,取队头元素,队列的销毁这些功能,不过这些都是小问题,我们可以巧用轮子 (轮子就是我们提前已经实现好的栈的一系列功能) 来灵活解决这些问题。
用栈实现队列
- 这里我们直接以题目的方式来实现,题目链接:->传送门<-。
题目描述:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty)
该题提供的需要我们实现的接口:
typedef struct { } MyQueue; MyQueue* myQueueCreate() { } void myQueuePush(MyQueue* obj, int x) { } int myQueuePop(MyQueue* obj) { } int myQueuePeek(MyQueue* obj) { } bool myQueueEmpty(MyQueue* obj) { } void myQueueFree(MyQueue* obj) { }
- 由于这里我们用
C语言
实现,因此需要 “造轮子”,也就是将之前实现过的栈拷贝过去。
接下来,就是对队列的一系列功能接口的实现了:
1.
- 首先当然是造轮子,有了轮子,我们对栈的一系列操作,只需要调用我们已经实现好的函数接口即可。
- 我们将之前写的栈直接拷贝过来,拷贝的代码如下:
typedef int STDataType; typedef struct stack { STDataType* a; int capacity; int top; }stack; // 初始化 void STInit(stack* pt); // 入栈 void STPush(stack* pt, STDataType x); // 出栈 void STPop(stack* pt); // 判空 bool STEmpty(stack* pt); // 取栈顶元素 STDataType STTop(stack* pt); // 栈的元素个数 int STSize(stack* pt); // 销毁栈 void STDestroy(stack* pt); void STInit(stack* pt) { assert(pt); pt->a = NULL; pt->capacity = 0; pt->top = 0; } void STPush(stack* pt, STDataType x) { assert(pt); if (pt->top == pt->capacity) { int newcapacity = pt->capacity == 0 ? 4 : pt->capacity * 2; STDataType* tmp = realloc(pt->a, sizeof(STDataType) * newcapacity); if (tmp == NULL) { perror("realloc fail"); exit(-1); } pt->a = tmp; pt->capacity = newcapacity; } pt->a[pt->top++] = x; } void STPop(stack* pt) { assert(pt && !STEmpty(pt)); pt->top--; } bool STEmpty(stack* pt) { assert(pt); return pt->top == 0; } STDataType STTop(stack* pt) { assert(pt && !STEmpty(pt)); return pt->a[pt->top - 1]; } int STSize(stack* pt) { assert(pt); return pt->top; } void STDestroy(stack* pt) { assert(pt); free(pt->a); pt->capacity = 0; pt->top = 0; }
- 有了轮子之后,就是对队列的结构体的创建了,由于队列是由两个栈实现的(一个为入数据的栈,一个为出数据的栈),因此队列的结构体的成员也是两个栈:
相关代码实现:
// 匿名结构体 typedef struct { // 入数据的栈 stack pushst; // 出数据的栈 stack popst; } MyQueue;
- 然后是创建一个队列,就是开辟一个队列的空间,其间包含对队列里的两个栈的初始化操作。
相关代码实现:
MyQueue* myQueueCreate() { MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue)); assert(obj); // 调用栈自有的初始化函数 STInit(&obj->pushst); STInit(&obj->popst); return obj; }
- 接着就是对入队列操作的实现。
- 前面说过,无论如何,只需要在入数据的栈中入栈即可。
相关代码实现:
void myQueuePush(MyQueue* obj, int x) { // 调用自己的入栈函数 STPush(&obj->pushst, x); }
再接着就是最复杂的出队列操作。
由前面的介绍,我们已经知道了思路,而现在最主要的,就是我们还需要对出数据的栈进行判空,如果出数据的栈为空,就需要在入数据的栈里倒数据过来,然后再出栈。如果不为空,直接出栈即可。
相关代码实现:
注意: 由于该函数功能与下面的返回队列开头的元素的函数功能类似,只是一个要将数据干掉,一个不干,因此,这里直接复用返回队列开头的元素的函数功能,最后只要将数据删除即可。
int myQueuePop(MyQueue* obj) { // assert(!myQueueEmpty(obj)); // 首先判断是否是空,是否需要倒数据 // if (STEmpty(&obj->popst)) // { // // 为空就倒数据 // while (!STEmpty(&obj->pushst)) // { // int top = STTop(&obj->pushst); // STPop(&obj->pushst); // STPush(&obj->popst); // } // } // int top = STTop(&obj->popst); // STPop(&obj->popst); // return top; int top = myQueuePeek(obj); // 多了一步删除操作 STPop(&obj->popst); return top; }
- 当然还有获取队头数据的功能,也就是返回队列开头的元素。
- 在上面出队列部分已经说明思路,只是这里不需要删除那个队头数据。
相关代码实现:
int myQueuePeek(MyQueue* obj) { assert(!myQueueEmpty(obj)); if (STEmpty(&obj->popst)) { while (!STEmpty(&obj->pushst)) { int top = STTop(&obj->pushst); STPop(&obj->pushst); STPush(&obj->popst, top); } } return STTop(&obj->popst); }
- 论队列的功能怎么能少得了判空呢。
- 对于该队列的判空,我们实际上只需要判断那两个栈是否为空即可。
相关代码实现:
bool myQueueEmpty(MyQueue* obj) { return STEmpty(&obj->pushst) && STEmpty(&obj->popst); }
- 最后就是队列的销毁了。
- 将两个栈销毁(调用自己的销毁函数),然后将队列销毁即可。
相关代码实现:
void myQueueFree(MyQueue* obj) { // 调用自己的销毁函数 STDestroy(&obj->pushst); STDestroy(&obj->popst); // 销毁队列 free(obj); }
整体的实现代码
typedef int STDataType; typedef struct stack { STDataType* a; int capacity; int top; }stack; // 初始化 void STInit(stack* pt); // 入栈 void STPush(stack* pt, STDataType x); // 出栈 void STPop(stack* pt); // 判空 bool STEmpty(stack* pt); // 取栈顶元素 STDataType STTop(stack* pt); // 栈的元素个数 int STSize(stack* pt); // 销毁栈 void STDestroy(stack* pt); void STInit(stack* pt) { assert(pt); pt->a = NULL; pt->capacity = 0; pt->top = 0; } void STPush(stack* pt, STDataType x) { assert(pt); if (pt->top == pt->capacity) { int newcapacity = pt->capacity == 0 ? 4 : pt->capacity * 2; STDataType* tmp = realloc(pt->a, sizeof(STDataType) * newcapacity); if (tmp == NULL) { perror("realloc fail"); exit(-1); } pt->a = tmp; pt->capacity = newcapacity; } pt->a[pt->top++] = x; } void STPop(stack* pt) { assert(pt && !STEmpty(pt)); pt->top--; } bool STEmpty(stack* pt) { assert(pt); return pt->top == 0; } STDataType STTop(stack* pt) { assert(pt && !STEmpty(pt)); return pt->a[pt->top - 1]; } int STSize(stack* pt) { assert(pt); return pt->top; } void STDestroy(stack* pt) { assert(pt); free(pt->a); pt->capacity = 0; pt->top = 0; } typedef struct { stack pushst; stack popst; } MyQueue; int myQueuePeek(MyQueue* obj); bool myQueueEmpty(MyQueue* obj); MyQueue* myQueueCreate() { MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue)); assert(obj); STInit(&obj->pushst); STInit(&obj->popst); return obj; } v oid myQueuePush(MyQueue* obj, int x) { STPush(&obj->pushst, x); } int myQueuePop(MyQueue* obj) { // assert(!myQueueEmpty(obj)); // if (STEmpty(&obj->popst)) // { // while (!STEmpty(&obj->pushst)) // { // int top = STTop(&obj->pushst); // STPop(&obj->pushst); // STPush(&obj->popst); // } // } // int top = STTop(&obj->popst); // STPop(&obj->popst); // return top; int top = myQueuePeek(obj); STPop(&obj->popst); return top; } int myQueuePeek(MyQueue* obj) { assert(!myQueueEmpty(obj)); if (STEmpty(&obj->popst)) { while (!STEmpty(&obj->pushst)) { int top = STTop(&obj->pushst); STPop(&obj->pushst); STPush(&obj->popst, top); } } return STTop(&obj->popst); } bool myQueueEmpty(MyQueue* obj) { return STEmpty(&obj->pushst) && STEmpty(&obj->popst); } void myQueueFree(MyQueue* obj) { STDestroy(&obj->pushst); STDestroy(&obj->popst); free(obj); }
😼写在最后
💝学会了用队列实现栈,也学会了用栈实现队列,回想一下,还是挺简单的,下一篇将带大家设计一个循环队列,难度会有些上升噢,大家打起精神来!!!
❤️🔥后续将会持续输出有关数据结构与算法的文章,你们的支持就是我写作的最大动力!
感谢阅读本小白的博客,错误的地方请严厉指出噢~