【C】浅刷栈和队列OJ题

  上一篇文章我们使用C语言实现了栈和队列,本章我们利用栈和队列的结构性质讲解几道相关的OJ题,更深刻地理解栈和队列。

目录

1、括号匹配问题

2、用队列实现栈

3、用栈实现队列

4、设计循环队列

1、括号匹配问题

力扣https://leetcode.cn/problems/valid-parentheses/【C】浅刷栈和队列OJ题_第1张图片

 【C】浅刷栈和队列OJ题_第2张图片

 对于这道题目的思考,我们试想历遍数组数左右括号个数,但是括号有顺序匹配的问题,左右指针双向历遍也不乏实例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;
}

2、用队列实现栈

力扣https://leetcode.cn/problems/implement-stack-using-queues/【C】浅刷栈和队列OJ题_第3张图片

 这道题目还是比较有意思的,用两个队列实现栈。

我们这道题从画图中寻找思路:

【C】浅刷栈和队列OJ题_第4张图片

【C】浅刷栈和队列OJ题_第5张图片

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);
}

3、用栈实现队列

力扣https://leetcode.cn/problems/implement-queue-using-stacks/【C】浅刷栈和队列OJ题_第6张图片

延续上面的思想,我们画图找灵感:

【C】浅刷栈和队列OJ题_第7张图片

 建立两个栈,一个是PushST,另一个是PopST,顾名思义,一个是压入数据,一个是出数据的。

1、push操作是元素x移到队尾,我们直接把它压入PushST中

2、pop是移除队头的数据,上图中我们先进的元素是1,所以要pop1,那么我们依次从PushST中拿出元素然后压入PopST中,那么得到如下图所示的PopST的栈,这时候我们把栈顶的元素移除掉即可 

【C】浅刷栈和队列OJ题_第8张图片

 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);
}

4、设计循环队列

力扣https://leetcode.cn/problems/design-circular-queue/【C】浅刷栈和队列OJ题_第9张图片

这道题的描述有点长,仔细读完之后发现我们需要设计一个循环队列来实现一系列操作,我们给出下面的一种图描述:
【C】浅刷栈和队列OJ题_第10张图片

 大概可以理解成这样,前面删除的空间,我们后续入队时还可以利用,但实际上存储空间可不是环形的,我们再画出数组的图描述:

【C】浅刷栈和队列OJ题_第11张图片

然后我们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);
}

你可能感兴趣的:(数据结构,leetcode,数据结构,算法)