目录
一.栈
1.概念
栈,作为一种特殊的线性表,只允许在其固定的一端进行插入和删除的操作,这一端称为栈顶,另一端则称为栈底。
栈中的数据元素遵循后进先出原则(Last In First Out),即后进入的数据元素先出去。这一特点可以类比于现实中的弹夹、羽毛球桶(只有一个口的)等。
栈的插入操作叫做进栈/压栈/入栈,将数据插入到栈顶。栈的删除操作叫做出栈,将栈顶的数据删除。
2.实现
2.1栈的结构体定义
栈,作为一种特殊的线性表,对栈(Stack)的结构体的定义完全可以参考线性表的构成。链表和数组实现都可以,这里就使用了动态顺序表的构造方法。关于动态顺序表详解可以查看我的这篇博客:【C语言】顺序表(原理+实现)-CSDN博客
//定义存储数据的类型
typedef int STDataType;
//定义栈
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
2.2栈的创建和销毁
创建栈时需要注意一点,top应该初始化为0吗?如果初始化为0,那么插入一个元素后,top变为1,指向的可不是栈顶元素,而是指向栈顶元素的下一个元素。如果你要想让top指向栈顶元素,那么top就必须初始化为-1.不过此刻为了与之前实现顺序表契合,选择了前者。
//创建和销毁
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->capacity = pst->top = 0;
}
2.3入栈和出栈
//入栈和出栈
void STPush(ST* pst, STDataType x)
{
assert(pst);
//扩容
if (pst->capacity == pst->top)
{
int newcapacity = (pst->capacity == 0) ? 4 : (pst->capacity) * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
2.4取栈顶数据
这里必须注意要和前文初始化Stack时一致,前文初始化top为0,top则指向栈顶元素的下一个元素。因此在这里取栈顶元素时,需要取top-1下标对应的值。
//取栈顶数据
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
2.5判空、取元素个数
说到底,理解到top所代表的意义,后面的这些问题都能迎刃而解。
//判空
bool STEmpty(ST* pst)
{
assert(pst);
return (pst->top == 0);
}
//获取数据个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
二.队列
1.概念
队列和栈一样,只允许在一端进行数据插入,但不同的是在删除数据上,栈只能从数据插入的这一端进行删除,而队列只能从另一端进行删除。如此,队列进行插入操作的一端称为队尾,进行删除操作的一端称为队头。
队列遵循先进先出原则(First In First Out),就像排队一样,先排队的人先得到服务。
2.实现
2.1队列的结构体定义
队列即可以用数组实现也可以用链表实现。但使用链表的结构更优秀一些,原因在于如果使用数组,那么在对队头的处理就比较麻烦,删除下标为0的元素后,还需要整体前移,效率比较低。因此,使用链表来实现,如下定义:
//定义数据类型
typedef int QDatatype;
//定义链表的节点
typedef struct QueueNode
{
struct QueueNode* next;
QDatatype val;
}QNode;
//定义队列
//便于函数内修改指针phead,ptail,同时便于传多个参数
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
由于在构造后续的函数中,需要频繁修改phead和ptail,因此要在函数内修改指针变量,函数内改变参数指针的方式:1:传二级指针 2:传结构体指针,结构体中包含指针,通过结构体来改变里面的指针
因此,使用结构体传phead和ptail避免使用二级指针,也避免了传多个参数,直接传结构体指针即可。
2.2队列的初始化和销毁
//初始化和销毁
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
//assert(pq->phead);
QNode* pcur = pq->phead;
while (pcur)
{
QNode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
2.3队列的插入和删除
队列的插入和删除可以参考链表的尾插和头删。不过值得注意的是,在删除的过程中,必须多考虑一个只有一个节点的情况,如果按照多个节点的写法取处理一个节点,会把phead置为NULL,而ptail不进行操作,这样ptail就成为了一个野指针,在后续再进行插入操作中这是很危险的行为,因此一定要考虑这种特殊情况进行处理,在一个节点删除时,phead和ptail都置为NULL。
//插入
void QueuePush(Queue* pq, QDatatype x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->val = x;
newnode->next = NULL;
//空队列
if (pq->ptail == NULL)
{
pq->ptail = pq->phead = newnode;
}
//非空队列
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
//删除
void QueuePop(Queue* pq)
{
assert(pq);
//assert(pq->phead);//报错不清晰
assert(pq->size != 0);
//一个节点
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
//多个节点
else
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;
}
2.4队列取头、尾值
//取头,取尾数值
QDatatype QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
QDatatype QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
2.5判空、取元素个数
在Queue的定义中加入size这个概念,在此就会很方便计算。
//判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
//元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}