C语言 队列 链表实现,笔记版

哼! 哼 ! 啊...

又又又来分享知识了,小伙子你渴望知识的力量吗?快端上来吧

1.队列

  1. 首先来说下队列的基本结构吧,从图中就可以看出,从队头出去,从队尾入数据互不干扰;也就说队头只出数据,队尾只入数据

  1. 队列具有公平性,入数据和出数据都是有着一对一的关系。也就说进去是 1 2 3 4,出来就一定是1 2 3 4。
  2. 应用的场景一般有判断,就好比如节假日去饭店吃饭,好吃的店铺往往会有会多人排队,这个时候就用到了队列。
  1. 怎么个事呢?,假如服务员每次叫号,餐厅内位置空出来了就会去叫 1号落座点菜,这个时候就是出队了,1要销毁,剩下就是2 3 4 5 6 7 8
  2. 而后面来的人,来排队就分配号码,就用前面的号码 +1
  3. 假如我在8号,前面排队排到了4号,我想知道还差几位到我用餐,这个时候就知道当前位置,减去他的位置,得出还差几位,如果系统高级一些还会有预计时间

2.1基本结构和头尾节点结构体

  1. 基本结构和单链表节点一样,其实就是用单链表实现的
  2. 第二个头尾节点结构体,可以解决二级指针和多传参数的问题,为什么呢?
    1. 结构体内放指针,那么要怎么改变指针,那么就只需要传结构体指针就行了,phead和ptail分别指向队列的头和尾
    2. 你想想把两个指针放到结构体里面,那你传参数的时候是不是只要传结构体指针就行,对就是这样这样足以改变。
typedef int QuDataType;
typedef struct QListNode
{
	QuDataType val;
	struct QuDataType* next;
}QNode;

typedef struct Queue
{
	QNode* phead;//这里的头尾指向的类型肯定是链表的
	QNode* ptail;//这两块空间可以指向QNode,要不然也不是这个类型
	int size;
}Queue;

2.2队列的实现

  1. 那实现之前,我用选择使用什么呢?
    1. 数组,单链表,双链表,这里我们选择使用单链表
    2. 数组怎么也做不好这件事,因为每次出队列的时候都要挪动数据,整个数据都要向前移动,不适合
    3. 双链表怎么样都行,但是还是要优先考虑单链表,因为比起单链表,双链表要多一个指针,浪费不少空间,还要多管理一个指针
  2. 所以这个地方,链表的第一个节点出数据,最后一个几点入数据

2.3初始化(Init) 销毁(Destroy)

  1. 如果你之前写过单链表就会很简单了;单链表实现增删改查
  2. 初始化和销毁
  3. 销毁部分也很简单,在销毁链表前保存下一个节点。
//初始化
void QueueInit(Queue* ps)
{
	assert(ps);//确保这个结构体不为NULL
	ps->phead = NULL;
	ps->ptail = NULL;
	ps->size = 0;
}
//销毁链表
void QueueDestroy(Queue* ps)
{
    assert(ps);
    QNode* cur = ps->phead;
    while (cur)
    {
        QNode* next = cur->next;
        free(cur);//free 
        cur = next;
    }
}

2.4插入(Push) 删除(Pop)

  1. 改变结构体指针的指针,就需要二级指针,改变单链表头指针就是需要二级指针
  2. 用一个结构体去装头尾指针,传结构体的指针就行了,这样就可以改变结构体内的指针了;这样避免二级指针还避免了多传了参数
  3. 因为队列的结构原因我们只需要尾插就行了,尾插前开辟空间
  4. 当然也分为单个节点和多个节点的情况
    1. 当节点为一个的时候,头尾节点都要指向一个地方
    2. 多个节点和单链表的尾插一样

//入队
void QueuePush(Queue* ps,QuDataType x)
{
	assert(ps);//保证结构体指针不为NULL

	//空间申请,并初始化防止对野指针使用
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("QuPush()::malloc");
		return;
	}
	newnode->next = NULL;
	newnode->val = x;
	//都指向一个节点
	if (ps->phead == NULL)//ps->phead == ps->ptail 这两个地址不相等
	{
		ps->phead = ps->ptail = newnode;
	}
	//多个节点
	else
	{
		ps->ptail->next = newnode;
		ps->ptail = newnode;//让尾节点指向新的尾
	}
	ps->size++;
}

2.4.1删除Pop

  1. 出队,也就是头删,也要分为两种情况
    1. 一个节点的情况,free释放第一个节点的时候phead和ptail指向同一个地方,这块空间被释放,但是指针变量还在,为了防止ptail指针为野指针,所以都要置为NULL
    2. 头删,保存头节点的下一个节点,然后删除头节点,把下一个节点地址给头节点指针
​​​​//出队
void QueuePop(Queue* ps)
{
	assert(ps); //确保这个结构体不为NULL
	assert(ps->size != 0);//没数据就不能删了
	//一个节点
	if (ps->phead->next == NULL)//此时说明只有一个节点了
	{
		free(ps->phead);//此时头尾指针都指向头节点,随便free一个就行
		ps->phead = ps->ptail = NULL;//都指向头节点,都要置NULL
	}
	//正常情况
	else
	{
		QNode* next = ps->phead->next;
		free(ps->phead);
		ps->phead = NULL;
		ps->phead = next;//成为新的头
	}
	ps->size--;
}

2.5队头和队尾的数据

  1. 为什么几行代码还要写一个函数,因为有断言这一步操作可以省去很多的麻烦,调用函数逻辑方面也会更加清晰
  2. 主要就是传过来参数的断言判断,防止拿到无效数据为NULL
  3. 如果在主函数调用,可能存在很多未发现的问题
  4. 取数据,直接访问就行,因为已经有了头尾指针
//队尾数据
QuDataType QueueBack(Queue* ps)
{
	assert(ps);//不保证尾节点是否为NULL
	assert(ps->ptail);
	return ps->ptail->val;
}
//队头数据
QuDataType QueueFront(Queue* ps)
{
	assert(ps);//断言ps,指针保证有没有这个结构体,不保证头节点是否为NULL
	assert(ps->phead);
	return ps->phead->val;
}

2.6判NULL和有效数据

  1. 判空,如果有效数据个数为0返回true(1),反之有数据就返回false(0)
    1. 判空运用到打印全部数据
  1. 有效个数拿结构体里面的Size的个数,在出队和入队时的+ -;
bool QueueEmpty(Queue* ps)
{
	assert(ps);
	return ps->size == 0;//不为NULL返回1
}
//有效个数
int QueueSize(Queue* ps)
{
	assert(ps);
	return ps->size;
}

3.完整代码

  • Queue.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int QuDataType;
typedef struct QListNode
{
	QuDataType val;
	struct QuDataType* next;
}QNode;

typedef struct Queue
{
	QNode* phead;//这里的头尾指向的类型肯定是链表的
	QNode* ptail;//这两块空间可以指向QNode,要不然也不是这个类型
	int size;
}Queue;
//初始化
void QueueInit(Queue* ps);

//入队和出队
void QueuePush(Queue* ps,QuDataType x);
void QueuePop(Queue* ps);

//队头和队尾的数据
QuDataType QueueFront(Queue* ps);
QuDataType QueueBack(Queue* ps);

//有效个数
int QueueSize(Queue* ps);

//判空
bool QueueEmpty(Queue* ps);

//销毁
void QueueDestroy(Queue* ps);
  • Queue.c,代码的核心逻辑
#include "Queue.h"
//初始化
void QueueInit(Queue* ps)
{
	assert(ps);//确保这个结构体不为NULL
	ps->phead = NULL;
	ps->ptail = NULL;
	ps->size = 0;
}
//入队
void QueuePush(Queue* ps,QuDataType x)
{
	assert(ps);//保证结构体指针不为NULL

	//空间申请,并初始化防止对野指针使用
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("QuPush()::malloc");
		return;
	}
	newnode->next = NULL;
	newnode->val = x;
	//都指向一个节点
	if (ps->phead == NULL)//ps->phead == ps->ptail 这两个地址怎么可能相等?
	{
		ps->phead = ps->ptail = newnode;
	}
	//多个节点
	else
	{
		ps->ptail->next = newnode;
		ps->ptail = newnode;//让尾节点指向新的尾
	}
	ps->size++;
}
//出队
void QueuePop(Queue* ps)
{
	assert(ps); //确保这个结构体不为NULL
	assert(ps->size != 0);//没数据就不能删了
	//一个节点
	if (ps->phead->next == NULL)//此时说明只有一个节点了
	{
		free(ps->phead);//此时头尾指针都指向头节点,随便free一个就行
		ps->phead = ps->ptail = NULL;//都指向头节点,都要置NULL
	}
	//正常情况
	else
	{
		QNode* next = ps->phead->next;
		free(ps->phead);
		ps->phead = NULL;
		ps->phead = next;//成为新的头
	}
	ps->size--;
}
//有效个数
int QueueSize(Queue* ps)
{
	assert(ps);
	return ps->size;
}
//队尾数据
QuDataType QueueBack(Queue* ps)
{
	assert(ps);//不保证尾节点是否为NULL
	assert(ps->ptail);
	return ps->ptail->val;
}
//队头数据
QuDataType QueueFront(Queue* ps)
{
	assert(ps);//断言ps,指针保证有没有这个结构体,不保证头节点是否为NULL
	assert(ps->phead);
	return ps->phead->val;
}
//判空
bool QueueEmpty(Queue* ps)
{
	assert(ps);
	return ps->size == 0;//不为NULL返回1
}
//销毁链表
void QueueDestroy(Queue* ps)
{
	assert(ps);
	QNode* cur = ps->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);//free 为NULL 不影响
		cur = next;
	}
}
  • test.c,测试部分
void queuetest()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 3);
	QueuePush(&q, 5);
	QueuePush(&q, 7);
	QueuePop(&q);

	printf("%d\n", QueueBack(&q));
	printf("%d\n", QueueSize(&q));
	printf("%d\n", QueueEmpty(&q));
	while (!QueueEmpty(&q))//不为NULL返回0,! 结果取反后是1,1就执行
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
}

4.总结

  1. 虽然栈比较简单,但是也不要掉以轻心,学好当前知识尤为重要;
  2. 后续还有关于栈和队列的经典例题的讲解,敬请期待!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值