栈和队列
本文主要介绍数据结构中的栈和队列,通过C语言来实现栈和队列以及介绍一些关于栈和队列的经典题目。
栈
栈是一种特殊的线性表,它只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则(先进后出)。
压栈:栈的插入操作叫进栈或压栈或入栈,每次入栈的数据都是放在栈顶位置。
出栈:栈的删除叫出栈。出数据也在栈顶。
出栈入栈都在栈顶位置
栈的实现
栈的底层结构可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
要实现栈,首先需要理解其结构形式及特性,它是先进后出(后进先出),由于链表和数组都可以实现栈,栈出入数据都在栈中的一端所以考虑用数组来实现(当然用链表也可以,需要存储栈顶元素的前一个位置(如果不存的话那也可以但是需要出栈付出的代价就有点高了)),本文将使用数组来实现栈的功能,其中包括入栈,删除栈顶元素,获取栈顶数据,当前栈中数据的个数,当前栈是否为空以及栈的销毁。
栈的声明和定义
typedef int Stackdatatype;//为了方便栈存储各种数据所以将int重定义为Stackdatatype,后面要改数据类型只需要修改int就行了
typedef struct Stack//栈的结构
{
Stackdatatype* arr;//用来存储数据
int top;//指向栈顶的下一个位置
int capacity;//表示arr数组的容量大小
}Stack;
int main()
{
Stack st;//定义栈
return 0;
}
Stackinit栈的初始化
对定义的栈进行初始化,可以为栈结构中的数组开辟一些空间也可以不给它开辟空间,下面代码实现的初始化不为栈开辟空间。
//栈初始化
void Stackinit(Stack* ps)//接收的参数是
{
assert(ps);
ps->arr = NULL;//初始化不为数组开辟空间
ps->capacity = 0;//没开辟空间容量大小就为0
ps->top = 0;//指向栈顶元素中的下一个位置
}
StackPush入栈
往栈中入数据操作,入栈前首先需要对栈结构中的底层数组进行判断看容量是否够,如果不够将对其进行扩容操作,扩容完成之后往top位置放入要入栈的数据(top位置表示栈顶位置的下一个所以往top位置放),同时top加1。
//入栈
void StackPush(Stack* ps, Stackdatatype data)
{
assert(ps);
if (ps->capacity == ps->top)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//开辟空间
Stackdatatype* tmp = (Stackdatatype*)realloc(ps->arr, sizeof(Stackdatatype) * newcapacity);
if (tmp == NULL)
{
perror("realloc");
return;
}
//此时表示扩容成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
//进行入栈操作
ps->arr[ps->top++] = data;//往top位置上方,top再往后一个位置
}
StackPop出栈
有入栈操作当然也有出栈操作,出栈操作比较简单,只需要对top减1操作就行,从0-top-1的数据都是栈中的数据。但需要注意的是,top位置一定要大于0,否则就表示栈中没有数据了。代码如下:
//出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
StackTop获取栈顶数据
这个函数的实现与上面类似,同样需要判断栈中是否还有数据,返回的是栈顶元素,但该函数知识获取栈顶元素并不对删除栈顶元素。如果需要获取一个栈顶元素并且将其删除就要配合上StackPop函数使用。StackTop代码如下:
//获取栈顶数据
Stackdatatype StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);//栈中没有数据就报错
return ps->arr[ps->top - 1];//返回top中的前一个这就是栈顶元素
}
StackSize获取栈中的元素个数
该函数的实现很简单,栈中的元素个数就是top的大小
//获取栈中的数据个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;//返回top就是栈中元素的个数
}
StackEmpty判断栈是否为空
栈为空就返回true,如果栈不为空就返回false,而top表示栈中元素的个数,所以直接返回top是否等于0,等于0就返回true,不等于0就返回false。
//栈是否为空,为空就返回true,不为空就返回false
bool StackEmpty(Stack* ps)
{
return ps->top == 0;
}
StackDestroy栈的销毁
//栈的销毁
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->arr);//释放动态开辟的空间
ps->capacity = 0;//容量置0
ps->top = 0;//top指向栈顶元素的下一个,而栈此时栈中没有数据所以将其也置0
}
测试栈
通过定义栈,向栈中插入数据然后再进行获取栈顶数据再出栈,将这些数据依次打印出来。其代码如下:
int main()
{
Stack st;
Stackinit(&st);
StackPush(&st, 1);//入栈
StackPush(&st, 2);//入栈
StackPush(&st, 3);//入栈
StackPush(&st, 4);//入栈
StackPush(&st, 5);//入栈
while (!StackEmpty(&st))//当栈还有数据就继续
{
Stackdatatype ret = StackTop(&st);//获取栈顶数据
printf("%d ", ret);
StackPop(&st);//弹出栈顶元素
}
return 0;
}
实现栈的完整代码
对于栈这个数据结构我们依然采用分文件的形式实现,stack.h文件进行声明,stack.c文件实现栈的核心功能,test.c对栈进行测试
stack.h文件代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
#include<stdbool.h>
typedef int Stackdatatype;
typedef struct Stack
{
Stackdatatype* arr;//用来存储数据
int top;//指向栈顶的下一个位置
int capacity;//表示arr数组的容量大小
}Stack;
//栈初始化
void Stackinit(Stack* ps);
//入栈
void StackPush(Stack* ps, Stackdatatype data);
//出栈
void StackPop(Stack* ps);
//获取栈顶数据
Stackdatatype StackTop(Stack* ps);
//获取栈中的数据个数
int StackSize(Stack* ps);
//栈是否为空,为空就返回true,不为空就返回false
bool StackEmpty(Stack* ps);
stack.c文件代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"
//栈初始化
void Stackinit(Stack* ps)
{
assert(ps);
ps->arr = NULL;
ps->capacity = 0;
ps->top = 0;//指向栈顶元素中的下一个位置
}
//入栈
void StackPush(Stack* ps, Stackdatatype data)
{
assert(ps);
if (ps->capacity == ps->top)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//开辟空间
Stackdatatype* tmp = (Stackdatatype*)realloc(ps->arr, sizeof(Stackdatatype) * newcapacity);
if (tmp == NULL)
{
perror("realloc");
return;
}
//此时表示扩容成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
//进行入栈操作
ps->arr[ps->top++] = data;//往top位置上方,top再往后一个位置
}
//出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
//获取栈顶数据
Stackdatatype StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->arr[ps->top - 1];
}
//获取栈中的数据个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
//栈是否为空,为空就返回true,不为空就返回false
bool StackEmpty(Stack* ps)
{
return ps->top == 0;
}
//栈的销毁
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->arr);//释放动态开辟的空间
ps->capacity = 0;//容量置0
ps->top = 0;//top指向栈顶元素的下一个,而栈此时栈中没有数据所以将其也置0
}
test.c文件代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"
int main()
{
Stack st;
Stackinit(&st);
StackPush(&st, 1);//入栈
StackPush(&st, 2);//入栈
StackPush(&st, 3);//入栈
StackPush(&st, 4);//入栈
StackPush(&st, 5);//入栈
while (!StackEmpty(&st))//当栈还有数据就继续
{
Stackdatatype ret = StackTop(&st);//获取栈顶数据
printf("%d ", ret);
StackPop(&st);//弹出栈顶元素
}
return 0;
}
运行结果如下所示:
队列
队列也是一种数据结构,它只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。队列有着先进先出(First In First Out)入队列,进行插入数据操作的一端就是队尾,出数据的一端是对头。
队列的进出数据示意图如上图所示,从对头出数据,从队尾入数据。
考虑到队列需要不断的在队头队尾进行插入数据和在队头删除数据,所以底层考虑使用链表来实现。(当然底层用数组也可以实现,但所需的成本有点高,删除队头数据时需要挪动数据,而链表只需要改变队头的指向,消耗较低)
队列的实现
队列有着先进先出(后进后出)的性质,其底层可以使用数组来实现,但是不推荐,这是由于队列需要不断的从队头出数据,这就需要不断的挪动数据,这消耗的代价是比较高的。由此,我们底层采用链表结构来实现队列这个数据结构。队列所实现的接口也包括队列的初始化,入队列,删除队头,取队头数据,取队尾数据,队列是否为空,队列中元素的个数,队列的销毁。
队列声明和定义
队列的结构设计:我们采用链表来实现队列,所以其结构声明如下所示:
//定义队列中的节点结构
typedef struct queuenode
{
queuedatatype data;//存放数据
struct queuenode* next;//存放写一个节点的地址
}queuenode;
这上面定义的还只是队列中的节点。如果只是定义上面这个结构后面进行出入队列操作起来就很麻烦了,所以考虑到这我们有可以使用在定义一个结构,这个结构包含队头,队尾以及队列中存有的数据个数,其结构如下:
//定义队列结构
typedef struct queue
{
queuenode* qhead;//队列的头节点
queuenode* qtail;//队列尾节点
int size;//当前队列中所存放的数据个数
}queue;
队列的定义:
int main()
{
queue q;//定义队列
return 0;
}
队列初始化queueinit
队列使用的数据结构是链表所以其初始化时需要将队头队尾都置为空指针,并将元素个数置为0。其代码如下:
//队列初始化
void queueinit(queue* pq)
{
assert(pq);
pq->qhead = NULL;//队头置NULL
pq->qtail = NULL;//队尾置NULL
pq->size = 0;//初始时队列中的元素个数为0
}
入队列queuepush
往一个队列中插入数据,由于底层使用的是链表结构所以每次插入一个数据都需要开辟一个节点。需要注意的是传的是queue*这个类型,这个结构里有指向队头数据的也有指向队尾数据的。如果有数据的话直接通过队尾这个指针进行操作就行,但是此时队列为空就需要不能直接操作了,就需要将新节点的地址同时给队头和队尾指针。其代码实现如下所示:
//入队列
void queuepush(queue* pq, queuedatatype data)
{
assert(pq);
//不管队列是空还是有数据都需要开一个节点的空间
queuenode* newnode = (queuenode*)malloc(sizeof(queuenode));
if (newnode == NULL)//开辟失败
{
perror("malloc");//开辟空间失败打印提示信息
return;
}
//将数据存放在新节点中的data中并将新节点的next置空
newnode->data = data;
newnode->next = NULL;
if (pq->qhead == NULL)//队列为空
{
pq->qhead = pq->qtail = newnode;
}
else//队列不为空
{
pq->qtail->next = newnode;//将当前尾节点的next指向下一个
pq->qtail = newnode;//尾节点指向新节点
}
pq->size++;//队列入一个数据,size+1
}
取队头队尾数据queuefront和queueback
这两个函数接口实现的方式比简单,但也需要注意调用这两个接口的时候需要注意当前队列中是否还有数据,如果没有数据将会报错。其代码的实现如下
//取队头数据
queuedatatype queuefront(queue* pq)
{
assert(pq);
assert(pq->size > 0);//当前队列中s是否还有数据,没有数据就报错
return pq->qhead->data;//有数据就直接返回队头数据
}
//取队尾数据
queuedatatype queueback(queue* pq)
{
assert(pq);
assert(pq->size > 0);//当前队列中是否还有数据,没有数据就报错
return pq->qtail->data;//有数据就直接返回队尾数据
}
删除队头数据queuepop
删除队头的数据也需要注意当前队列中是否还有数据,如果队列中没有数据还进行删除就会报错。要删除的是队头的数据,首先想到的是让保存队头指向的节点再让队头队头指向后一个节点,再释放所保存的那个节点。当队列中有多个数据怎么操作没有问题,但是当队列中只有一个节点的时候就会出现问题(队列结构中的队尾指针将会变成野指针)。所以我们将其分为两种情况,一种是一个节点,另一个是多个节点,不论是多个节点还是一个节点都将头节点保存下来,对于只有一个节点的(其实就是队头和队尾的地址相同,指向同一个节点),还是多个节点,都将队头指针保存下下来,最后将size减1再释放这个指针所指向的空间。中间部分有所不同,当只有一个节点也就是只有一个数据时,同时将头指针和尾指针都置空。如果是多个节点就将头指针指向下一个节点,其代码实现形式如下:
//删除队头数据
void queuepop(queue* pq)
{
assert(pq);
assert(pq->size > 0);//当前队列中是否还有数据,没有数据就报错
queuenode* del = pq->qhead;//将要删除的节点的地址存下来
if (pq->qhead == pq->qtail)//此时只有一个节点
{
pq->qhead = pq->qtail = NULL;//只有一个节点就将指向队头和队尾的指针都置空
}
else//此时有多个节点
{
pq->qhead = pq->qhead->next;//让队头指针指向下一个节点
}
pq->size--;
free(del);//释放节点
}
队列是否为空和队列元素个数
这两个函数接口很简单,但是为了队列这个数据结构的结构的完整性,所以也将其实现出来,其代码如下:
//队列为空返回true
bool queueempty(queue* pq)
{
assert(pq);
//如果size为0就表示队列为空就返回true否则返回false
return pq->size == 0;
}
//返回队列中的数据个数
int queuesize(queue* pq)
{
assert(pq);
return pq->size;//直接返回size
}
销毁队列
队列的销毁,需要释放动态开辟的节点(这需要用遍历的方式),释放完成之后需要将队列这个结构中的队头指针和队尾指针都置成NULL,并且将size置成0。其代码实现如下:
//队列销毁
void queuedestroy(queue* pq)
{
assert(pq);
queuenode* del = pq->qhead;
while (del)//通过链表的遍历,依次释放节点
{
queuenode* next = del->next;//保存下一个节点
free(del);//释放当前节点
del = next;//让del指向下一个节点
}
pq->qhead = NULL;//队头指针置空
pq->qtail = NULL;//队尾指针置空
pq->size = 0;//队列中元素个数置0
}
测试队列
测试队列的代码如下:
int main()
{
queue q;//定义队列
queueinit(&q);
queuepush(&q, 1);//入队列
queuepush(&q, 2);//入队列
queuepush(&q, 3);//入队列
queuepush(&q, 4);//入队列
queuepush(&q, 5);//入队列
queuedatatype frontdata= queuefront(&q);//获取队头数据
queuedatatype backdata= queueback(&q);//获取队尾数据
printf("frontdata=%d\n", frontdata);
printf("backdata=%d\n", backdata);
while (!queueempty(&q))
{
printf("%d ", queuefront(&q));
queuepop(&q);
}
return 0;
}
实现队列完整代码
queue.h文件(实现函数和类型的声明)
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
#include"vld.h"
typedef int queuedatatype;
//定义队列中的节点结构
typedef struct queuenode
{
queuedatatype data;//存放数据
struct queuenode* next;//存放写一个节点的地址
}queuenode;
//定义队列结构
typedef struct queue
{
queuenode* qhead;//队列的头节点
queuenode* qtail;//队列尾节点
int size;//当前队列中所存放的数据个数
}queue;
//队列初始化
void queueinit(queue* pq);
//入队列
void queuepush(queue* pq, queuedatatype data);
//取队头数据
queuedatatype queuefront(queue* pq);
//取队尾数据
queuedatatype queueback(queue* pq);
//删除队头数据
void queuepop(queue* pq);
//队列为空返回true
bool queueempty(queue* pq);
//返回队列中的数据个数
int queuesize(queue* pq);
//队列销毁
void queuedestroy(queue* pq);
test.c文件(实现队列的测试)
#define _CRT_SECURE_NO_WARNINGS 1
#include"queue.h"
int main()
{
queue q;//定义队列
queueinit(&q);
queuepush(&q, 1);//入队列
queuepush(&q, 2);//入队列
queuepush(&q, 3);//入队列
queuepush(&q, 4);//入队列
queuepush(&q, 5);//入队列
queuedatatype frontdata= queuefront(&q);//获取队头数据
queuedatatype backdata= queueback(&q);//获取队尾数据
printf("frontdata=%d\n", frontdata);
printf("backdata=%d\n", backdata);
while (!queueempty(&q))
{
printf("%d ", queuefront(&q));
queuepop(&q);
}
queuedestroy(&q);
return 0;
}
queue.c文件(实现队列函数的操作)
#define _CRT_SECURE_NO_WARNINGS 1
#include"queue.h"
//队列初始化
void queueinit(queue* pq)
{
assert(pq);
pq->qhead = NULL;//队头置NULL
pq->qtail = NULL;//队尾置NULL
pq->size = 0;//初始时队列中的元素个数为0
}
//入队列
void queuepush(queue* pq, queuedatatype data)
{
assert(pq);
//不管队列是空还是有数据都需要开一个节点的空间
queuenode* newnode = (queuenode*)malloc(sizeof(queuenode));
if (newnode == NULL)//开辟失败
{
perror("malloc");//开辟空间失败打印提示信息
return;
}
//将数据存放在新节点中的data中并将新节点的next置空
newnode->data = data;
newnode->next = NULL;
if (pq->qhead == NULL)//队列为空
{
pq->qhead = pq->qtail = newnode;
}
else//队列不为空
{
pq->qtail->next = newnode;//将当前尾节点的next指向下一个
pq->qtail = newnode;//尾节点指向新节点
}
pq->size++;//队列入一个数据,size+1
}
//取队头数据
queuedatatype queuefront(queue* pq)
{
assert(pq);
assert(pq->size > 0);//当前队列中还有数据,没有数据就报错
return pq->qhead->data;//有数据就直接返回队头数据
}
//取队尾数据
queuedatatype queueback(queue* pq)
{
assert(pq);
assert(pq->size > 0);//当前队列中是否还有数据,没有数据就报错
return pq->qtail->data;//有数据就直接返回队尾数据
}
//删除队头数据
void queuepop(queue* pq)
{
assert(pq);
assert(pq->size > 0);//当前队列中是否还有数据,没有数据就报错
queuenode* del = pq->qhead;//将要删除的节点的地址存下来
if (pq->qhead == pq->qtail)//此时只有一个节点
{
pq->qhead = pq->qtail = NULL;//只有一个节点就将指向队头和队尾的指针都置空
}
else//此时有多个节点
{
pq->qhead = pq->qhead->next;//让队头指针指向下一个节点
}
pq->size--;
free(del);//释放节点
}
//队列为空返回true
bool queueempty(queue* pq)
{
assert(pq);
//如果size为0就表示队列为空就返回true否则返回false
return pq->size == 0;
}
//返回队列中的数据个数
int queuesize(queue* pq)
{
assert(pq);
return pq->size;//直接返回size
}
//队列销毁
void queuedestroy(queue* pq)
{
assert(pq);
queuenode* del = pq->qhead;
while (del)//通过链表的遍历,依次释放节点
{
queuenode* next = del->next;//保存下一个节点
free(del);//释放当前节点
del = next;//让del指向下一个节点
}
pq->qhead = NULL;//队头指针置空
pq->qtail = NULL;//队尾指针置空
pq->size = 0;//队列中元素个数置0
}
测试结果如下:
栈和队列经典题目
我们有时还会使用一种队列叫循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。
Leetcode:设计循环队列
循环队列的实现首先最容易想到的就是用链表来实现,用链表来实现的话需要对细节把握的更好,本文将采用数组来实现循环队列。循环队列(底层使用数组)的关键就在于如何将其循环起来以及如何判断当前是空和满。下面采用的方式是通过多开辟一个空间以次来更好的判断当前循环队列是空还是满。当head和tail在同一位置就表示队列为空,当tail+1的位置为head就表示队列为满,但需要注意的时候要形成循环队列,这就需要对其进行取模运算来保证是循环的,其参考代码如下:
typedef struct
{
int*arr;//数组存储循环队列中的数据
int head;//循环队列中的头位置
int tail;//循环队列中的尾的后一个
int k;//循环队列的容量
} MyCircularQueue;
//创建循环队列并初始化
MyCircularQueue* myCircularQueueCreate(int k)
{
//动态创建循环队列将这个队列初始化并将队列的地址返回出去
MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//动态开辟数组,多开一个空间是为了更好的区分队列和队列满的情况
obj->arr=(int*)malloc(sizeof(int)*(k+1));
//将队列中的头和尾置0,当队列结构中的head和tail和相同时表示队列为空
obj->head=0;
obj->tail=0;
obj->k=k;//表示队列
return obj;//返回循环队列的地址
}
//往循环队列中插入数据,
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
assert(obj);
if((obj->tail+1)%(obj->k+1)==obj->head)//循环队列的tail的下一个位置为head就表示循环队列已满
{
return false;
}
else//不满就往tail位置插入数据,tail进行+1操作,tail进行
{
obj->arr[obj->tail]=value;
obj->tail++;
obj->tail=obj->tail%(obj->k+1);
return true;
}
}
//删除队头
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
assert(obj);
if(obj->head==obj->tail)//head等于tail表示队列为空
{
return false;//队列满了就返回false
}
else
{
//队列删除就是head进行++,但是需要不让其越界所以进行取模运算
obj->head++;
obj->head=(obj->head)%(obj->k+1);
return true;
}
}
//得到队头数据
int myCircularQueueFront(MyCircularQueue* obj)
{
assert(obj);
if(obj->head==obj->tail)
{
return -1;
}
else
{
return obj->arr[obj->head];
}
}
//得到队尾数据
int myCircularQueueRear(MyCircularQueue* obj)
{
assert(obj);
if(obj->head==obj->tail)
{
return -1;
}
else
{
return obj->arr[(obj->tail-1+obj->k+1)%(obj->k+1)];
}
}
//循环队列为空就返回返回true,否则返回false
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
assert(obj);
return obj->head==obj->tail;
}
//循环队列满了就返回true,否则返回false
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
assert(obj);
return (obj->tail+1)%(obj->k+1)==obj->head;
}
//循环队列的销毁
void myCircularQueueFree(MyCircularQueue* obj)
{
//首先销毁循环队列中的数组
free(obj->arr);
obj->arr=NULL;
obj->head=0;
obj->tail=0;
obj->k=0;
free(obj);
}
/**
* Your MyCircularQueue struct will be instantiated and called as such:
* MyCircularQueue* obj = myCircularQueueCreate(k);
* bool param_1 = myCircularQueueEnQueue(obj, value);
* bool param_2 = myCircularQueueDeQueue(obj);
* int param_3 = myCircularQueueFront(obj);
* int param_4 = myCircularQueueRear(obj);
* bool param_5 = myCircularQueueIsEmpty(obj);
* bool param_6 = myCircularQueueIsFull(obj);
* myCircularQueueFree(obj);
*/
这种括号匹配的问题需要借助栈来实现,这道题的核心思想就是左括号入栈,如果是右括号那就出栈,看当前的括号是否与这个有括号相匹配。需要特别注意的是,定义栈之后需要对栈进行初始化,入栈和出栈要注意当前栈是否为空否则就会出现问题,还有就是当已经确定括号不匹配或匹配时需要将栈进行销毁
typedef char Stackdatatype;
typedef struct Stack
{
Stackdatatype* arr;//用来存储数据
int top;//指向栈顶的下一个位置
int capacity;//表示arr数组的容量大小
}Stack;
//栈初始化
void Stackinit(Stack* ps)
{
assert(ps);
ps->arr = NULL;
ps->capacity = 0;
ps->top = 0;//指向栈顶元素中的下一个位置
}
//入栈
void StackPush(Stack* ps, Stackdatatype data)
{
assert(ps);
if (ps->capacity == ps->top)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//开辟空间
Stackdatatype* tmp = (Stackdatatype*)realloc(ps->arr, sizeof(Stackdatatype) * newcapacity);
if (tmp == NULL)
{
perror("realloc");
return;
}
//此时表示扩容成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
//进行入栈操作
ps->arr[ps->top++] = data;//往top位置上方,top再往后一个位置
}
//出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
//获取栈顶数据
Stackdatatype StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->arr[ps->top - 1];
}
//获取栈中的数据个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
//栈是否为空,为空就返回true,不为空就返回false
bool StackEmpty(Stack* ps)
{
return ps->top == 0;
}
//栈的销毁
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->arr);//释放动态开辟的空间
ps->capacity = 0;//容量置0
ps->top = 0;//top指向栈顶元素的下一个,而栈此时栈中没有数据所以将其也置0
}
bool isValid(char* s)
{
//定义一个栈
Stack st;
Stackinit(&st);
while(*s)
{
if(*s=='('||*s=='{'||*s=='[')//左括号入栈
{
//入栈操作
StackPush(&st,*s);
}
else
{
//出栈
if(StackEmpty(&st))//如果此时栈为空表示不匹配
{
StackDestroy(&st);//销毁栈
return false;
}
else//此时表示栈不为空
{
//出栈
char ret=StackTop(&st);
StackPop(&st);
if((*s==')'&&ret!='(')||(*s=='}'&&ret!='{')||(*s==']'&&ret!='['))
{
//销毁栈
StackDestroy(&st);
return false;
}
}
}
s++;
}
//此时表示已经遍历完成
//当栈中还有数据就表示不满足返回false,栈中无数据表示满足返回true
if(StackEmpty(&st))
{
//销毁栈
StackDestroy(&st);
return true;
}
else
{
//销毁栈
StackDestroy(&st);
return false;
}
}
使用两个栈来实现队列,根据栈的性质先进后出(后进先出),通过一个栈专门入数据,另一个栈专门出数据,这样便能实现一个队列。入数据的时候应该用栈1,出数据时如果栈2数据为空将栈1中的数据导入到栈2中。队列销毁需要删除队列结构中两个栈,然后再销毁动态开辟的队列结构。
typedef int Stackdatatype;
typedef struct Stack
{
Stackdatatype* arr;//用来存储数据
int top;//指向栈顶的下一个位置
int capacity;//表示arr数组的容量大小
}Stack;
//栈初始化
void Stackinit(Stack* ps)
{
assert(ps);
ps->arr = NULL;
ps->capacity = 0;
ps->top = 0;//指向栈顶元素中的下一个位置
}
//入栈
void StackPush(Stack* ps, Stackdatatype data)
{
assert(ps);
if (ps->capacity == ps->top)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//开辟空间
Stackdatatype* tmp = (Stackdatatype*)realloc(ps->arr, sizeof(Stackdatatype) * newcapacity);
if (tmp == NULL)
{
perror("realloc");
return;
}
//此时表示扩容成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
//进行入栈操作
ps->arr[ps->top++] = data;//往top位置上方,top再往后一个位置
}
//出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
//获取栈顶数据
Stackdatatype StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->arr[ps->top - 1];
}
//获取栈中的数据个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
//栈是否为空,为空就返回true,不为空就返回false
bool StackEmpty(Stack* ps)
{
return ps->top == 0;
}
//栈的销毁
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->arr);//释放动态开辟的空间
ps->capacity = 0;//容量置0
ps->top = 0;//top指向栈顶元素的下一个,而栈此时栈中没有数据所以将其也置0
}
//两个栈实现一个队列,一个栈入数据,另一个栈出数据
typedef struct
{
Stack st1;//这个栈入数据
Stack st2;//这个栈出数据
} MyQueue;
MyQueue* myQueueCreate()
{
//动态开辟MyQueue类型的空间
MyQueue*obj=(MyQueue*)malloc(sizeof(MyQueue));
//初始化栈
Stackinit(&obj->st1);
Stackinit(&obj->st2);
return obj;//返回创建的MyQueue空间的的地址
}
//往队列中插入数据
void myQueuePush(MyQueue* obj, int x)
{
//入st1这个栈
StackPush(&obj->st1,x);
}
//删除队头数据并返回队头数据
int myQueuePop(MyQueue* obj)
{
//获取队头数据
int ret=myQueuePeek(obj);
//删除队头数据,其实就是删除st2这个栈
StackPop(&obj->st2);
return ret;
}
//获取队头数据
int myQueuePeek(MyQueue* obj)
{
//判断st2这个栈是否为空,如果为空就需要将st1数据导入到st2中
if(StackEmpty(&obj->st2))
{
while(!StackEmpty(&obj->st1))
{
StackPush(&obj->st2,StackTop(&obj->st1));
StackPop(&obj->st1);
}
}
return StackTop(&obj->st2);
}
//队列为空就返回true,当两个栈都为空时队列就位空
bool myQueueEmpty(MyQueue* obj)
{
return StackEmpty(&obj->st1)&&StackEmpty(&obj->st2);
}
//释放动态开辟的空间
void myQueueFree(MyQueue* obj)
{
//首先释放两个栈的空间
StackDestroy(&obj->st1);
StackDestroy(&obj->st2);
//在释放动态开辟的myQueue空间
free(obj);
}
/**
* Your MyQueue struct will be instantiated and called as such:
* MyQueue* obj = myQueueCreate();
* myQueuePush(obj, x);
* int param_2 = myQueuePop(obj);
* int param_3 = myQueuePeek(obj);
* bool param_4 = myQueueEmpty(obj);
* myQueueFree(obj);
*/
使用两个队列实现栈需要知道队列的形状(先进先出或后进先出),这个最关键的就是Pop函数,返回队头的数据并且删除队头数据的这个Pop函数,这个Pop函数关键就是将不为空的那个队列的n-1的数据导入到另一个不为空的队列中,然后再进行后续操作,其参考代码如下:
typedef int queuedatatype;
//定义队列中的节点结构
typedef struct queuenode
{
queuedatatype data;//存放数据
struct queuenode* next;//存放写一个节点的地址
}queuenode;
//定义队列结构
typedef struct queue
{
queuenode* qhead;//队列的头节点
queuenode* qtail;//队列尾节点
int size;//当前队列中所存放的数据个数
}queue;
//队列初始化
void queueinit(queue* pq)
{
assert(pq);
pq->qhead = NULL;//队头置NULL
pq->qtail = NULL;//队尾置NULL
pq->size = 0;//初始时队列中的元素个数为0
}
//入队列
void queuepush(queue* pq, queuedatatype data)
{
assert(pq);
//不管队列是空还是有数据都需要开一个节点的空间
queuenode* newnode = (queuenode*)malloc(sizeof(queuenode));
if (newnode == NULL)//开辟失败
{
perror("malloc");//开辟空间失败打印提示信息
return;
}
//将数据存放在新节点中的data中并将新节点的next置空
newnode->data = data;
newnode->next = NULL;
if (pq->qhead == NULL)//队列为空
{
pq->qhead = pq->qtail = newnode;
}
else//队列不为空
{
pq->qtail->next = newnode;//将当前尾节点的next指向下一个
pq->qtail = newnode;//尾节点指向新节点
}
pq->size++;//队列入一个数据,size+1
}
//取队头数据
queuedatatype queuefront(queue* pq)
{
assert(pq);
assert(pq->size > 0);//当前队列中还有数据,没有数据就报错
return pq->qhead->data;//有数据就直接返回队头数据
}
//取队尾数据
queuedatatype queueback(queue* pq)
{
assert(pq);
assert(pq->size > 0);//当前队列中是否还有数据,没有数据就报错
return pq->qtail->data;//有数据就直接返回队尾数据
}
//删除队头数据
void queuepop(queue* pq)
{
assert(pq);
assert(pq->size > 0);//当前队列中是否还有数据,没有数据就报错
queuenode* del = pq->qhead;//将要删除的节点的地址存下来
if (pq->qhead == pq->qtail)//此时只有一个节点
{
pq->qhead = pq->qtail = NULL;//只有一个节点就将指向队头和队尾的指针都置空
}
else//此时有多个节点
{
pq->qhead = pq->qhead->next;//让队头指针指向下一个节点
}
pq->size--;
free(del);//释放节点
}
//队列为空返回true
bool queueempty(queue* pq)
{
assert(pq);
//如果size为0就表示队列为空就返回true否则返回false
return pq->size == 0;
}
//返回队列中的数据个数
int queuesize(queue* pq)
{
assert(pq);
return pq->size;//直接返回size
}
//队列销毁
void queuedestroy(queue* pq)
{
assert(pq);
queuenode* del = pq->qhead;
while (del)//通过链表的遍历,依次释放节点
{
queuenode* next = del->next;//保存下一个节点
free(del);//释放当前节点
del = next;//让del指向下一个节点
}
pq->qhead = NULL;//队头指针置空
pq->qtail = NULL;//队尾指针置空
pq->size = 0;//队列中元素个数置0
}
//两个队列实现一个栈
typedef struct
{
queue q1;
queue q2;
} MyStack;
//创建栈MyStack,并进行初始化,返回MyStack结构的地址
MyStack* myStackCreate()
{
//开辟MyStack的空间
MyStack*obj=(MyStack*)malloc(sizeof(MyStack));
//两个队列进行初试化
queueinit(&obj->q1);
queueinit(&obj->q2);
return obj;
}
//入栈
void myStackPush(MyStack* obj, int x)
{
//假设法,假设q1为不为队列
queue*qne=&obj->q1;
if(queueempty(&obj->q1))//如果q1为空,那就让qne等于q2,qne指向的是不为空队列那块空间
{
qne=&obj->q2;
}
queuepush(qne,x);
}
//
int myStackPop(MyStack* obj)
{
//假设法,假设q1不为空,q2为空
queue*qne=&obj->q1;
queue*qe=&obj->q2;
if(queueempty(&obj->q1))//如果q1为空就换一下
{
qne=&obj->q2;
qe=&obj->q1;
}
//导数据,不为空的队列数据个数为n,将不为空的队列的n-1个数据导入到为空的队列中
while(queuesize(qne)>1)
{
queuepush(qe,queuefront(qne));
queuepop(qne);
}
int ret=queuefront(qne);
queuepop(qne);
return ret;
}
//返回栈顶元素,由于一个栈总是空的
//队列的实现方式又有访问队尾数据,所以直接找出哪个不为空就直接掉用这个接口
int myStackTop(MyStack* obj)
{
//假设法,假设q1不为空,q2为空
queue*qne=&obj->q1;
if(queueempty(&obj->q1))//如果q1为空就换一下
{
qne=&obj->q2;
}
int ret=queueback(qne);
return ret;
}
//判断栈是否为看那个,为空就返回true
bool myStackEmpty(MyStack* obj)
{
return queueempty(&obj->q1)&&queueempty(&obj->q2);
}
void myStackFree(MyStack* obj)
{
//首先释放动态开辟的队列
queuedestroy(&obj->q1);
queuedestroy(&obj->q2);
//再释放动态开辟的栈myStack
free(obj);
}
总结:本文主要介绍了栈和队列这两种数据结构性质和特点,栈是后进先出,队列是先进先出。通过使用C语言实现了这两种数据结构,栈的底层是用数组,队列底层使用的是链表,最后还介绍了一些经典的栈和队列的题的思路以及代码的实现。感谢大家的观看,如有错误不足之处欢迎大家批评指针!!!