数据结构(链式队列——c语言实现)

基本概念 

        链式队列是基于链表结构实现的队列,链式队列结构体通常有两个成员,一个用来存储数据,另一个为结构体指针,指向下一个节点,也就是链队中的链。

优点

  1. 动态大小:链式队列的大小是动态的,可以根据需要动态地扩展和收缩,无需事先定义队列的最大容量。

  2. 内存利用率高:在插入和删除操作中,链式队列不会浪费内存空间(如数组可能因固定大小而浪费的空间)。

  3. 灵活性:链式队列在插入和删除操作上具有较高的灵活性,可以在O(1)时间复杂度内完成。

缺点

  1. 指针开销:每个节点都需要额外的指针域来存储下一个节点的地址,这增加了存储开销。

  2. 内存分配和释放:链式队列在创建和删除节点时需要进行内存分配和释放操作,这可能会增加程序的复杂性和运行时间。

  3. 缓存不友好:由于链式队列的节点在内存中不是连续存储的,这可能导致缓存命中率降低,从而影响性能。

链式队列 

       在之前的顺序队列里面我们操作队列是通过两个变量来保存队头和队尾+1的元素下标来实现对队列的操作的;在链式队列这里同样需要两个变量来操作队头和队尾,因为这里是链式队列,因此操作队头和队尾就需要使用两个结构体指针,在顺序队列中,我们的队头和队尾是跟数据在同一个结构体中,但是在链式队列这里需要把队列成员和操作队列的指针分开放在两个结构体里面,通过存放队头和队尾指针的结构体,来操作队列;故在头文件中就需要定义两个结构体。

       跟顺序队列不同的是,链式队列没有放满的说法,因此在这里两个指针一个指队头,一个指队尾,就没有顺序队列里面的两个规定了;除此之外,在之前的链式表以及链式栈里面我都规定了头节点不存储数据,但是在链式队列这里,就没有这个规定了,我们的每一个节点都需要存储数据。

#ifndef _LINKQUEUE_H
#define _LINKQUEUE_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define OUT(A) {printf("%d\n",A);}

typedef int Type;
//链队节点
typedef struct list{
    Type data;
    struct list *next;
}node;
//队头和队尾
typedef struct{
    node *front;
    node *rear;
    int len;
}queue;

//创建
queue *create_linkQueue();
//判空 空为0,非空为1
int empty_queue(queue *q);
//入队
void enter_queue(queue *q,Type data);
//出队
Type delete_queue(queue *q);
//初始化
void init_queue(queue *q);
//回收
void free_queue(queue **q);

#endif

       在存放队头front和队尾rear的结构体里面我增加了一个保存链队长度的变量len,这个len不是必须的,根据实际需求来即可;在创建节点结构体时,list依旧不能省略,因为在结构体里面我们要创建指向这个结构体的指针,需要用到这个结构体。

#include "linkqueue.h"

//创建
queue *create_linkQueue()
{
    queue *q = (queue *)malloc(sizeof(queue));
    if(NULL == q)
    {
        perror("create queue malloc");
        return NULL;
    }
    q->len = 0;
    q->front = q->rear = NULL;
    return q;
}
//判空 空为0,非空为1
int empty_queue(queue *q)
{
    if(NULL == q)
    {
        puts("queue is NULL");
        return -1;
    }
    if(NULL == q->front && q->rear == NULL)
    {
        puts("queue is empty");
        return 0;
    }
    else
        return 1;
}
//入队
void enter_queue(queue *q,Type data)
{
    if(NULL == q)
    {
        puts("queue is NULL");
        return;
    }
    node *l = (node *)malloc(sizeof(node));
    if(NULL == l)
    {
        perror("malloc node");
        return;
    }
    l->data = data;
    l->next = NULL;
    if(NULL == q->front)
    {
        q->rear = l;  //空的时候
        q->front = l;
    }
    else
    {
        q->rear->next = l;  //非空的时候
        q->rear = l;
    }
    q->len++;
}
//出队
Type delete_queue(queue *q)
{
#if 1
    if(1 == empty_queue(q))
    {
        Type n = q->front->data;
        node *l = q->front;
        q->front = l->next;
        if(NULL == q->front)
            q->rear = NULL;
        free(l);
        q->len--;
        return n;
    }
#else
    if(empty_queue(q)<=0)
        return -1;
    node *t = q->front;
    Type data = t->data;
    q->front = t->next;
    if(!q->front)
        q->rear = NULL;
    free(t);
    return data;
#endif
}
//初始化
void init_queue(queue *q)
{
    if(NULL == q)
    {
        puts("queue is NULL");
        return;
    }
    while(q->front)
    {
    node *l = q->front;
    q->front = l->next;
    q->len--;
    free(l);
    }
    q->rear = NULL;
}
//回收
void free_queue(queue **q)
{
    init_queue(*q);
    free(*q);
    *q = NULL;
}

创建:queue *create_linkQueue()

       在创建队列这里,有两个结构体我们创建的时候应该给哪一个开辟空间呢?对于这个问题我们稍作分析就知道啦,队列一开始肯定是个空队列,既然是空队列那就没有成员,没有成员那就不需要给存放数据的节点开辟空间,我们只需要给存放两个指针的结构体开空间即可;因为一开始队列为空因此队头(front)和队尾(rear) 都指向NULL,并且len=0,创建完成返回结构体的空间地址。

队列判空:int empty_queue(queue *q)

       因为队列为空的时候front和rear都指向NULL,因此我们只需要判断这两个指针是否都指向空即可判断队列是否为空;在我这个结构体中也可以用len来判断,但是一般来说都会通过头尾指针的指向来判断,队列空函数返回值为0,非空返回值为1。

入队:void enter_queue(queue *q,Type data)

       在入队函数中,首先需要创建成员节点,创建完成之后给里面的data赋值;因为入队是在队尾进行操作,因此入队的这个节点一定是队列的最后一个成员,故它里面的指针next的指向一定尾空;除此之外入队的时候会有两种情况,一种情况是:队列为空队列,这时入队以后之后一个节点,因此需要让front和rear都指向这个节点;另一种情况是:队列里面有成员,那这时我们就需要做两个操作,首先让原来rear指向的节点里面的next指向新入队的节点,再让rear往后移动指向我们新入队的节点,在我这里还需要让len++,这样就完成了入队操作。

出队:Type delete_queue(queue *q)

       在出队这里我们需要拿到出队的成员,故函数返回值类型为Type;在出队之前我们需要做一个判断,如果队列非空我们才能进行出队操作;因为每一个成员是一个结构体,因此出队需要释放存放节点的空间,所以我们需要一个结构体指针来接收这个出队的成员,然后用一个Type类型的变量来保存节点里data的值,之后让front指向出队成员的下一个,也就是出队成员中next的指向;在这里又需要做另一个判断,那就是如果出队这个成员是队列里的最后一个,也就是说执行上述操作之后发现front指向NULL,那就证明队列里面没有成员了,这时需要让rear也指向NULL,避免野指针的出现,如果front没有只想空,那就不需要这一操作;最后将节点空间释放,让len--,然后返回data的内容即可。

初始化:void init_queue(queue *q)

       在初始化这里跟链表是一样的,都需要通过循环遍历这个队列的成员,将存储每一个成员的空间都释放,这里操作队头front一直往后移动即可,在将所有的节点空间都释放之后还需要将rear指向NULL,这里不要忘记啦;在这里可以在循环结束后直接让len=0,可以不用放在循环里面做自减操作,这样就完成了队列的初始化。

回收:void free_queue(queue **q)

       回收还是同样的配方,首先传参传指向结构体指针的地址,然后先做初始化,再将传入的这个指针空间也释放,最后让指针指向空即可。

测试(主函数)

主函数代码 

#include "linkqueue.h"
int main(int agrc,char *agrv[])
{
    queue *q = create_linkQueue();

    puts("10,20,30,40,50,60依次入队");
    enter_queue(q,10);
    enter_queue(q,20);
    enter_queue(q,30);
    enter_queue(q,40);
    enter_queue(q,50);
    enter_queue(q,60);
    printf("此时链队长度为:%d\n",q->len);

    puts("出队");
    OUT(delete_queue(q));
    puts("出队");
    OUT(delete_queue(q));
    puts("出队");
    OUT(delete_queue(q));
    printf("此时链队长度为:%d\n",q->len);

    puts("初始化");
    init_queue(q);
    printf("此时链队长度为:%d\n",q->len);

    puts("回收");
    free_queue(&q);
    printf("q=%p\n",q);
    return 0;
}

栈和队列的知识图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值