《数据结构》--【栈和队列总结】---【常见OJ题】

本文详细介绍了栈和队列的概念、结构及其在C语言中的实现,包括栈的压栈、出栈、判断空栈,队列的入队、出队、判断空队等操作。并结合力扣题目,如有效括号、用队列实现栈和用栈实现队列,探讨了栈和队列在实际问题中的应用。最后进行了知识点总结,强调在解决OJ题时要注意的细节和难点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

栈的概念和结构

  1. 栈是一种特殊的线性表,只允许在固定的一端进行插入和删除元素的操作,其中进行操作的一端叫做栈顶,另一端叫做栈底
  2. 栈中的数据元素遵守**后进先出(先进后出)**的原则
  3. 栈的插入操作叫做压栈(入栈)(进栈)
  4. 栈的删除操作叫做出栈,出数据在栈顶操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HwmGWO6h-1659798848716)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806212807450.png)]

栈的实现

栈的实现一般使用数组或者链表实现,相对来说数组的结构实现会更加优化一点,因为数组在尾上插入数据的代价比较小

定义一个整形数组类型的栈

typedef int STDataTypedef;
typedef struct STNode
{
	STDataTypedef* a;
	int top;
	int capacity;
}ST;

实现栈的初始化

需要将结构体里的指针置空,并且两个数据都置为0

//初始化
void StackInit(ST* st) {
	assert(st);

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

实现栈的销毁

要将结构体里的指针释放,并且两个数据都置为0

//销毁
void StackDestroy(ST* st) {
	assert(st);

 	free(st->a);
	st->a = NULL;

	st->capacity = st->top = 0;
}

栈顶的插入

首先需要判断是否有足够的容量存储数据,如果容量不足需要进行增容

//插入
void StackPush(ST* st, STDataTypedef x) {
	assert(st);

	//判断是否需要扩容
	if (st->top == st->capacity) {
		//需要扩容的新容量
		int newcapacity = st->capacity == 0 ? 4 : 2 * st->capacity;
		STDataTypedef* tmp = (STDataTypedef*)realloc(st->a, sizeof(STDataTypedef) * newcapacity);
		if (tmp == NULL) {
			perror("malloc fail");
			return;
		}
		//原容量变为新容量
		st->capacity = newcapacity;

		st->a = tmp;
	}

	//栈顶数据插入
	st->a[st->top] = x;
	st->top++;
}

栈顶的出栈

要判断栈是否为空,如果为空则不能进行该操作

//删除栈顶数据
void StackPop(ST* st) {
	assert(st);
	assert(!StackEmpty(st));

	st->top--;
}

判断栈是否为空

只需要判断栈里是否有元素

//判断栈是否为空
bool StackEmpty(ST* st) {
	assert(st);

	return st->top == 0;
}

访问栈顶数据

要判断栈是否为空,如果为空则不能进行该操作

//访问栈顶数据
STDataTypedef Stacktop(ST* st) {
	assert(st);
	assert(!StackEmpty(st));

	return st->a[st->top - 1];
}

main函数调试

void Test1() {
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	while (!StackEmpty(&st)) {
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}
	StackDestory(&st);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0vrYjKRc-1659798848717)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806214834435.png)]

队列

队列的概念和结构

  1. 队列只允许在一端进行插入数据操作,另一端进行删除数据操作的,是一种特殊的线性表
  2. 队列遵守先进先出 后进后出的原则
  3. 进行插入操作的一端叫做队尾,进行删除操作的一端叫做队头

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cG9cLVaQ-1659798848718)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806215352010.png)]

队列的实现

队列的定义

相对于栈而言,使用链表实现队列更优

先定义一个节点结构再定义一个队列结构,节点结构里包括数据和指向下一个节点的指针,队列结构里包括队列的队头和队尾以及队列的长度

typedef struct QueueNode {
	int val;
	struct QueueNode* next;
}QN;

typedef struct Queue {
	QN* head;
	QN* tail;
	int size;
}QU;

队列的初始化

需要把队头和队尾置空,以及长度置为0

void QueueInit(QU* pq) {
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

队列的销毁

因为队列里会有节点,每个节点都需要被销毁,所以需要遍历队列链表将每一个节点销毁i,再把队列的队头和队尾都置空,并且队列的长度要置为0

void QueueDestroy(QU* pq) {
	assert(pq);
	QN* cur = pq->head;
	//遍历队列
	while (cur) {
		QN* del = cur;
		cur = cur->next;
		free(del);
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

队列插入数据

每一次插入数据都需要开辟一块新的节点,这里会有两种情况:当队列为空时 那么新的节点既是队头也是队尾;当队列不为空时,原队尾向后走一步变成新节点。队列长度加1

void QueuePush(QU* pq, int x) {
	assert(pq);
	QN* newnode = (QN*)malloc(sizeof(QN));
	if (newnode == NULL) {
		perror("malloc fail");
		return;
	}
	newnode->val = x;
	newnode->next = NULL;
	if (pq->tail == NULL)
		pq->head = pq->tail = newnode;
	else {
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

队列删除数据

删除数据都是要从队头开始,所以只需要原队头向后走一步变成新队头,并且释放原队头。需要注意的是队列不能为空;队列如果只有一个节点的话,直接将队头释放掉即可。队列长度-1

void QueuePop(QU* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
    //判断队列是否只有一个节点
	if (pq->head->next==NULL) {
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else {
		QN* del = pq->head;
		pq->head = del->next;
		free(del);
		del = NULL;
	}
	pq->size--;
}

队头的数据

判断队列是否为空,如果不为空直接取出队头数据即可

int QueueFront(QU* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->val;
}

队尾的数据

判断队列是否为空,如果不为空直接取出队尾数据即可

int QueueBack(QU* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->val;
}

判断队列是否为空

只需要判断队头是否为空即可

bool QueueEmpty(QU* pq) {
	assert(pq);
	return pq->head == NULL;
}

main函数调试

void Test2() {
	QU p;
	QueueInit(&p);
	QueuePush(&p, 1);
	QueuePush(&p, 2);
	QueuePush(&p, 3);
	QueuePush(&p, 4);
	while (!QueueEmpty(&p)) {
		printf("%d ", QueueFront(&p));
		QueuePop(&p);
	}
	QueueDestroy(&p);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VcxuMVn7-1659798848718)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806221722164.png)]

OJ题

力扣20:有效的括号

OJ链接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-078MBWXc-1659798848719)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806221904567.png)]

思路:利用 先进后出的原则,遍历字符串,当遇到左括号时将它存放进数组里,如果一旦遇到右括号就将数组里的左括号拿出来一一比较,一旦发现不匹配就返回fasle。一种情况如果字符串为空或者字符串长度为奇数时那就不可能会匹配了

bool isValid(char * s){
    int k = strlen(s);
    int arr[k + 1];
    int n=0;
    if(strlen(s) % 2 == 1){
        return false;
    }
    if(*s == '\0')
    {
        return false;
    }
    //遍历字符串
    while(*s){
        char c = *s;
        //遇到左括号存放进数组
        if(*s == '(' || *s == '{' || *s == '['){
            arr[n] = c;
            n++;
        }
        //一旦遇到右括号就可以进行匹配
        else{
            if(n == 0)
                return false;
            char ch = arr[n - 1];
            if((*s ==')' && ch != '(')
               || (*s =='}' && ch != '{')
               || (*s ==']' && ch != '['))
                {
                    return false;
                }
            n--;
        }
        s++;
    }
    return n == 0;
}

力扣225:用队列实现栈

OJ链接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-roCy3GvJ-1659798848720)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806222546276.png)]

思路:利用两个队列,始终保持一个为空,需要出数据的时候把除了队尾外的数据导入空的队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kUEo8bBH-1659798848721)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806223126409.png)]

由于C语言库里没有定义队列,所以需要自己定义一个,操作功能和前面写的一样

typedef struct QueueNode {
	int val;
	struct QueueNode* next;
}QN;

typedef struct Queue {
	QN* head;
	QN* tail;
	int size;
}QU;

void QueueInit(QU* pq);

void QueueDestroy(QU* pq);

void QueuePush(QU* pq, int x);

void QueuePop(QU* pq);
//队列头的值
int QueueFront(QU* pq);
//队列尾的值
int QueueBack(QU* pq);
//判断是否为空
bool QueueEmpty(QU* pq);

int QueueSize(QU* pq);

void QueueInit(QU* pq) {
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

void QueueDestroy(QU* pq) {
	assert(pq);
	QN* cur = pq->head;
	while (cur) {
		QN* del = cur;
		cur = cur->next;
		free(del);
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

void QueuePush(QU* pq, int x) {
	assert(pq);
	QN* newnode = (QN*)malloc(sizeof(QN));
	if (newnode == NULL) {
		perror("malloc fail");
		return;
	}
	newnode->val = x;
	newnode->next = NULL;
	if (pq->tail == NULL)
		pq->head = pq->tail = newnode;
	else {
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

void QueuePop(QU* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	if (pq->head->next==NULL) {
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else {
		QN* del = pq->head;
		pq->head = del->next;
		free(del);
		del = NULL;
	}
	pq->size--;
}

int QueueFront(QU* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->val;
}

int QueueBack(QU* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->val;
}

bool QueueEmpty(QU* pq) {
	assert(pq);
	return pq->head == NULL;
}

int QueueSize(QU* pq) {
	assert(pq);
	return pq->size;
}

//定义两个队列
typedef struct {
    QU p1;
    QU p2;
} MyStack;


MyStack* myStackCreate() {
    MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
    QueueInit(&obj->p1);
    QueueInit(&obj->p2);
    return obj;
}

//插入数据需要插入到不为空的队列里
void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->p1))
        QueuePush(&obj->p1, x);
    else
        QueuePush(&obj->p2, x);

}

//先判断出哪个队列不为空
//然后将该队列除了队尾外的数据
//导入到空的队列,将原队尾销毁即可
int myStackPop(MyStack* obj) {
    QU* empty = &obj->p1;
    QU* noempty = &obj->p2;
    if(!QueueEmpty(&obj->p1)){
        empty = &obj->p2;
        noempty = &obj->p1;
    }

    while(QueueSize(noempty) > 1){
        QueuePush(empty, QueueFront(noempty));
        QueuePop(noempty);
    }
    int top = QueueFront(noempty);
    QueuePop(noempty);
    return top;
}

//直接利用队列函数返回不为空的队列的尾数据即可
int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&obj->p1))
        return QueueBack(&obj->p1);
    else
        return QueueBack(&obj->p2);
}

//两个队列都为空才为空
bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->p1) && QueueEmpty(&obj->p2);
}

//要注意不止是释放掉开辟的内存
//开辟的内存指向的空间也要释放
void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->p1);
    QueueDestroy(&obj->p2);
    free(obj);
}

力扣232:用栈实现队列

OJ链接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WgIpa8KG-1659798848721)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806223706387.png)]

思路:和上一道题差不多,需要用到两个栈来实现,一个栈用来插入数据,一个栈用来删除数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qjlhTYT2-1659798848722)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806224545851.png)]

由于C语言库里没有定义栈,所以需要自己定义一个,操作功能和前面写的一样

typedef struct Stack {
	int* a;
	int top;
	int capacity;
}ST;

//初始化
void StackInit(ST* ps);
//销毁
void StackDestory(ST* ps);
//插入
void StackPush(ST* ps, int x);
//删除
void StackPop(ST* ps);
//判断是否为空
bool StackEmpty(ST* ps);
//统计数据个数
int StackSize(ST* ps);
//访问栈顶数据
int StackTop(ST* ps);

//初始化
void StackInit(ST* ps) {
	assert(ps);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

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

//插入
void StackPush(ST* ps, int x) {
	assert(ps);
	int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
	if (ps->capacity == ps->top) {
		int* tmp = (int*)realloc(ps->a, sizeof(int) * newcapacity);
		if (tmp == NULL) {
			perror("malloc fail");
			return;
		}
		ps->a = tmp;
	}
	ps->a[ps->top++] = x;
}

//删除
void StackPop(ST* ps){
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
}

//判断是否为空
bool StackEmpty(ST* ps) {
	assert(ps);
	return ps->top == 0;
}

//统计数据个数
int StackSize(ST* ps) {
	assert(ps);
	return ps->top;
}

//访问栈顶数据
int StackTop(ST* ps) {
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->top - 1];
}

//创建两个栈
typedef struct {
    ST q1;
    ST q2;
} MyQueue;


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

//在第一个栈里直接插入数据即可
void myQueuePush(MyQueue* obj, int x) {
    StackPush(&obj->q1, x);
}

//将push的队列数据导入pop队列
void PushToPop(MyQueue* obj){
    //如果pop栈为空
    if(StackEmpty(&obj->q2)){
        //将push栈的所有数据全部导入pop栈
        while(!StackEmpty(&obj->q1)){
            StackPush(&obj->q2, StackTop(&obj->q1));
            StackPop(&obj->q1);
        }
    }
}

//取出的数据就是负责删除数据的栈的栈顶数据
int myQueuePop(MyQueue* obj) {
    PushToPop(obj);
    int front=StackTop(&obj->q2);
    StackPop(&obj->q2);
    return front;
}

int myQueuePeek(MyQueue* obj) {
    PushToPop(obj);
    return StackTop(&obj->q2);
}

//两个栈为空才为空
bool myQueueEmpty(MyQueue* obj) {
    return StackEmpty(&obj->q1) && StackEmpty(&obj->q2);
}

//要注意不止是释放掉开辟的内存
//开辟的内存指向的空间也要释放
void myQueueFree(MyQueue* obj) {
    StackDestory(&obj->q1);
    StackDestory(&obj->q2);
    free(obj);
}

力扣622:设计循环队列

OJ链接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PzqRcybC-1659798848722)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806225424474.png)]

思路:因为队列的长度是固定的,所以使用数组结构解决该组。创建一个长度为队列长度+1的数组,可以利用两个下标,一个头下标,一个尾下标,头下标为队头的下标,尾下标为队尾下标+1的位置。

当头下标和尾下标相等时,说明该队列为空

当尾下标加一等于头下标时,说明该队列满了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pq1x8BHr-1659798848723)(C:\Users\黄竞标\AppData\Roaming\Typora\typora-user-images\image-20220806230343505.png)]

typedef struct {
    //数组
    int* a;
    //头下标
    int front;
    //尾的下一个下标
    int back;
    //数组长 = 队列长+1
    int N;
} MyCircularQueue;

//当back等于front就为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->back;
}

//由于考虑到back在最尾时如果+1就会越界
//所以让back+1后余上数组长度就会返回到0下标
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->back + 1) % obj->N == obj->front;
}

MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a = (int*)malloc(sizeof(int) * (k + 1));
    obj->front = obj->back = 0;
    obj->N = k + 1;
    return obj;
}

//如果数组满了那就不能插入数据了
//如果数组没满,那原back位置插入数据后
//但是考虑到back会处在最尾端
//所以加1后余上数组长度就会走到数组内的下一个位置
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
        return false;
    obj->a[obj->back] = value;
    obj->back++;
    obj->back %= obj->N;
    return true;
}

//如果数组为空就不能插入数据了
//如果不为空,原front下标加1
//但是考虑到front会处在最尾端
//所以加1后余上数组长度就会走到数组内的下一个位置
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return false;
    obj->front++;
    obj->front %= obj->N;
    return true;
}

//数组不为空时,返回front下标的数据即可
int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    return obj->a[obj->front];
}

//数组不为空时,返回back-1下标的数据即可
//但是考虑到back会处在最前端
//所以减1后加上数组长再余上数组长就可以
//得到数组内的back下标前一个下标
int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;
    return obj->a[(obj->back - 1 + obj->N) % obj->N];
}

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

总结

栈和队列的结构在做起OJ题时需要注意的细节比较多,也比较复杂,要更加耐心细心的去思考,多画图去解决问题

以上就是个人对栈和队列这一节的知识点总结,如果不足 希望指出

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值