1.1 栈的概念及结构
- 栈:一种特殊的线性表,只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)原则。
- 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
- 出栈:栈的删除操作叫做出栈。出数据也在栈顶。
- 满足后进先出原则下,可以入一个数据出一个,也可以全部入完后再出,这就可以有多种出栈顺序。
1.2栈的实现
- 栈一般用链表或数组实现,用数组结构更优,尾部插入和删除数据方便,缓存命中率高。
栈的定义
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//用于存储栈数据的指针成员
int top;//栈顶
int capacity;//容量
}ST;
定义支持动态增长的栈
栈的初始化STInit
void STInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
return;//由于无返回值不能返回空
}
ps->capacity = 4;
ps->top = 0;//top是栈顶元素的下一个位置
//ps->top=-1;//top是栈顶元素当前位置
}
malloc函数从堆中分配指定字节数的空间来存储栈的数据,并进行容量初始化,注意top选择是0或1,都表示栈为空并且都先自增top然后把元素存入top指向位置,只是下标位置区别
判断栈是否为空STEmpty
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;//若为空返回非零结果,不为则返回0
}
压栈STPush
void STPush(ST* ps,STDataType x)
{
assert(ps);
//检查是否需要扩容
if (ps->top == ps->capacity)
{
STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType) * ps->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail");
return ;
}
else
{
ps->a = tmp;
ps->capacity *= 2;
}
}
ps->a[ps->top] = x;
ps->top++;
}
断言确保指针非空,避免造成非法访问等未定义行为。将传入的x元素放入栈顶位置,即ps->a数组的ps->top索引处,然后top自增1,表示栈顶移动到下一个位置,准备下次压入新元素。
出栈STPop
void STPop(ST* ps)
{
assert(ps);
//断言下标为0不再减
assert(!STEmpty(ps));
ps->top--;
}
注意判断栈不为空,不等于非零就是零,报错,避免数组下标越界访问。进行自减即可。
获取栈中有效元素个数STSize
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
top初始化为0,压栈后有效元素个数就等于top值;top初始化为-1时,压栈后有效元素个数是top+1
获取栈顶元素STDataType
STDataType STTop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));
return ps->a[ps->top - 1];
}
初始化为-1直接访问top即可
销毁栈STDestroy
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
整体代码
- 函数声明
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
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);
int STSize(ST* ps);
bool STEmpty(ST* ps);
STDataType STTop(ST* ps);
- 函数定义
#include"Stack.h"
void STInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
return;//由于无返回值不能返回空
}
ps->capacity = 4;
ps->top = 0;//top是栈顶元素的下一个位置
//ps->top=-1;//top是栈顶元素当前位置
}
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
//压栈
void STPush(ST* ps,STDataType x)
{
assert(ps);
//检查是否需要扩容
if (ps->top == ps->capacity)
{
STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType) * ps->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
else
{
ps->a = tmp;
ps->capacity *= 2;
}
}
ps->a[ps->top] = x;
ps->top++;
}
//判断栈是否为空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
//出栈
void STPop(ST* ps)
{
assert(ps);
//断言下标为0不再减
assert(!STEmpty(ps));
ps->top--;
}
//获取栈中有效元素个数
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
//获取栈顶元素
STDataType STTop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));
return ps->a[ps->top - 1];
}
- 主函数实现
#include"Stack.h"
int main()
{
ST st;
STInit(&st);
STPush(&st, 1);
STPush(&st, 2);
//printf("%d ", STTop(&st));//可改变栈的出栈顺序,遵循原则即可
//STPop(&st);
STPush(&st, 3);
STPush(&st, 4);
STPush(&st, 5);
while (!STEmpty(&st))//由于不知道top指向的栈顶当前位置还是下一位置不要直接访问top,将其封装为函数
{
printf("%d ", STTop(&st));
STPop(&st);
}
STDestroy(&st);
return 0;
}
2.1队列的概念及结构
- 队列也可以用数组和链表的结构实现,链表效率更优,数组头部出数据需要整体挪动数据效率低。
2.2队列的实现
队列的定义QDataType
typedef int QDatatype;
typedef struct QueueNode
{
struct QueueNode* next;
QDatatype data;
}QNode;
定义了一个QueueNode的结构体,用于表示队列中的节点
//对常用的多个数据进行封装,便于传参和修改
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
定义了一个名为Queue的结构体,表示整个队列
数据封装中设置tail指针为了方便找尾,size为了方便确认元素个数两者关系:Queue结构体通过head和tail指针来管理一系列QueueNode结构体组成的链式队列。QueueNode结构体通过next指针相互链接,形成队列的链式结构,而 Queue结构体掌控着这个结构体两端指针以及队列大小信息。
队列的初始化QueueInit
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
将头尾指针都置空,元素个数为0,明确初始状态。
队列的销毁QueueDestroy
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
提前保存cur的next指针,避免free后找不到。
队尾入队列QueuePush
void QueuePush(Queue* pq, QDatatype x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
//尾插next置空
newnode->next = NULL;
//因为没有哨兵位节点,需要判断第一次尾插时头节点是否为空
if (pq->head == NULL)
{
//初始化时tail和head都为空
assert(pq->tail == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
用malloc创建新节点后赋值有效信息,并就是否有哨兵位头节点进行判断插入。无的情况第一次插入要给head和tail指针赋值,有的情况直接进行插入链接即可。
队头出队列QueuePop
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode*next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
要判断删除的节点是否为尾节点,若是直接将头尾指针置空即可,不是的话改变头指针指向并将size–。
检查队列是否为空QueueEmpty
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
为空则返回非零值,非空返回0
获取队列中有效元素个数QueueSize
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
由于我们在Queue结构体中设置了size参数,避免了遍历整个队列来获取个数。
获取队列头部元素QueueFront
QDatatype QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
要多进行一次断言判断队列是否为空,避免造成非法访问。
获取队列尾部元素QueueBack
QDatatype QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
整体代码
- 函数声明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int QDatatype;
typedef struct QueueNode
{
struct QueueNode* next;
QDatatype data;
}QNode;
//对常用的多个数据进行封装,便于传参和修改
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDatatype x);
void QueuePop(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
QDatatype QueueFront(Queue* pq);
QDatatype QueueBack(Queue* pq);
- 函数的定义
#include"Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDatatype x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
//尾插next置空
newnode->next = NULL;
//因为没有哨兵位节点,需要判断第一次尾插时头节点是否为空
if (pq->head == NULL)
{
//初始化时tail和head都为空
assert(pq->tail == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode*next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
QDatatype QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDatatype QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
- 主函数实现
#include"Queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
QueuePush(&q, 6);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
QueueDestroy(&q);
return 0;
}
结语
- 感谢支持,祝大家新年快乐,共同进步。