作者:江不平
博客:江不平的博客
学如逆水行舟,不进则退
欢迎关注点赞收藏⭐️留言
❀本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在一端进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。也叫先进后出(Last Out First In)
因为是特殊的线性表,所以有一些独特的接口,比如获取栈顶元素,判断栈是否为空等,下面我们来实现一下
栈结构,我们都在栈顶进行插入删除操作,类似尾删尾插,在这方面数组比较优,所以我们用数组来实现
栈有静态的和动态的
静态栈
#define n 10
typedef int stdatatype;
typedef struct stack
{
stdatatype a[n];
int top;//最后一个元素的下一个位置,就是size,
}st;
动态栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
栈的初始化我们可以直接创建空间,也可以赋个空指针给它,将其他数据置零。
这里我置零,后面再进行扩容
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;//如果表示的是最后一个元素的位置,就初始化为-1
ps->capacity = 0;
}
后面我们都按top表示最后一个元素位置进行实现
插入数据与顺序表相似
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType)*newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));//这里要记得把StackEmpty放到前面去
ps->top--;
}
StackEmpty是常用的函数,在其他函数调用前要往前放,早点定义
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
我们在判断栈是否为空的函数时使用了布尔类型,如果栈为空则返回true,不是空则返回false。使用布尔类型,我们得先包含头文件
获取栈顶元素函数与出栈函数不同。出栈函数是将栈空间中栈顶的元素弹出,也就是删除销毁,获取栈顶函数是将栈顶空间保存的数据取出,并不会改变栈空间的数据存储。因此我们只需要将顺序表中top所指向的上一个空间的位置存储的数据返回即可。
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
top的值就是size的值
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
栈的销毁
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
- 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
- 队列的元素遵守先进先出FIFO(First In First Out)
进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列的实现可以采用顺序表或者链表,由于队列需要使用头删,所以顺序表实现并不划算,原因在于顺序表进行头删的时候,元素需要移动覆盖队头的数据,增加了时间复杂度。用链表比较好,所以一般采用链表形式
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
//int size;
QNode* head;
QNode* tail;
}Queue;
队列最重要的就是队尾入数据队头出数据,为了方便操作,我们定义一头一尾两个指针
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;//写成带哨兵位的也可,但没必要,如果写的话,malloc一个结点给head和tail就可
}
两种情况,一种什么数据都没有,一种有数据
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//放完数据后记得更新head和tail
if (pq->tail == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
三种情况:1.空队列2.只剩一个结点时再进行pop会导致tail为野指针3.多个结点,正常删除
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));//考虑空队列
if (pq->head->next == NULL)//只剩一个结点情况
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else//多个结点情况
{
//先保存再free
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
记得调用empty时,将它往前挪,不然会报错:函数未定义
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head == NULL;//head和tail一个为空队列就为空了,判断一个即可
}
一般分为取队头的数据和取队尾的数据,取中间的对于队列的特点来说没啥意义了
取队头的数据
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;
}
元素的统计,追求效率最好可以在队列的定义时就增加变量size,进行插入删除时size加减,我们也可以遍历统计,但是效率不高,这里给出遍历的代码
int QueueSize(Queue* pq)
{
assert(pq);
int size = 0;
QNode* cur = pq->head;
while (cur)
{
size++;//如果觉得遍历麻烦,可以在队列结构体中定义一个size
cur = cur->next;
}
return size;
}
对于链表的删除插入的操作,我们之前就说过如果怕难以连接成新的链表结构,我们可以先将数据用新的变量存储起来,再进行连接,不然就要好好考虑连接的顺序,避免出错,我建议存储起来。
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;
}