队列,栈,循环队列的实现

队列我们用的是链表实现,

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;
}

这就是一个队列的函数接口和代码实现

cc25c57109534366a78fd4ee377fd7f4.png

/  栈的接口 和代码实现 /

栈是跟队列相反的,栈是先入后出,后入先出的,栈的实现上会比队列来的更简单,栈我们选择的是数组来实现,结构体里面包含了一个指向数组的指针,还有一个记录栈顶位置的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;
}

fdf45508dad6415284d772a203c9f5cc.png

///  循环队列的实现 /

循环队列就跟循环链表类似 只不过他是固定长度的,就是一个固定长度的环那样,这里我用数组方法来实现,循环队列

先声明一个结构体


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每次在插入数据的时候都会指向尾的下一个位置,

dfdbf00bdccc4a0d86073e81fea5528c.png

如果判断这个循环链表为空呢,非常简单

//front 等于  back的时候就代表这个循环队列是空的
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->back;
}

df2ea651609c4ba594848c51f40d1dbf.png 

如果判断一个循环队列满了呢

//我们只需要判断,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; 
}

 dfdbf00bdccc4a0d86073e81fea5528c.png

插入数据,我们需要考虑满的情况,和不满的情况

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;
}

1e6c87a5dcf74a378eb66bbe7d8c70de.png

 删除循环队列中的数据

//删除队列中的数据,有两种情况 一种是空的,一种是有数据的,空的话直接返回false,有数据就front加加
//跳过有效数据范围,就是删除成功了
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    //首先需要先判断 这个队列是否为空
    if(myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    obj->front++;
    //这一步是为了防止front加加的时候 超过数组的长度
    obj->front %= obj->N;
    return true;
}

3fe83082d3bb441aaad0077ae75f7ea7.png

从队列的头获取数据

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];
    }
}

 

860b7d43aa644c8d8a818abf39157798.png

翻译这个循环队列,我们需要释放两次,一个是数组的空间,还有结构体的空间


void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值