上一篇文章我们使用C语言实现了栈和队列,本章我们利用栈和队列的结构性质讲解几道相关的OJ题,更深刻地理解栈和队列。
目录
1、括号匹配问题
2、用队列实现栈
3、用栈实现队列
4、设计循环队列
力扣https://leetcode.cn/problems/valid-parentheses/
对于这道题目的思考,我们试想历遍数组数左右括号个数,但是括号有顺序匹配的问题,左右指针双向历遍也不乏实例2这样带给我们的困惑。联想我们刚刚讲解过的栈,大家会不会有一点思路呢?
这里我们给出一种思路。创建一个栈后,我们历遍s,如果是左半部分括号(也就是‘(’,‘ [ ’,' { ')则压栈,如果遇到右半部分括号,那么和栈顶的匹配,如果匹配上,则栈顶弹出一个元素,如果不匹配,那么返回FALSE,以此类推,直到历遍完s还没返回false则返回TRUE。
由于是C语言的OJ题,我们没有栈的库函数,栈的基本操作我们需要复制到代码段中(上篇文章有实现栈),这里我们再给出一部分栈实现的函数:
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps)
{
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
void StackDestory(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 :ps->capacity*2;
STDataType* tmp = realloc(ps->a,sizeof(STDataType) * newcapacity);
if(tmp==NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
bool StackEmpty(ST* ps)
{
assert(ps);
/*if (ps->top == 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top-1];
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
有了这些函数,我们下面就可以调用了,然后实现这道题目:
bool isValid(char * s){
ST st;
StackInit(&st);//初始化
while(*s)
{
if(*s=='{'||*s=='('||*s=='[')
{
StackPush(&st,*s);
s++;
}//左括号压栈
else
{
if(StackEmpty(&st))
{
StackDestory(&st);
return false;
}
STDataType top=StackTop(&st);
StackPop(&st);
if((*s=='}'&&top!='{')||
(*s==')'&&top!='(')||
(*s==']'&&top!='['))
{
StackDestory(&st);
return false;
}
else
{
++s;
}//右括号匹配
}
}
bool ret=StackEmpty(&st);//判断匹配完栈中是否为NULL。如果不为NULL,那么返回假
StackDestory(&st);
return ret;
}
力扣https://leetcode.cn/problems/implement-stack-using-queues/
这道题目还是比较有意思的,用两个队列实现栈。
我们这道题从画图中寻找思路:
1、Push操作我们只需要把元素放入不为空的队列即可。
2、Top操作我们返回队尾的元素即可。
3、队列是先进先出的,上图所示,1先进队列那么1肯定先出队列,但是这时我们想到还有一个空队列,我们让上面的队列依次出队,然后入第二个队列直到上面的队列中只剩下最后一个元素,也就是队尾的元素,这时候让他出队我们就实现了模拟出栈的操作,这样就完成了pop的操作。
4、看两个队列是否都为空,我们就可以判断栈是否为空了,这样就实现了Empty操作。
我们先给出队列实现的一些分装函数:
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
//记录头指针和尾指针
typedef struct Queue
{
QNode* head;
QNode* tail;
}Queue;
//队列的初始化
void QueueInit(Queue* pq);
//队列的销毁
void QueueDestroy(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType x);
//出队
void QueuePop(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//队列中数据的个数
size_t QueueSize(Queue* pq);
//队头的数据
QDataType QueueFront(Queue* pq);
//队尾的数据
QDataType QueueBack(Queue* pq);
//队列的初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
//队列的销毁
void QueueDestroy(Queue* pq)
{
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
//入队
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
//printf("%s\n", strerror(errno));
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//尾插 - 用了尾指针就不用找尾了
if (pq->tail == NULL)
{
assert(pq->head == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
//出队
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head && pq->tail);
//头删 - 只有一种个结点的情况时tail会成野指针
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL && pq->tail == NULL;
}
//队列中数据的个数
size_t QueueSize(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
size_t size = 0;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
//队头的数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->head->data;
}
//队尾的数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->tail);
return pq->tail->data;
}
有了上述这些队列的函数,我们就可以按照上面画图得出的思想来实现栈了:
typedef struct
{
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate()
{
MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
if(pst == NULL)
exit(-1);
QueueInit(&pst->q1);
QueueInit(&pst->q2);
return pst;
}
void myStackPush(MyStack* obj, int x)
{
assert(obj);
if(!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1, x);
}
else
{
QueuePush(&obj->q2, x);
}
}
int myStackPop(MyStack* obj)
{
assert(obj);
Queue* empty = &obj->q1;
Queue* nonEmpty = &obj->q2;//先假设q1位空,然后判断,找出为空的队列
if(!QueueEmpty(empty))
{
empty = &obj->q2;
nonEmpty = &obj->q1;
}
while(QueueSize(nonEmpty) > 1)
{
QueuePush(empty,QueueFront(nonEmpty));
QueuePop(nonEmpty);
}
int top = QueueBack(nonEmpty);//把除最后一个元素的其他元素移到另一个队列,然后删除最后一个元素
QueuePop(nonEmpty);
return top;
}
int myStackTop(MyStack* obj)
{
assert(obj);
if(!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);//不为空的队列的队尾元素即为栈顶
}
}
bool myStackEmpty(MyStack* obj)
{
assert(obj);
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
void myStackFree(MyStack* obj)
{
assert(obj);
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
力扣https://leetcode.cn/problems/implement-queue-using-stacks/
延续上面的思想,我们画图找灵感:
建立两个栈,一个是PushST,另一个是PopST,顾名思义,一个是压入数据,一个是出数据的。
1、push操作是元素x移到队尾,我们直接把它压入PushST中。
2、pop是移除队头的数据,上图中我们先进的元素是1,所以要pop1,那么我们依次从PushST中拿出元素然后压入PopST中,那么得到如下图所示的PopST的栈,这时候我们把栈顶的元素移除掉即可
3、peek是返回队头的元素,也就是返回1,那么和操作2一样,我们只要取出PopST栈顶元素即可。
4、判断队列是否为空,我们只需要判断PushST和PopST是否同时为空即可。
栈的操作实现函数:
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestory(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
void StackInit(ST* ps)
{
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
void StackDestory(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 :ps->capacity*2;
STDataType* tmp = realloc(ps->a,sizeof(STDataType) * newcapacity);
if(tmp==NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top-1];
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(ST* ps)
{
assert(ps);
/*if (ps->top == 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;
}
按照上面操作,用栈实现队列的操作:
typedef struct {
ST pushST;
ST popST;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* q=(MyQueue*)malloc(sizeof(MyQueue));
StackInit(&q->pushST);
StackInit(&q->popST);
return q;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushST,x);//直接让元素压栈到PushST中
}
int myQueuePop(MyQueue* obj) {
if(StackEmpty(&obj->popST))
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
int front=StackTop(&obj->popST);
StackPop(&obj->popST);
return front;
}
int myQueuePeek(MyQueue* obj) {
if(StackEmpty(&obj->popST))
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
return StackTop(&obj->popST);
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->pushST)&&StackEmpty(&obj->popST);
}
void myQueueFree(MyQueue* obj) {
StackDestory(&obj->pushST);
StackDestory(&obj->popST);
free(obj);
}
力扣https://leetcode.cn/problems/design-circular-queue/
这道题的描述有点长,仔细读完之后发现我们需要设计一个循环队列来实现一系列操作,我们给出下面的一种图描述:
大概可以理解成这样,前面删除的空间,我们后续入队时还可以利用,但实际上存储空间可不是环形的,我们再画出数组的图描述:
然后我们1,2,3,4入队,然后1,2出队,然后让5,6入队,我们可以画出下面的图:
有人就发现了,这里我们多给了一块空间,也就是上面Tail所在的位置,这是为什么呢?
假如我们不多开辟这一块空间,我们判空和判满的条件都是Tail和Front相等的,无法区别。额外开辟一块空间则可以让判空的条件是Front和Tail相等,判满条件则是Tail加1等于Front。
这道题目用顺序表实现很简单,最主要的就是循环我们要用到取模的操作,即%。
详细操作我们在代码中讲解。
typedef struct {
int* a;
int front;
int tail;
int k;//元素个数
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);//判空和判满函数,取函数名放在头,以便调用,实现操作在下面
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue*cp=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));//开辟结构体空间
cp->a=(int*)malloc(sizeof(int)*(k+1));//开辟数组空间,多开辟一块额外的
cp->front=cp->tail=0;
cp->k=k;
return cp;
}
//插入一个元素
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
return false;
else
{
obj->a[obj->tail]=value;//tail处插入,即为队尾插入
++obj->tail;
obj->tail%=(obj->k+1);//实现循环操作
return true;
}
}
//出队即为头删
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return false;
else
{
++obj->front;
obj->front%=(obj->k+1);//实现循环操作
return true;
}
}
//返回头元素
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
else
return obj->a[obj->front];
}
//返回尾元素
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
else
{if(obj->tail==0)
return obj->a[obj->k];//特殊情况单独说明
else
return obj->a[obj->tail-1];
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
if(obj->front==obj->tail)
return true;
else
return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
if((obj->tail+1)%(obj->k+1)==obj->front)
return true;
else
return false;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}