【数据结构】栈与队列OJ - 精选精讲

文章目录

  • 前言
  • 一、注意事项
  • 二、OJ精析
    •   1. 括号匹配问题
    •   2. 用队列实现栈
    •   3. 用栈实现队列
    •   4. 设计循环队列
  • 总结


前言

本文选取了一些经典的来自力扣的有关栈与队列的OJ题,致力于帮助读者巩固相关知识概念,并提供优质,值得借鉴的思路供读者积累与掌握。

一、注意事项

  1. 使用C语言实现,大部分题目使用了相应的事先准备好的接口函数,读者可以直接复制粘贴自己敲过的接口函数在OJ题提供的代码上方即可。
  2. 确保接口函数100%正确,如果确定代码逻辑正确,仍报错,极有可能是接口函数写错了,这是一个极好的检验知识是否掌握的机会。

二、OJ精析

  1. 括号匹配问题

【数据结构】栈与队列OJ - 精选精讲_第1张图片

  • 思路讲解
  1. 读题:有效括号包括两部分,顺序相同和数量相同。
  2. 实现
  1. 顺序相同:
    利用栈后进先出的特点,读取字符串s,遇到左括号就进栈,遇到右括号就出栈查看是否为匹配的左右括号
  2. 数量相同:
    遇到右括号时,查看栈是否为空,为空说明右括号多余;
    字符串读取完时,查看栈是否为空,不为空说明左括号有剩余
  • 代码实现
typedef char StackDataType;

typedef struct Stack
{
	StackDataType* a;
	int top;  //栈顶下标
	int capacity; //空间容量
}Stack;

//创建并初始化
void StackInit(Stack* ps)
{
	assert(ps);

	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

//销毁
void StackDestory(Stack* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;

}

//入栈
void StackPush(Stack* ps, StackDataType x)
{
	assert(ps);

	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//注意是数组,指针类型为存放数据的类型
		StackDataType* tmp = (StackDataType*)realloc(ps->a,sizeof(StackDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;  //在栈顶后插入
	ps->top++;
}

//出栈
void StackPop(Stack* ps)
{
	assert(ps);
	//不为空
	assert(ps->top > 0);
	ps->top--;  //并没有真的“扔出”,只是不再访问这个位置的数据
}

//获取栈顶元素值
StackDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);  //栈顶大于0才拥有有效元素
	return ps->a[ps->top - 1];
}

//获取栈中有效元素个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}

//检测栈是否为空
bool StackEmpty(Stack* ps)  //布尔类型,需包含头文件
{
	assert(ps);
	return ps->top == 0;  //利用表达式返回值,如果表达式为真,则返回ture,否则返回false。
}
bool isValid(char* s) {
	//创建栈并初始化
    Stack a;
    StackInit(&a);
    
    /*while判断顺序是否匹配*/
	while (*s)
	{
		//当访问的s为左括号时进栈
		if (*s == '(' || *s == '{' || *s == '[')
			StackPush(&a, *s);

		//否则保存栈顶值,判断是否为匹配左括号的三个右括号,不为时返回false
		else
		{
			/*如果为空,说明右括号多于,数量不匹配*/
			if (StackEmpty(&a))
			{
                StackDestory(&a);
				return false;
			}
			//栈里面取左括号,此时*s里面应为右括号
			char top = StackTop(&a);
			StackPop(&a);
			//连续&& 和 || 可以一行写一个判断,且将操作符放行头,可以直接对齐
			if ((*s == ')' && top != '(')
			|| (*s == '}' && top != '{')
			|| (*s == ']' && top != '['))
                {
                    StackDestory(&a);
				    return false;
                }
		}
		++s;
	}
/*
	if (StackEmpty(&a))
		 {
			StackDestory(&a);
			 return true;
		 }
	 return false; 
*/
	 //利用bool类型精简代码
	 /*如果不为空,说明左括号多于,数量不匹配*/
	bool ret = StackEmpty(&a);
	StackDestory(&a);
	return ret;
    
}

  2. 用队列实现栈

【数据结构】栈与队列OJ - 精选精讲_第2张图片

  • 思路讲解

重点:满足栈后进先出的性质即可
整体:空队列倒数据,非空队列存数据,初始均为空时随便。
(删除时将不需要删除的数据由非空队列转存进空队列,再删除剩下的即可)
注意点
1.每次删除(模拟出栈)非空队列和空队列会互换,不是固定的,用假设法判断空与非空非常方便。
2.注意销毁模拟栈时,记得先销毁队列。

  • 代码实现
typedef int QueueDataType;

typedef struct QueueNode
{
	QueueDataType val;
	struct QueueNode* next;

}QNode;

typedef struct
{
	QNode* phead;  //头指针
	QNode* ptail;  //尾指针
	int size;	   //队内有效元素个数

}Queue;


//创建并初始化(使用的是无头单链表,这里不需要开辟节点,初始化新定义的结构体即可)
void QueueInit(Queue* pq)  
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

//销毁
void QueueDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	
	while (cur)
	{
		QNode* tmp = cur;
		cur = cur->next;
		free(tmp);
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

//队尾入队(插入)
void QueuePush(Queue* pq,QueueDataType x)
{
	assert(pq);
	//只有插入需要开辟空间,不需要单独写一个接口
	//1.开辟新节点,检验成功后初始化
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;

	//2.分情况插入 - 为空;不为空
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++; //更新有效元素个数
}

//队头出队(删除)
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->phead);  //为空时不能再出栈了

	QNode* tmp = pq->phead;
	pq->phead = pq->phead->next;
	free(tmp);
	//仅有一个节点时,需要置空尾指针
	if (pq->phead == NULL)
		pq->ptail = NULL;

	pq->size--; //更新有效元素个数

}

//获取队头元素值
QueueDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead); //为空时没有元素,不能访问队头

	return pq->phead->val;
}

//获取队尾元素值
QueueDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail); //为空时没有元素,不能访问队尾
	
	return pq->ptail->val;
}

//获取队列中有效元素个数
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

//检查队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->phead == NULL;  //利用表达式返回值,表达式为真则返回1,为假则返回0
}
//                      以上为队列接口
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->q2))
        QueuePush(&obj->q2,x);
    else
    {
        QueuePush(&obj->q1,x);
    }
}

int myStackPop(MyStack* obj) {
    //假设法精简代码,不用动脑子自己判断哪个是存队列,哪个是转队列
    Queue* empty = &obj->q2;
    Queue* unempty = &obj->q1;
    if(!QueueEmpty(&obj->q2))
    {
        empty = &obj->q1;
        unempty = &obj->q2;
    }
    //将不需要删除的元素由非空转入空
    while(unempty->phead != unempty->ptail)
    //while(QueueSize(unempty)>1)
    {
        QueuePush(empty,QueueFront(unempty));
        QueuePop(unempty);
    }
    int top = QueueFront(unempty);
    QueuePop(unempty);
  
    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) {
    //注意队列的销毁,free只是把模拟栈内的存放链表指针的结构体释放了,但指针指向的链表是不会被释放的
    QueueDestory(&obj->q2);
    QueueDestory(&obj->q1);
    free(obj);
    obj == NULL;
}


  3. 用栈实现队列

【数据结构】栈与队列OJ - 精选精讲_第3张图片

  • 思路讲解

整体:利用栈接口,整体思路同队列模拟栈,实现队列先进先出的性质即可。
思路:一个栈专门存数据,一个栈专门删数据,获取数据。两个栈功能恒定不变,实现比模拟队列容易理解。

  • 代码实现
typedef int StackDataType;

typedef struct Stack
{
	StackDataType* a;
	int top;  //栈顶下标
	int capacity; //空间容量
}Stack;

void StackInit(Stack* ps)
{
	assert(ps);

	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

//销毁
void StackDestory(Stack* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;

}

//入栈
void StackPush(Stack* ps, StackDataType x)
{
	assert(ps);

	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//注意是数组,指针类型为存放数据的类型
		StackDataType* tmp = (StackDataType*)realloc(ps->a, sizeof(StackDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;  //在栈顶后插入
	ps->top++;
}

//出栈
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;  //并没有真的“扔出”,只是不再访问这个位置的数据
}

//获取栈顶元素值
StackDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);  //栈顶大于0才拥有有效元素
	return ps->a[ps->top - 1];
}

//获取栈中有效元素个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}

//检测栈是否为空
bool StackEmpty(Stack* ps)  //布尔类型,需包含头文件
{
	assert(ps);
	return ps->top == 0;  //利用表达式返回值,如果表达式为真,则返回ture,否则返回false。
}
            //以上为栈接口

typedef struct {
    Stack s1;
    Stack s2;
} MyQueue;


MyQueue* myQueueCreate() {
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
    StackInit(&obj->s1);
    StackInit(&obj->s2);
    return obj;
}

void myQueuePush(MyQueue* obj, int x) {
    StackPush(&obj->s1,x);
}

int myQueuePeek(MyQueue* obj) {
    //不为空直接获取s2栈顶
    if(!StackEmpty(&obj->s2))
    {
        return StackTop(&obj->s2);
    }
    //否则先从s1倒数据到s2
    while(obj->s1.top>0)
    {
        StackPush(&obj->s2,StackTop(&obj->s1));
        StackPop(&obj->s1);
    }
    return StackTop(&obj->s2);
}

int myQueuePop(MyQueue* obj) {
    int top = myQueuePeek(obj);
    StackPop(&obj->s2);
    return top;
}

bool myQueueEmpty(MyQueue* obj) {
    return StackEmpty(&obj->s1) && StackEmpty(&obj->s2);
}

void myQueueFree(MyQueue* obj) {
    StackDestory(&obj->s1);
    StackDestory(&obj->s2);
    free(obj);
}

  4. 设计循环队列

【数据结构】栈与队列OJ - 精选精讲_第4张图片

  • 思路讲解

数组队列实现,不使用接口。
重点

  1. 判断为空和为满(为空时back == front;满时back+1 == front,注意数学取模方法)
  2. 涉及下标操作都要进行数学取模使下标在处于尾部时能循环回头部(循环队列空间恒定不变)

数学取模思想
适用于循环问题。
x = x % k
x = (x+k) % k

  • 代码实现
typedef struct {
    int* a;
    int k;
    int front; //队头下标
    int back;  //队尾下标
} MyCircularQueue;

MyCircularQueue* myCircularQueueCreate(int k) {
    //1.malloc结构体指针
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    //2.malloc数组(大小固定且多开辟一个空间)
    obj->a = (int*)malloc(sizeof(int)*(k+1));
    obj->k = k;
    obj->front = 0;
    obj->back = 0;
    return obj;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->back == obj->front;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->back+1)%(obj->k+1) == obj->front;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
        return false;
     obj->a[obj->back] = value;
     //插入完back下标++
     obj->back++;
     //取模使back绕回头部
     obj->back %= (obj->k+1);
     return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return false;
    //删除队头,队头下标++
    obj->front++;
    //先++再取模
    obj->front %= (obj->k+1);
    
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(!myCircularQueueIsEmpty(obj))
        return obj->a[obj->front];
    else
        return -1;
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(!myCircularQueueIsEmpty(obj))
        return obj->a[(obj->back-1 + obj->k+1) % (obj->k+1)];
    else
        return -1;
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

总结

本文精选了栈与队列经典OJ题,如果对你有所帮助,还望点赞收藏支持博主。
文章中有什么不对的丶可改正的丶可优化的地方,欢迎各位来评论区指点交流,博主看到后会一一回复。

你可能感兴趣的:(数据结构,算法,c++,c语言)