栈和队列是常见的数据结构,前面也说过它们都是线性表,只不过比较特殊罢了,今天我们就来讲讲它们。
栈(Stack)
栈的概念及结构
栈(Stack):只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。它遵循先进后出LIFO(Last In First Out)的原则,你就可以把它想象成汉诺斯塔,只能从最上面拿和放。
而栈通常来说可以用数组或链表实现,但是相对而言数组的结构实现更优一些,因为数组在尾上插入数据的代价比较小,所以这里我们使用数组来实现一个基本的栈。
主要操作
栈的主要操作包括:
1.初始化栈
2.销毁栈
3.入栈
4.出栈
5.取栈顶元素6.判断栈是否为空
7.获取栈中有效元素个数
下面我们就用代码来实现这些操作。
代码实现
头文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
//方便修改数据类型
typedef int STDataType;
typedef struct Stack
{
STDataType* a; //数组实现栈
int top; //栈顶
int capacity; //最大容量
}Stack;
//初始化及销毁
void StackInit(Stack* st);
void StackDestroy(Stack* st);
//进栈,出栈
void StackPush(Stack* st, STDataType x);
void StackPop(Stack* st);
//取栈顶元素
STDataType StackTop(Stack* st);
//判断栈是否为空
bool StackEmpty(Stack* st);
//获取栈中有效元素个数
int StackSize(Stack* st);
函数实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
//初始化栈
void StackInit(Stack* st)
{
assert(st);
st->a = NULL;
st->top = 0;
st->capacity = 0;
}
//销毁栈
void StackDestroy(Stack* st)
{
assert(st);
free(st->a);
st->a = NULL;
st->top = st->capacity = 0;
}
//进栈
void StackPush(Stack* st, STDataType x)
{
assert(st);
//扩容
if (st->top == st->capacity)
{
//如果容量为0,就赋值为4,否则每次栈满就扩容为原来的2倍
int newcapacity = st->capacity == 0 ? 4 : st->capacity * 2;
//防止扩容失败,数据丢失
STDataType* tmp = (STDataType*)realloc(st->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail!");
return;
}
//将扩容后的数组以及容量赋值给我们创建栈
st->capacity = newcapacity;
st->a = tmp;
}
st->a[st->top] = x;
st->top++;
}
//出栈
void StackPop(Stack* st)
{
assert(st);
//断言防止栈为空还取出栈
assert(!(StackEmpty(st)));
st->top--;
}
//取栈顶元素
STDataType StackTop(Stack* st)
{
assert(st);
assert(!StackEmpty(st));
return st->a[st->top - 1];
}
//判断栈是否为空
bool StackEmpty(Stack* st)
{
assert(st);
return st->top == 0;
}
//获取栈中有效元素个数
int StackSize(Stack* st)
{
assert(st);
return st->top;
}
队列
队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。队列具有先进先出 FIFO(First In First Out) 入队列,进行插入操作的一端称为队尾出队列,进行删除操作的一端称为队头,就和我们排队吃饭一样,先去的先吃到饭,后去的就等着(前提是不要插队)。
和栈一样,队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低(要将后面的所有数据往前挪动),所以这里我们使用链表的结构。
主要操作
队列的主要操作包括:
1.初始化队列
2.销毁队列
3.入队列
4.出队列
5.获取队列头部元素6.获取队列尾部元素
7.获取队列中有效元素个数
8.判断队列是否为空
下面我们就用代码来实现这些操作。
代码实现
头文件:
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
//方便当我们使用不同的数据类型时的修改
typedef int QDataType;
//节点
typedef struct QueueNode
{
QDataType a;
struct QueueNode* next;
}QNode;
//队列的结构
typedef struct Queue
{
QNode* phead;//队头
QNode* ptail;//队尾
int size; //有效数据个数
}Queue;
// 初始化队列
void QueueInit(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType x);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 判断队列是否为空
bool QueueEmpty(Queue* q);
上面不难看出,我们是直接定义了一个队列结构,将队头、队尾和队列有效元素个数进行了封装,这样操作的目的是因为:在这里我们使用的是链表,所以在函数参数设计时不仅要传递二级指针,而且在入队操作时,我们还要将链表遍历一遍找到队尾才能进行入队操作,而这样不仅影响效率还麻烦,所以这里我们直接自己定义一个队列结构,来方便我们找队尾以及进行其他操作。
函数实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
// 初始化队列
void QueueInit(Queue* q)
{
assert(q);
//开始时phead和ptail都指向空
q->phead = NULL;
q->ptail = NULL;
q->size = 0;
}
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* ner = q->phead;
//遍历队列
while (ner)
{
//保存删除节点的下一个节点
QNode* next = ner->next;
free(ner);
ner = next;
}
q->phead = NULL;
q->ptail = NULL;
q->size = 0;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType x)
{
assert(q);
//创建节点进行入队
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail!");
return;
}
newnode->a = x;
newnode->next = NULL;
//如果是第一个节点,那么phead和ptail指向同一个位置
//否则直接在ptail后面进行链接进行
if(q->ptail)
{
q->ptail->next = newnode;
q->ptail = newnode;
}
else
{
q->ptail = q->phead = newnode;
}
//不要忘了size++
q->size++;
}
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
//判断队列是否为空,空队列就不能进行出队操作
assert(!QueueEmpty(q));
if (q->phead == q->ptail)
{
free(q->phead);
q->phead = q->ptail = NULL;
}
//不为空则释放头节点
else
{
//保存phead的下一个节点位置,防止释放完找不到
QNode* next = q->phead->next;
free(q->phead);
q->phead = next;
}
q->size--;
}
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->phead != NULL);
return q->phead->a;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->ptail != NULL);
return q->ptail->a;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
// 判断队列是否为空
bool QueueEmpty(Queue* q)
{
assert(q);
return q->phead == NULL;
}
这就是栈和队列的一些基本讲解及代码实现。