04栈和队列

Posted 再吃一个橘子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了04栈和队列相关的知识,希望对你有一定的参考价值。

肚子饿了就要吃   ~   嗝  ——— 路飞  

目录

1、栈

1.1栈的结构及概念

1.2栈的实现

Stack.h文件

初始化栈

销毁栈

压栈

弹栈

取栈顶数据

获取栈中有效元素个数

检测栈是否为空,如果为空返回非零结果,如果不为空返回0

遍历栈( - 从栈顶到栈底)

栈面试题目

(1)括号匹配问题

1.3栈的应用

2.队列

2.1队列的概念及结构

2.2队列的实现

初始化

销毁队列

入队

出队(!!)

取队尾数据

取队头数据

是否为空队列

计算队列中数据个数

遍历数据

2.3队列应用


1、栈

1.1栈的结构及概念

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

 采用数组栈的原因:

1.2栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

定长的静态栈的结构,实际中一般不实用,如下代码定义结构体。         

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
typedef int STDataType;
#define N 10
typedef struct Stack
{
	STDataType a[N];
	int top; // 栈顶
}Stack;

 所以我们主要实现下面的支持动态增长的栈

——————————

Stack.h文件

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

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

//初始化
void StackInit(ST* ps);

//销毁栈
void StackDestroy(ST* ps);

//压栈
void StackPush(ST* ps, STDataType x);

//弹栈
void StackPop(ST* ps);

//取栈顶数据
STDataType StackTop(ST* ps);

//获取栈中有效元素个数
int StackSize(ST* ps);

//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(ST* ps);

初始化栈

注意点:

ps->top = 0;可以;也可以ps->top = -1;差别在于:

初始化时,top给的是0,意味着top指向栈顶数据的下一个

如果top给的是-1,意味着top指向栈顶数据

说明:在如下程序中,我们都采用  ps->top = 0

//初始化
void StackInit(ST* ps) 
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;//ps->top = -1,注意两者写法的区别
	ps->capacity = 0;
}

销毁栈

//销毁栈
//销毁栈很有必要,因为StackPop只是把数据弹栈,并没有把空间还回去
void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 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));//<==> assert(ps->top > 0);

	ps->top--;
}

取栈顶数据

//取栈顶数据
STDataType StackTop(ST* ps)
{
    assert(ps);
    return ps->a[ps->top - 1];
}

获取栈中有效元素个数

//获取栈中有效元素个数
int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

检测栈是否为空,如果为空返回非零结果,如果不为空返回0

//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(ST* ps)
{
	assert(ps);

	//if (ps->top == 0)
	//{
	//	return true;
	//}
	//else
	//{
	//	return false;
	//}
	return ps->top == 0;
}

遍历栈( - 从栈顶到栈底)

	//遍历栈
	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}

总代码:

Stack.c文件

#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
//初始化
void StackInit(ST* ps) 
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;//ps->top = -1,注意两者写法的区别
	ps->capacity = 0;
}

//销毁栈
//销毁栈很有必要,因为StackPop只是把数据弹栈,并没有把空间还回去
void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 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));//<==> assert(ps->top > 0);

	ps->top--;
}

//取栈顶数据
STDataType StackTop(ST* ps)
{
	assert(ps);
	return ps->a[ps->top - 1];
}

//获取栈中有效元素个数
int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(ST* ps)
{
	assert(ps);

	//if (ps->top == 0)
	//{
	//	return true;
	//}
	//else
	//{
	//	return false;
	//}
	return ps->top == 0;
}

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);
	StackPush(&st, 6);
	StackPush(&st, 7);

	//遍历栈
	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}
	StackDestroy(&st);
}
int main()
{
	TestStack();

	return 0;
}

栈面试题目

(1)括号匹配问题

想想代码1有无错误?

代码1:

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

//初始化
void StackInit(ST* ps);

//销毁栈
void StackDestroy(ST* ps);

//压栈
void StackPush(ST* ps, STDataType x);

//出栈
void StackPop(ST* ps);

//取栈顶数据
STDataType StackTop(ST* ps);

//获取栈中有效元素个数
int StackSize(ST* ps);

//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(ST* ps);

//初始化
void StackInit(ST* ps) 
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;//ps->top = -1,注意两者写法的区别
	ps->capacity = 0;
}

//销毁栈
//销毁栈很有必要,因为StackPop只是把数据弹栈,并没有把空间还回去
void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 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));//<==> assert(ps->top > 0);

	ps->top--;
}

//取栈顶数据
STDataType StackTop(ST* ps)
{
	assert(ps);
	return ps->a[ps->top - 1];
}

//获取栈中有效元素个数
int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

bool isValid(char * s)
{
    ST st;
    StackInit(&st);
    while(*s)
    {
        //左括号
        if(*s == '('
        ||*s == '{'
        ||*s == '[')
        {
            StackPush(&st,*s);
            s++;
        }
        //右括号
        else
        {
            STDataType top = StackTop(&st);
            StackPop(&st);
            if((*s == '}' && top != '{')
             ||(*s == ']' && top != '[')
             ||(*s == ')' && top != '('))
             {
                 //防止内存泄漏
                 StackDestroy(&st);
                 return false;
             }
             else
             {
                 s++;
             }
        }
    }

    StackDestroy(&st);
    return ret;
}

————————

当只有一个左括号的时候,输出应该是false,所以,我们需要在最后判断一下,是否栈是空的?如果栈不是空的,则说明栈中多出来的左括号,没有相应的右括号和他匹配!

 所以,做出以下修改:

 修改以后,再运行发现还有缺漏____断言错误:

即:在程序执行了assert(!StackEmpty(ps));而我们知道,assert()会在内容为假( 0 )的时候,才去执行。所以,执行了assert,说明内容!StackEmpty(ps)( 0 ),即:StackEmpty(ps)(1)。——即:在栈是空的时候会出现问题!!

 ————————   所以,这个地方出了问题:

需要先判断一下,此时进来的右括号是不是有栈中的左括号对应(不考虑是不是同类型的对应),即:判断栈是不是空栈!

修改方案:

 AC代码:

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

//初始化
void StackInit(ST* ps);

//销毁栈
void StackDestroy(ST* ps);

//压栈
void StackPush(ST* ps, STDataType x);

//出栈
void StackPop(ST* ps);

//取栈顶数据
STDataType StackTop(ST* ps);

//获取栈中有效元素个数
int StackSize(ST* ps);

//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(ST* ps);

//初始化
void StackInit(ST* ps) 
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;//ps->top = -1,注意两者写法的区别
	ps->capacity = 0;
}

//销毁栈
//销毁栈很有必要,因为StackPop只是把数据弹栈,并没有把空间还回去
void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 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));//<==> assert(ps->top > 0);

	ps->top--;
}

//取栈顶数据
STDataType StackTop(ST* ps)
{
	assert(ps);
	return ps->a[ps->top - 1];
}

//获取栈中有效元素个数
int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

//检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

bool isValid(char * s)
{
    ST st;
    StackInit(&st);
    while(*s)
    {
        //左括号
        if(*s == '('
        ||*s == '{'
        ||*s == '[')
        {
            StackPush(&st,*s);
            s++;
        }
        //右括号
        else
        {
            //遇到右括号了,但是栈里面没有数据,说明
            //前面没有左括号,不匹配,返回false
            if (StackEmpty(&st))
            {
	             StackDestroy(&st);
	             return false;
            }
            STDataType top = StackTop(&st);
            StackPop(&st);
            if((*s == '}' && top != '{')
             ||(*s == ']' && top != '[')
             ||(*s == ')' && top != '('))
             {
                 //防止内存泄漏
                 StackDestroy(&st);
                 return false;
             }
             else
             {
                 s++;
             }
        }
    }

    // 如果栈不是空,说明栈中还有左括号未出
    // 没有相匹配的右括号,返回的是false
    bool ret = StackEmpty(&st);
    StackDestroy(&st);
    return ret;
}

1.3栈的应用

弹夹上子弹

2.队列

2.1队列的概念及结构

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

入队列:进行插入操作的一端称为队尾 。  

出队列:进行删除操作的一端称为队头

2.2队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

Queue.h文件

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

typedef int QDataType;

typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QueueNode;

//队列的头尾
typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;

//初始化队列
void QueueInit(Queue* pq);

//销毁队列
void QueueDestroy(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);

初始化

//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}

销毁队列

//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur != NULL)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

入队

入队有两种情况:

//入队
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//创建新节点
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

出队(!!)

 参考下图、代码1,思考代码1是否有错误?

代码1:

//出队
void QueuePop(Queue* pq)
{
	assert(pq);
	QueueNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
}

————————代码1出现错误!!

当我们删除队列中的最后一个结点时,不能再删除了!所以我们需要判断是否删完了队列!!

代码2(改进代码1)


//出队
void QueuePop(Queue* pq)
{
	assert(pq);
	//温柔的方式(不推荐)
	//if (pq->head == NULL)
	//{
	//	return;
	//}

	//粗暴的方式
	assert(!QueueEmpty(pq));
	QueueNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
}

代码2这样写就对了嘛??

——————同样是存在错误的!!因为当我们删除完最后一个结点的时候,是如图所示这种情况的:

那么此时的tail对应的尾结点被删除,tail就是野指针了!!

最终代码:

//出队
void QueuePop(Queue* pq)
{
	assert(pq);
	//温柔的方式(不推荐)
	//if (pq->head == NULL)
	//{
	//	return;
	//}

	//粗暴的方式
	assert(!QueueEmpty(pq));
	QueueNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	if (pq->head == NULL)
	{
		pq->tail = NULL;
	}
}

取队尾数据

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

取队头数据

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

是否为空队列

//是否为空队列
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

计算队列中数据个数

//计算有多少个数据
int QueueSize(Queue* pq)
{
	int n = 0;
	QueueNode* cur = pq->head;
	while (cur)
	{
		n++;
		cur = cur->next;
	}
	return n;
}

遍历数据

	//遍历数据
	while (!QueueEmpty(&q))
	{
		QDataType front = QueueFront(&q);
		printf("%d ", front);
		QueuePop(&q);
	}
	printf("\\n");

2.3队列应用

医院、银行、营业厅排队的抽号机,保证公平性,先来先被服务。

以上是关于04栈和队列的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode通关:栈和队列六连,匹配问题有绝招

LeetCode通关:栈和队列六连,匹配问题有绝招

表栈和队列

数据结构 Java数据结构 栈和队列 以及LeetCode相关面试题

栈和队列基本操作

栈和队列