1.栈
1.1 栈的概念及结构
栈:一种特殊的线性表,只允许在固定的一段进行插入或删除元素操作。进行数据插入和删除操作
的一端叫做栈顶,另一端叫做栈底。
压栈:栈的插入操作
出栈:栈的删除操作(都在栈顶操作)
后进先出(last in first out)
1.2 栈的数组动态实现(静态类似,直接给定capacity,不实用)
栈的实现一般可以由链表或者数组进行实现,平时我们更提倡用数组进行实现,因为数组的尾插尾删效率更高,cpu缓存的利用率也更高。
下面就是由数组进行的对栈的实现:
//创建一个栈的结构体,其中top代表栈顶,capacity代表栈的容量,a代表栈的起始地址。
typedef int DataType;
typedef struct Stack
{
DataType* a;
int capacity;
int top;
}ST;
//这里实现的是栈的初始化,在初始化的时候,可以给这个栈开一点空间,这样一会扩容的realloc就可以只执行扩容的功能,而不用像顺序表一样,先执行开辟空间,再执行扩容。
void StackInit(ST* st)
{
assert(st);
st->a = (DataType*)malloc(sizeof(DataType) * 4);
if (st->a == NULL)
{
perror("malloc failed");
exit(-1);
}
st->capacity = 4;
st->top = 0;
}
top初始化可以为0,也可以为-1,
初始化为0时,代表top是栈顶元素的后一格。
为-1时,代表top是栈顶元素的那一格。
//栈的销毁
//栈的销毁非常简单,只需要先将这个栈使用的这块空间释放,再将指向这块空间的指针置空即可。
void StackDestroy(ST* st)
{
assert(st);
free(st->a);
st->a = NULL;
//同理,栈销毁了,容量(capacity)要归零,栈顶元素也要归零
st->capacity = 0;
st->top = 0;
}```
```c
//压栈
void StackPush(ST* st, DataType x)
{
assert(st);
//首先判断这个栈的空间是否已经满了,如果满了,就申请扩容
if (st->capacity == st->top)
{
DataType* New = (DataType*)realloc(st->a, st->capacity * 2 * sizeof(DataType));
if (New == NULL)
{
perror("realloc failed");
exit(-1);
}
st->a = New;
st->capacity = st->capacity * 2;
}
//空间足够了,就往栈顶元素放进一个x,然后把栈顶元素向后挪一位。
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;
}
//获取栈顶值
DataType StackTop(ST* st)
{
assert(st);
assert(!StackEmpty(st));
DataType Top = st->a[st->top -1];
return Top;
}
//获取栈的大小
int StackSize(ST* st)
{
assert(st);
return st->top;
}
2.队列
2.1 队列的概念及结构
队列:只允许在一段进行数据插入,在另一端进行数据删除的特殊线性表,队列
具有先进先出FIFO(first in first out)
入队列:进行插入操作的一段叫做队尾
出队列:进行删除操作的一段称为队头
2.2 队列的链表实现
队列的实现一般可以由链表或者数组进行实现,平时我们更提倡用链表进行实现,因为链表对队列的效果更好。
下面就是由链表进行的对栈的实现:
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
//给int起个新名字
typedef int QDataType;
//设置出队列结构体
typedef struct QueueNode
{
QDataType x;
struct QueueNode* next;
}QNode;
//队列的结构中,需要两个指针指向整个队列的首尾,用一个结构包含两个指针简单一点,
//还能加一个size用来计算队列的长度
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
//队列需要实现的接口
void QueueInit(Queue* qn);
void QueueDestroy(Queue* qn);
void QueuePush(Queue* qn, QDataType x);
void QueuePop(Queue* qn);
QDataType QueueFront(Queue* qn);
QDataType QueueBack(Queue* qn);
int QueueSize(Queue* qn);
bool QueueEmpty(Queue* qn);
//队列接口的实现
#include"Queue.h"
void QueueInit(Queue* qn)
{
assert(qn);
qn->head = NULL;
qn->tail = NULL;
qn->size = 0;
}
void QueueDestroy(Queue* qn)
{
assert(qn);
QNode* cur = qn->head;
while (cur)
{
QNode* new = cur;
cur = cur->next;
free(new);
}
qn->head = qn->tail = NULL;
qn->size = 0;
}
void QueuePush(Queue* qn, QDataType x)
{
assert(qn);
QNode* NewNode = (QNode*)malloc(sizeof(QNode));
if (NewNode == NULL)
{
perror("malloc failed");
exit(-1);
}
NewNode->next = NULL;
NewNode->x = x;
if (qn->head == NULL)
{
qn->head = qn->tail = NewNode;
}
else
{
qn->tail->next = NewNode;
qn->tail = NewNode;
}
qn->size++;
}
void QueuePop(Queue* qn)
{
assert(qn);
assert(!QueueEmpty(qn));
if (qn->head == qn->tail)
{
QNode* new = qn->head;
free(new);
qn->head = NULL;
qn->tail = NULL;
}
else
{
QNode* new = qn->head;
qn->head = qn->head->next;
free(new);
}
qn->size--;
}
QDataType QueueFront(Queue* qn)
{
assert(qn);
assert(!QueueEmpty(qn));
return qn->head->x;
}
QDataType QueueBack(Queue* qn)
{
assert(qn);
assert(!QueueEmpty(qn));
return qn->tail->x;
}
int QueueSize(Queue* qn)
{
assert(qn);
return qn->size;
}
bool QueueEmpty(Queue* qn)
{
assert(qn);
if (qn->head == NULL)
{
return true;
}
return false;
}
PS
学完栈和队列的感觉,
就是用顺序表或者链表实现结构,
如果是顺序表实现栈的话,只需要一个指针array就可以指向栈,这个时候,顺序表的长度,top之类的值都可以和那个指针array一起作为栈这个结构的成员变量。初始化的时候,
如果定义了一个栈的结构体,那么初始化只要malloc动态开辟一个空间给这个指针就可以了,
如果没有定义,初始化的时候,就还需要再malloc一个栈,在用栈中的成员变量指针array接收malloc的值。
用链表实现队列的时候,因为链表结构SL都是一个节点一个节点的,如果需要额外的指针去控制队列的话,就需要额外创建一个结构体S,将这些额外的指针,以及队列整体用到的变脸作为S的成员变量,同理,如果初始化有提前定义结构体,初始化的时候甚至都不需要malloc,直接在push的时候malloc SL的节点。