【C语言数据结构】之队列

一、基本概念

队列是一种特殊的线性表,它被限制只能在头部和尾部进行操作。并且只能在头部(队首)删除,在尾部(队尾)插入。
这就是队列的特点:先进先出(FIST IN FIST OUT)``(FIFO)结构。
常用的两种实现队列的方式:一是使用顺序结构来实现,二是使用链式结构来实现。

二、顺序队列

顺序队列就是使用顺序结构来实现的队列,分配一段连续的存储空间,有头指针和尾指针分别指向头部元素和尾部元素的下一个位置。(在顺序队列中,头指针尾指针叫做头下标尾下标更合理,但是大家都习惯叫做指针了,也就这样吧)。
插入元素的时候现在尾指针所指向的空间插入,然后尾指针向后挪移一位。(这一点对下面的图示理解比较重要)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
诶,这时候问题就出来了,这时候数组还有这么多空间没有存放数据,我如果想加入一个元素,可是我这时候队尾指针都指向了一片没有开辟的内存,这该怎么办啊。
这时候我们就得采用一种循环的方法来避免这种情况的发生,当队尾指针超出最大范围,我们就让他重新指向下标为0的位置。
这时候这个顺序队列就可以用这样的图来理解。
在这里插入图片描述
上图是空队列的图示。
在这里插入图片描述
上图是满队列的图示。
诶,这时候又有人想问了,为什么有6个空间,只有5个空间存放了元素呢?
原因是这样的:当头指针和尾指针重合的时候,我们就说他是空队列,但是,循环了一圈之后,尾指针追上了头指针,但是这时候是满队列,所以我们无法以这种方式区别空队列和满队列。
为了解决这种问题,我们只能浪费一个空间不去存放元素,我叫它空空间,它不是在一个下标固定不变的,这个空空间的下一个空间就是头指针所指向的空间。当尾指针到达了空空间的时候,这时候队头指针就在队尾指针的下一个位置,这时候我们称之为满队列。
因为我们的指针要循环,所以不能单纯的++,这样会超出,所以我们需要用到取模运算的方法。
队满:(p->back+1)%Size == p->head;

1 宏定义和结构体

# define True 1
# define False 0
# define Size 10

typedef struct Queue
{
	int data[Size];
	int head;
	int back;
}Queue;

2 初始化一个队列

Queue* CreateQueue()
{
	Queue* p = (Queue*)malloc(sizeof(Queue));
	assert(p);
	memset(p, 0, sizeof(Queue));//头指针尾指针都在下标为0处
	return p;
}

3 判断队空队满

//判断队列为空
int IsEmpty(Queue* p)
{
	assert(p);
	if (p->head == p->back)
	{
		return True;
	}
	return False;
}
//判断队列满
int IsFull(Queue* p)
{
	assert(p);
	if (p->head == ((p->back + 1) % Size))
	{
		return True;
	}
	return False;
}

4 入队

入队,先让元素插入到队尾指针所指向的下标的位置,然后队尾指针向后挪移一个,注意不能是单纯的++,因为是循环的,所以我们要取模运算。
比如说我目前尾下标是4,总共有6个空间大小。插入了元素之后尾下标挪移到了下标为5的位置。
这时候我们要想加入一个元素之后,下标就会再向后挪移一位,这时候不能挪移到6了,因为下标最大就是5
按道理我们要挪移到下标为1的位置,但是怎么去呢,我们采用取模运算的方法(5 + 1) % 6 = 0,这样指针就到了下标为0的位置。
在这里,6是最大空间,5是当前队尾指针所指下标,所以可以这样表示(p->back + 1) % Size

void InsertQueue(Queue* p, int num)
{
	if (IsFull(p) == True)
	{
		printf("队满无法入队\n");
		return;
	}
	p->data[p->back] = num;
	p->back = (p->back + 1) % Size;
}

5 出队

int PopQueue(Queue* p)
{
	if (IsEmpty(p) == True)
	{
		printf("队伍为空无法出队\n");
		return -1;
	}
	int x = p->data[p->head];
	p->head = (p->head + 1) % Size;
	return x;
}

6 获取队列内元素数量

(p->back - p->head + Size) % Size就能获取队列元素数量

int LenQueue(Queue* p)
{
	return (p->back - p->head + Size) % Size;
}

7 查看队首元素

void ShowHead(Queue* p)
{
	if (IsEmpty(p) == True)
	{
		printf("队列为空\n");
		return;
	}
	printf("队首元素是 %d \n", p->data[p->head]);
}

三、链式队列

链式队列就是单链表,只不过限制了单链表只能头删尾插操作。剩下的操作和单链表相差不大。

1.结构体和宏定义

# define True 1
# define False 0

typedef struct Queue
{
	int data;
	struct Queue* next;
	struct Queue* back;//尾部指针
}Queue;

2. 初始化

Queue* CreateQueue()
{
	Queue* p = (Queue*)malloc(sizeof(Queue));
	assert(p);
	memset(p, 0, sizeof(Queue));
	return p;
}

3. 判断队空

int IsEmpty(Queue* p)
{
	assert(p);
	if (p->next == NULL)
	{
		return True;
	}
	return False;
}

4. 入队

入队后记得更新队尾指针即可。

void InsertQueue(Queue* p, int num)
{
	assert(p);
	Queue* node = (Queue*)malloc(sizeof(Queue));
	memset(node, 0, sizeof(Queue));
	node->data = num;
	if (IsEmpty(p) == True)	
	{
		p->next = node;
		p->back = node;
	}
	p->back->next = node;
	p->back = node;
}

5. 出队

如果出队后没有元素了,记得更新队尾指针指向空即可。

int PopQueue(Queue* p)
{
	if (IsEmpty(p) == True)
	{
		printf("空队列无法出队\n");
		return -1;
	}
	Queue* tmp = p->next;
	p->next = tmp->next;
	int x = tmp->data;
	free(tmp);
	tmp = NULL;
	if (IsEmpty(p) == True)
	{
		p->back = NULL;
	}
	return x;
}

6. 打印队首元素

void ShowHead(Queue* p)
{
	if (IsEmpty(p) == True)
	{
		printf("空队列没有首元素\n");
		return;
	}
	printf("%d\n", p->next->data);
}

四、总结

顺序队列用的最多的,最方便的就是循环顺序队列,循环顺序队列我们只需要确保循环时候的下标不要出错,合理运用取模运算就好。
链式队列就是限制了输入输出的单链表,我个人习惯在结构体中多加了一个尾指针,所以在进行入队出队的时候要确保尾指针指向正确,尤其是最后一个元素出队后,队列中没有元素了,尾指针要指向空。并且入队时,如果队列是空的,不能直接使用尾指针来入队。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LiuJWHHH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值