【数据结构】栈和队列

目录

栈的基本概述

栈的程序实现

栈的实现

Stack.h

Stack.c

Test.c

队列的基本概述

队列的程序实现

队列的实现

Queue.h

Queue.c

Test.c

栈和队列oj练习题

有效的括号

用队列实现栈


栈的基本概述

:一种特殊的线性表,其只允许在固定的一端进行插入删除元素操作。进行数据插入和删除
操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)
的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。

栈的程序实现

栈的实现

顺序表解决了头删头插的难题,但需要增容
链表

如果是单链表,则在尾删尾插时,需要找到尾节点的前驱,这是比较麻烦的:

1.双向链表,在节点内多定义一个prev指针

2.逆向思维,出栈入栈看作链表的头插头删

Stack.h

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>
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);

Stack.c

StackInit

和动态顺序表极其类似,malloc一个空间

值得注意的是:

如果top = 0,则意味着 ps->a[top]没有存储数据,入栈的时候先录入数据再++,且 top == 实际栈中的数据个数。

如果top = -1,则意味着 ps->a[top]存储了有效数据,入栈时先++再录入数据,且top == 实际栈中数据个数 - 1

void StackInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*) malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	ps->capacity = 4;
	ps->top = 0;
}

StackDestory

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

StackPush

void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		STDataType*tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}
	ps->a[ps->top] = x;
	ps->top++;
}

StackPop

出栈

void StackPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}

StackTop

获取栈顶元素

STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}

注意assert,如果top == 0时,则说明栈中没有数据了

StackSize

int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

StackEmpty

判断栈是否为空

bool StackEmpty(ST* ps)
{
	assert(ps);
	return !(ps->top);
}

注意,这里使用了布尔值,如果ps->top == 0,则返回true

Test.c

#define _CRT_SECURE_NO_WARNINGS

#include"Stack.h"

void TestStack()
{
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	printf("%d\n", StackSize(&st));
	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}
	printf("\n");
	printf("%d", StackSize(&st));
	StackDestory(&st);
}
{
	TestStack();
	return 0;
}

队列的基本概述

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有
进先出
FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一
端称为队头

队列的程序实现

队列的实现

队列最重要的性质是:队头出队,队尾入队,而我们之前学过的单链表,头删和尾插是能够十分便捷的完成这一功能,所以我们需要使用单链表作为队列的基本结构。

但是值得注意的是:在进行尾插的时候,我们需要知道尾节点的地址,而遍历会让程序变得冗余。所以与单链表不同的是,我们需要定义一个tail,目的是指向尾节点,这样子可以极大提高程序运行效率。

Queue.h

这里与单链表不同的是,在一个队列中,我们需要定义两个指针,一个指向队头,进行头删;一个指向队尾,进行尾删。所以我们不妨将这两个量也封装成一个结构体,每次都从这个结构体内调用head和tail。

第二个结构体,就是我们常规的单链表的结构体了,节点中有一个后驱和存储的数据。

值得注意的是:由于我们在函数中会修改结构体的值,所以在函数传参的时候,我们会采用传址传参的方法,即传入结构体的指针

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>

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 QueueDestory(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType x);
//出队
void QueuePop(Queue* pq);
//取队头的数据
QDataType QueueFront(Queue* pq);
//取队尾的数据
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

Queue.c

QueueInit

初始化队列:即将pq内的tail和head置空

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

QueueDestory

销毁队列:这里我们每次入队,都会malloc一个新的内存空间,所以我们需要依次释放这些内存空间,需要遍历整个队列,而不是简单地置空pq这个结构体

void QueueDestory(Queue* pq)
{
	QNode* cur = pq->head;
	//销毁需要将所有申请的内存空间全部置空
	//所以需要遍历+free
	while (cur)
	{
		QNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
	pq->head = pq->tail = NULL;
}

QueuePush

入队,即单链表中的尾插。这里与尾插有不同的地方是:我们多定义了一个tail指针,当队列没有节点时,即head和tail均为空,我们需要额外将head和tail赋值为新节点。

void QueuePush(Queue* pq, QDataType x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	newnode->next = NULL;
	newnode->data = x;
	if (newnode == NULL)
	{
		printf("malloc fail!\n");
		exit(-1);
	}
	assert(pq);
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

QueuePop

出队,相当于单链表中的头删法

我们也是需要分情况讨论

1.当队列中还有一个以上的节点时:

进行头删法,注意先保存head->next,再释放head

2.当队列中只有一个节点时,即head == tail:

为了防止tail成为野指针,我们需要直接将head释放,并将head和tail置空

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	//删除到最后一个节点时,tail会变成野指针
	//还剩多个节点时
	if (pq->head->next)
	{
		QNode* tmp = pq->head->next;
		free(pq->head);
		pq->head = tmp;
	}
	//删除到只剩最后一个节点时:head->next为空
	else
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
		//将head和tail置空,恢复成初始化后的情形
	}
}

QueueFront

取队头的节点,注意断言:没有节点时需要断言

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->head->data;
}

QueueBack

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->tail);
	return pq->tail->data;
}

QueueSize 

注意循环条件 cur不为空,当cur为最后一个节点时,仍需要进入循环,size++

int QueueSize(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	int size = 0;
	while (cur)
	{
		cur = cur->next;
		size++;
	}
	return size;
}

QueueEmpty

判断队列是否为空,返回值是布尔值

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	if (pq->head)
	{
		return false;
	}
	else
	{
		return true;
	}
}

Test.c

#include"Queue.h"
void TestQueue()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePop(&q);
	printf("%d\n", QueueSize(&q));
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	QueueDestory(&q);
}
int main()
{
	TestQueue();
	return 0;
}

栈和队列oj练习题

有效的括号

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"
输出:true

示例 2:

输入:s = "()[]{}"
输出:true

示例 3:

输入:s = "(]"
输出:false

提示:

  • 1 <= s.length <= 104
  • s 仅由括号 '()[]{}' 组成

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/valid-parentheses/

思路:

括号匹配的本质是:如果出现一个左括号,在这个左括号后有且只有一个右括号与之匹配。

这题难点在于:我们不知道和左括号匹配的右括号具体位置在哪。

我们先考虑括号匹配的情况:

例如:

"(){}{}"        "[(){}]"        "{[()]}"

我们可以注意到,括号匹配的字符串有一个关键,至少会有一个相连的括号对存在!

"(){}{}"        "[(){}]"        "{[()]}"

然后我们可以发现:如果将这个相连的括号对删去,我们可以很欣喜的发现:

再次出现了至少一个相连的括号对

"(){}"        "[]"        "{[]}"

如此反复,全部元素被删除完毕后,则一定是匹配的

那我们接下来考虑其他不匹配的情况:

1.例如:

"{{"        "{{{}}"

只有左括号和右括号,或者左右括号数量不相等:

这显然不成立,依据上述算法,不可能将所有括号删除

2.例如

"(([)])"        "(}"

左右括号数量相等,但是没有相连,这也不能进行删除,所以不会清空字符串。

所以,我们可以得到一个思路:找到相连的括号对,进行删除

所以,我们可以利用栈来实现这个思路。

typedef char STDataType;
typedef struct Stack
{
    STDataType* s;
    int Top;
    int capacity;
}ST;
void StackInit(ST* ps)
{
    assert(ps);
    ps->Top = 0;
    ps->capacity = 4;
    ps->s = (STDataType*)malloc(sizeof(STDataType) * 4);
}
void StackDestory(ST* ps)
{
    assert(ps);
    free(ps->s);
    ps->capacity = ps->Top = 0;
}
void StackPush(ST* ps, STDataType x)
{
    assert(ps);
    if (ps->capacity == ps->Top)
    {
        STDataType* tmp = (STDataType*)realloc(ps->s, sizeof(STDataType) * ps->capacity * 2);
        if (tmp == NULL)
        {
            printf("realloc fail\n");
            exit(-1);
        }
        else
        {
            ps->s = tmp;
            ps->capacity *= 2;
        }
    }
    ps->s[ps->Top] = x;
    ps->Top++;

}
void StackPop(ST* ps)
{
    assert(ps);
    assert(ps->Top);
    ps->Top--;
}
STDataType StackTop(ST* ps)
{
    assert(ps);
    assert(ps->Top);
    return ps->s[ps->Top - 1];
}
bool StackEmpty(ST* ps)
{
    assert(ps);
    return !ps->Top;
}
bool isValid(char* s)
{
    //初始化一个栈,用于存放字符串s
    ST st;
    StackInit(&st);
    //遍历字符串中所有的字符
    int i = 0;
    while (s[i] != '\0')
    {
        switch (s[i])
        {
            //如果是这三个左括号,则入栈
        case '(':
        case '{':
        case '[':
        {
            StackPush(&st, s[i]);
            break;
        }
        //如果是这三个右括号,则取出栈顶的元素与之进行匹配
        //如果匹配成功,则继续;如果匹配不成功,则返回false
        case ')':
        case '}':
        case ']':
        {
            if (StackEmpty(&st))
            {
                StackDestory(&st);
                return false;
            }
            char Top = StackTop(&st);
            StackPop(&st);
            //不匹配
            if ((Top == '(' && s[i] != ')')
                || (Top == '{' && s[i] != '}')
                || (Top == '[' && s[i] != ']'))
            {
                StackDestory(&st);
                return false;
            }
        }
        }
        i++;

    }
    //实际上,只有左括号可以入栈,如果有匹配的右括号,则出栈
    //所以,当栈为空时,说明s中所有的字符都匹配
    bool ret = StackEmpty(&st);
    StackDestory(&st);
    return ret;

}

注意:记得释放空间,防止内存泄漏

用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
 

注意:

你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
 

示例:

输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
 

提示:

1 <= x <= 9
最多调用100 次 push、pop、top 和 empty
每次调用 pop 和 top 都保证栈不为空
 

进阶:你能否仅用一个队列来实现栈。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/implement-stack-using-queues

 思路:

定义两个队列:

Queue11234
Queue2

队列的特点是:先入先出        栈的特点是:后入先出

所以我们需要将队尾的元素依次取出

1.将Queue1中除队尾所有的元素依次入队至Queue2中

Queue14
Queue2123

2.将Queue1中的元素出队

Queue1
Queue2123

 从此循环,直至所有元素出队

这就模拟了入栈和出栈的模式,基本实现了栈的功能

初始化

MyStack* myStackCreate() 
{
    MyStack* ps = (MyStack*)malloc(sizeof(MyStack));
    QueueInit(ps->Queue1);
    QueueInit(ps->Queue2);
    if (ps == NULL)
    {
        printf("malloc fail\n");
        exit(-1);
    }
    return ps;
}

入栈

按照上述思路,应该往有数据的队列中入数据

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)
{
    //先假设q1为空,q2不为空
    QNode* emptyq = &obj->q1;
    QNode* nonemptyq = &obj->q2;
    //找到不为空的队列
    if (!QueueEmpty(&obj->q1))
    {
        emptyq = &obj->q2;
        nonemptyq = &obj->q1;
    }
    //倒数据
    while (QueueSize(nonemptyq) > 1)
    {
        QueuePush(emptyq,QueueFront(nonemptyq));
        QueuePop(nonemptyq);
    }
    int top = QueueFront(nonemptyq);
    QueuePop(nonemptyq);
    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);
    QueueDestory(&obj->q1);
    QueueDestory(&obj->q2);
    free(obj);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值