文章目录
栈
栈的概念和结构
- 栈是一种特殊的线性表,只允许在固定的一端进行插入和删除元素的操作,其中进行操作的一端叫做栈顶,另一端叫做栈底
- 栈中的数据元素遵守**后进先出(先进后出)**的原则
- 栈的插入操作叫做压栈(入栈)(进栈)
- 栈的删除操作叫做出栈,出数据在栈顶操作
栈的实现
栈的实现一般使用数组或者链表实现,相对来说数组的结构实现会更加优化一点,因为数组在尾上插入数据的代价比较小
定义一个整形数组类型的栈
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);
}
队列
队列的概念和结构
- 队列只允许在一端进行插入数据操作,另一端进行删除数据操作的,是一种特殊的线性表
- 队列遵守先进先出 后进后出的原则
- 进行插入操作的一端叫做队尾,进行删除操作的一端叫做队头
队列的实现
队列的定义
相对于栈而言,使用链表实现队列更优
先定义一个节点结构再定义一个队列结构,节点结构里包括数据和指向下一个节点的指针,队列结构里包括队列的队头和队尾以及队列的长度
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);
}
OJ题
力扣20:有效的括号
思路:利用栈 先进后出的原则,遍历字符串,当遇到左括号时将它存放进数组里,如果一旦遇到右括号就将数组里的左括号拿出来一一比较,一旦发现不匹配就返回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:用队列实现栈
思路:利用两个队列,始终保持一个为空,需要出数据的时候把除了队尾外的数据导入空的队列
由于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:用栈实现队列
思路:和上一道题差不多,需要用到两个栈来实现,一个栈用来插入数据,一个栈用来删除数据
由于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:设计循环队列
思路:因为队列的长度是固定的,所以使用数组结构解决该组。创建一个长度为队列长度+1的数组,可以利用两个下标,一个头下标,一个尾下标,头下标为队头的下标,尾下标为队尾下标+1的位置。
当头下标和尾下标相等时,说明该队列为空
当尾下标加一等于头下标时,说明该队列满了
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题时需要注意的细节比较多,也比较复杂,要更加耐心细心的去思考,多画图去解决问题
以上就是个人对栈和队列这一节的知识点总结,如果不足 希望指出