1. 栈
1.1 概念与结构
栈:⼀种特殊的线性表,其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶,另⼀端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插⼊操作叫做进栈/压栈/⼊栈,⼊数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
栈的底层结构
理论来说栈的底层结构可以是链表可以是数组,但是数组更好实现,因为栈只在栈底插入数组,链表尾插要找尾结,时间复杂度为O(N),而数组尾插时间复杂度为O(1)
1.2 栈的实现
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
初始化栈
void STInit(ST* ps);
销毁栈
void STDestroy(ST* ps);
⼊栈
void STPush(ST* ps, STDataType x);
出栈
void STPop(ST* ps);
取栈顶元素
STDataType STTop(ST* ps);
获取栈中有效元素个数
int STSize(ST* ps);
栈是否为空
bool STEmpty(ST* ps);
编写代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"
void STInitialise(ST* ps)
{
assert(ps);
ps->arr = NULL;
ps->capacity = ps->top = 0;
}
void STDestroy(ST* ps)
{
assert(ps);
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//判断空间是否充足
if (ps->top == ps->capacity)
{
//增容//0*2 = 0
//若capacity为0,给个默认值,否则×2倍
int NewCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* tmp = (STDataType*)realloc(ps->arr, NewCapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = NewCapacity;
}
ps->arr[ps->top++] = x;
}
bool StackEmpty(ST* ps)
{
if (ps->top)
{
return true;
}
return false;
}
void StackPop(ST* ps)
{
assert(ps);
assert(StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(StackEmpty(ps));
return ps->arr[ps->top - 1];
}
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
代码和顺序表的内容基本大差不差,非常好实现
2. 队列
2.1 概念与结构
概念:只允许在⼀端进⾏插⼊数据操作,在另⼀端进⾏删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
⼊队列:进⾏插⼊操作的⼀端称为队尾
出队列:进⾏删除操作的⼀端称为队头
如同我们上学的时候如同,后来的人排队尾,只有队头的人才能走。禁止插队的说
2.2 队列的实现
队列的底层结构
队列的底层结构当然也是数组和链表都行,但是我们更倾向链表。队列队头出数据,用数组去实现的话所有数据都左移一位。链表直接头删就行了。队列队尾插入数据用数组直接插入,而链表要去找尾结点。
当然这里就很巧妙,我直接标识一下尾结点就像头结点一样不就行了吗?但是你队头出数据时用数组有什么巧妙的方法吗,博主的话是想不到。
typedef int QDataType;
队列结点结构
typedef struct QueueNode
{
int val;
struct QueueNode* next;
}QNode;
队列结构
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}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);
队列判空
bool QueueEmpty(Queue* pq);
队列有效元素个数
int QueueSize(Queue* pq);
编写代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void QueueInitialise(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
if (pq->phead == NULL && pq->ptail == NULL)
{
return false;
}
return true;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
if (NewNode == NULL)
{
perror("malloc fail!");
exit(1);
}
NewNode->data = x;
NewNode->next = NULL;
if (pq->phead == NULL)
{
pq->phead = pq->ptail = NewNode;
}
else
{
pq->ptail->next = NewNode;
pq->ptail = NewNode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
QueueNode* Next = pq->phead->next;
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
free(pq->phead);
pq->phead = NULL;
pq->phead = Next;
pq->size--;
}
当队列只有一个结点的情况下我们free队头,此时队头和队尾指向相同,那队头就变成野指针了,所以要判断只有一个结点的情况
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
return pq->ptail->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
/*size_t size = 0;
QueueNode* pcur = pq->phead;
while (pcur)
{
size++;
pcur = pcur->next;
}*/
return pq->size;
}
如果不用实现队列有效元素个数这个接口的话就不用在队列中定义size了,如果是链表的话我们当然可以去遍历然后拿计数器++求元素个数。但在队列这里就不规范了,因为队列不能遍历(
while(pcur)),好伐我感觉这里还是比较灵活。我觉得主要原因是因为有个size我们就可以直接返回了。遍历复杂度为O(N)不够高效
void QueueDestroy(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
虽然说队列不能遍历,但是不遍历怎么去销毁每个结点感觉还是有点矛盾的