队列我们用的是链表实现,
typedef int QDataType;
// 链式结构:表示队列
typedef struct QListNode
{
//创建一个指向结构体的指针
struct QListNode* _next;
//data代表链表中存储的数据
QDataType _data;
}QNode;
// 队列的结构
typedef struct Queue
{
//创建一个指向另外一个结构体的指针,用做队列的头
QNode* _front;
//创建一个指向另外一个结构体的指针,用做队列的尾
QNode* _rear;
//size用来记录存储了多少个数据
int size;
}Queue;
首先初始化队列
void QueueInit(Queue* q)
{
//判断一下传过来的参数不能为空
assert(q);
q->size = 0;
//结构体的指针都先初始化为指向空
q->_front = q->_rear = NULL;
}
队列的尾插
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
//尾插数据的时候,会有两种情况,一种是队列为空的时候,和有数据的时候
//队列的为空的时候,需要把头和尾都向一个指针
//后面继续增加的数据,每增加一个rear指针都向最后一个数据
if(newnode == NULL)
{
perror("Queuepush malloc fail");
exit(-1);
}
else
{
newnode->_data = data;
newnode->_next = NULL;
}
if(q->_rear == NULL)
{
q->_rear = q->_front = newnode;
}
else
{
q->_rear->_next = newnode;
q->_rear = newnode;
}
q->size++;
}
队列是先进先出的
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
//QueueEmpty函数是用来判断这个链表是否为空的,如果为空就没有数据可以获取
assert(!QueueEmpty(q));
//直接返回队列头的数据
return q->_front->_data;
}
由于队列是先进先出的,队头的数据出了之后,就需要同时删除,不然下一个数据是无法出的
//删除队头的数据
void QueuePop(Queue* q)
{
assert(q);
//需要先判断一下这个队更是否有数据
assert(!QueueEmpty(q));
//数据的next是空的话,就只能删除自己了
if(q->_front->_next == NULL)
{
free(q->_front);
q->_rear = q->_front = NULL;
}
//把队头的指针给到另外一个临时的指针指向,队头就指向next的next
else
{
QNode* del = q->_front;
q->_front = del->_next;
free(del);
del = NULL;
}
q->size--;
}
获取队尾的数据
QDataType QueueBack(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
//在获取队尾的数据前需要先判断一下队列是否有空,有数据就直接返回队尾的数据即可
return q->_rear->_data;
}
获取队列的数据个数,由于我们在创建结构体的时候,有创建一个变量记录了队列的插入和删除
直接返回这个size就是队列的数据个数了
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
检查队列是否为空
//如果队列为空 返回小于零的数,如果队列不为空返回零
int QueueEmpty(Queue* q)
{
assert(q);
if(q->_front == NULL && q->_rear == NULL)
{
return -1;
}
else
{
return 0;
}
}
销毁一个队列的步骤
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->_front;
//由于队列我们是用链表实现的,所以需要一个一个遍历链表,一个一个free掉
while(cur)
{
QNode* del = cur;
cur = cur->_next;
free(del);
}
//最后再free掉头指针和尾指针
q->_rear = q->_front = NULL;
}
这就是一个队列的函数接口和代码实现
/ 栈的接口 和代码实现 /
栈是跟队列相反的,栈是先入后出,后入先出的,栈的实现上会比队列来的更简单,栈我们选择的是数组来实现,结构体里面包含了一个指向数组的指针,还有一个记录栈顶位置的top变量,还有一个记录数组大小的capacity,如果满了,随时扩容
typedef int STLDatatype;
typedef struct Stack
{
//栈我们选择的是数组实现
STLDatatype* a;
//记录栈顶的位置,方便我们随时获取栈顶的数据
int top;
//capacity记录的是动态数组的容量,不够了随时扩容
int capacity;
}ST;
栈的初始化,把top和capacity初始化为零,指针指向空
//初始化栈
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
栈每次插入都在栈顶上插入,出也是先出栈顶的数据
//在栈顶插入数据
void StackPush(ST* ps,STLDatatype x)
{
assert(ps);
//由于我们在初始化栈的时候,没有给初始化空间,所以我们需要先判断一下,如果没有空间,创建空间给 到栈
if(ps->top == ps->capacity)
{
int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STLDatatype* tmp=(STLDatatype*)realloc(ps->a,NewCapacity*(sizeof(STLDatatype)));
if(tmp == NULL)
{
perror("Stack Push realloc file");
exit(-1);
}
ps->a = tmp;
ps->capacity = NewCapacity;
}
ps->a[ps->top] = x;
(ps->top)++;
}
由于我们用的是数组来实现栈的,所以在删除栈顶的数据会比较简单
//删除栈顶的数据
void StackPop(ST* ps)
{
assert(ps);
//在删除的时候,要先判断一下是否为空,空的话,就没有删除了
assert(!StackEmpty(ps));
//栈顶有数据的话,记录栈顶的位置的top减减,就代表删除成功了
--(ps->top);
}
出栈也是比较简单的
STLDatatype StackTop(ST* ps)
{
assert(ps);
//出栈前先判断,栈是否有数据
assert(!StackEmpty(ps));
//有数据,直接返回栈顶的数据,由于数组是下标零开始的,所以在返回栈顶的时候,需要减1.
return ps->a[ps->top-1];
}
判断栈是否为空的,直接判断记录栈顶数据的top是否为空就行
bool StackEmpty(ST* ps)
{
assert(ps);
//如果top为零,代表这个栈还没有数据
return ps->top == 0;
}
获取栈顶的数据个数,也是返回top即可,top记录着栈顶的数据,同时也记录着栈的个数
//获取栈的大小
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
删除栈,栈是用数组实现的,所以直接free掉栈中的数组a所指向的地址就可以了
void StackDestory(ST* ps)
{
assert(ps);
free(ps->a);
ps->top = ps->capacity = 0;
ps->a = NULL;
}
/// 循环队列的实现 /
循环队列就跟循环链表类似 只不过他是固定长度的,就是一个固定长度的环那样,这里我用数组方法来实现,循环队列
先声明一个结构体
typedef struct {
//声明一个指向int数组的指针a
int* a;
//声明一个记录头的变量 front
int front;
//声明一个记录尾的变量 back
int back;
//声明一个记录这个循环队列的长度
int N;
} MyCircularQueue;
循环队列的创建
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a = (int*)malloc(sizeof(int)*(k+1));
//刚开始头和尾都是零
obj->front = obj->back = 0;
//我们创建的数组大小,则是循环队列的长度加1
obj->N = k+1;
return obj;
}
我们创建的数组长度是比实际存储数据的长度是多了一个的,我们的实现思想是,back每次在插入数据的时候都会指向尾的下一个位置,
如果判断这个循环链表为空呢,非常简单
//front 等于 back的时候就代表这个循环队列是空的
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->back;
}
如果判断一个循环队列满了呢
//我们只需要判断,back加1 是否等于front如果相等就代表这个数组满了
//这里我们分两种情况,一种是back在最尾的时候,加1是会越界的,所以我们需要跟我们的数组总大小模一下
//比如back是4+1 我们的数组总长度也是5,一模,back就变成了零了,就回到刚开始的位置
bool myCircularQueueIsFull(MyCircularQueue* obj) {
//这里需要注意back在数组最尾的时候,加1是会越界的,为了让back回到开头,需要跟数组总长度取模就能回到开头
return obj->front == (obj->back+1) % obj->N;
}
插入数据,我们需要考虑满的情况,和不满的情况
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//数据满了 返回false
if(myCircularQueueIsFull(obj))
{
return false;
}
//数据没满 在back的位置插入数据 back再加加
obj->a[obj->back] = value;
obj->back++;
//还有一种情况就是front在中间 back在尾,back加加会越界,所以我们需要对数组长度进行取模
obj->back %= obj->N;
return true;
}
删除循环队列中的数据
//删除队列中的数据,有两种情况 一种是空的,一种是有数据的,空的话直接返回false,有数据就front加加
//跳过有效数据范围,就是删除成功了
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//首先需要先判断 这个队列是否为空
if(myCircularQueueIsEmpty(obj))
{
return false;
}
obj->front++;
//这一步是为了防止front加加的时候 超过数组的长度
obj->front %= obj->N;
return true;
}
从队列的头获取数据
int myCircularQueueFront(MyCircularQueue* obj) {
//判断队列是否为空,为空返回小于零的数
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
//不为空 返回队头的数据
return obj->a[obj->front];
}
如果获取队尾的数据,由于我们的back是指向尾的下一个,所以back-1就是尾的数据,有一种情况我们需要处理一下,back刚好是在数组头的位置,我们减一就越界了,所以需要对他处理一下,如果back-1+数组的长度 再去跟数组的长度进行取模 就不会超出这个数组的长度,back-1也会回到尾数据的位置
int myCircularQueueRear(MyCircularQueue* obj) {
//队列为空 返回小于零的数
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
//这一步是为了防止back在数组第一个位置的时候减一变成负数
return obj->a[(obj->back-1+obj->N)%obj->N];
}
}
翻译这个循环队列,我们需要释放两次,一个是数组的空间,还有结构体的空间
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}