【数据结构与算法】队列的详细学习(普通队列,循环队列,增删改查判断空和满的详细使用)!

                                           

大家好,今天我们进入队列的学习!队列具体操作的源码附在最后!

首先我们来认识一下队列的概念及结构:

队列的结构和栈完全相反,栈只能再栈顶进再从栈顶出,遵从后进先出(LIFO);

队列只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,遵循的是先进先出,后进后出

入队列:进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头,队列的图示如下

     

上一篇博客讲了栈的具体内容,其中有个问题,就是入栈的顺序是1,2,3,4,问出栈的顺序是否一定为4,3,2,1。

那么队列这里也可以问这个问题:

 如果入队列的顺序是1,2,3,4,那出队列的顺序也是1,2,3,4吗?

答案是:一定的的,出队列的顺序一定是1,2,3,4,并且无论在什么情况下都是1,2,3,4,永远都能保持先进先出,所以抽号机的原理就是用队列来实现的。

当然队列也分数组队列链式队列,在这里我们研究链式队列是会对实际操作更方便一些的,因为队列需要操作的是队头和队尾,所以就要涉及到头部删除和尾部插入,而链表的插入和删除相对较容易一些,所以我在这里选择研究链式队列

既然决定使用链式队列,并且要求尾进头出,那么自然而然就容易想到单链表

                                          

那么我们就先来定义一个单链表结构:

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

但是,仅仅定义这样一个结构体,就够了吗?让我们从测试的角度来分析一下:

int main()
{
	QNode* phead = NULL;
	QNode* tail = NULL;
	int size = 0;
	return 0;
 
}

首先我们需要定义两个结构体,从命名上不难看出一个指向队头,一个指向队尾,当然也可以定义size来测量队列的大小。

如果我的队列中要存放多个数据该如何操作呢?

单链表中的每个结点都有两个部分组成:一个Data和一个next,

我们不妨再定义一个结构体,将整个队列的队头和队尾都记录下来,代码如下:

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

那这串代码记录的就是整个队列的头和尾了,结合图来看就很容易理解了。

 

接下来我们来进行队列的初始化

 初始化的过程很简单,只需要将队列内置空就可以了。

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

不要忘记队列的空间释放,因为是单链表的结构,所以要一个一个的释放空间:

void QueueDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail=NULL;
	pq->size = 0;
}

需要注意

1.首先我们要用asset来断言整个链表是否为空;

2.然后使用结构体指针cur保存phead的位置,然后操作cur;

3.当cur不为空的时候就用结构体指针再定义一个next用来保存cur的next;

4.然后释放掉当前的cur的空间,再用next赋给cur,这样next就成为了新的cur,完成迭代

5.当然如果cur为空时,那么整个队列也为空(QNode* cur = pq->phead),所以phead和ptail就可以直接置空,size也为0;

6.队列的置空和单链表的置空并没有太大差异,如果忘记了操作可以看往期博客单链表销毁空间的操作。

接下来进行入队操作:

和单链表尾插大差不差,同样要判断链表中是否为空,这里不仅要判断队头还要判断队尾

void QueuePush(Queue* pq, QDataType x)
{
	
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
 
 
    //只有一个结点的情况
	if (pq->ptail == NULL)
	{
		assert(pq->phead==NULL);
		pq->phead = pq->ptail = newnode;
 
	}
    
    //多个节点
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}

需要注意

入队和单链表中的尾插操作大差不差,所以要先找尾,然后malloc一段新的空间出来再插入数据、并且成为新的尾结点;

需要注意的是phead和ptail都需要判断,这样才能知道哪个结点是空;

所以还需要分成一个结点多个结点的情况来讨论。

那么接下来进行出队的操作:

出队和单链表的头部删除操作相似,所以还是要分一个结点多个结点的情况来讨论。

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	//一个节点
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;
}

 需要注意

当只有一个结点时我们需要判断phead的next是否为空,也就是第二个结点是否为空,如果第二个结点没有数据那就为空,所以直接free掉第一个结点(pq->next)即可。

如果有多个结点的话,那就再定义一个新的结点,来保存当前节点的下一个结点; 

再把当前节点的空间free掉;  再将新的结点赋值给phead使其成为新的头节点;

最后要给队列中元素的计数size--;

接下来实现获取队头和队尾元素的操作:

获取元素类似于查找函数,所以要注意函数的返回值是最开始typedef好的类型;

代码也比较简单,只需要用结构体访问数据内容即可:

1.获取队头

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}

2.获取队尾

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}

 计算队列元素的操作:

只需用结构体访问size即可

int QueueSize(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->size;
}

判空的操作:

思路也非常简单,根据图标来看判断头节点为空即可:

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->phead == NULL;
}

 也可以使用size来看:

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	/*return pq->phead == NULL;*/
	return pq->size==0;
}

当然如果是要用size来判空的话入队和出队的代码一定要记录好size,不要忘记给size的自加和自减。

队列元素展示操作:

和栈的展现相似,我们需要边出队边将其内容展现:

#include"Queue.h"
int main()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
 
 
	QueueDestory(&q);
	return 0;
 
}

基本的队列知识都讲完了,那么接下来学一个特殊的队列:

循环队列

需要注意的是:环形队列通常使用数组实现

                        

 那么在使用我们的循环队列时,我们可以发现很明显的一个问题就是如何区分空与满?!

因为刚开始没有存元素的时候我们的head和tail都是在同一位置,随着我们相继向队列中存元素,那么我们的head不动,tail往后走,当tail再次和head相遇的时候,那么问题来了,这个时候队列是满还是空的呢?如何解决?

那么这里有一个很巧妙的解决方法:

比如我们需要存储4个数据,那么我们就去开辟五个空间

                 

                     

这里解决队列是否为满的代码十分的巧妙,希望大家好好理解一下!

                                                 

(back+1)%(k+1)==front就为满!

接下来演示一个通过保留一个位置(计数器)来实现循环队列!

实现类:

class MyCircularQueue {
    private int front;
    private int rear;
    private int capacity;
    private int[] elements;//通过计数器的方式实现
 
    public MyCircularQueue(int k) {
        capacity = k + 1;
        elements = new int[capacity];
        rear = front = 0;
    }
}

插入一个元素:

 public boolean enQueue(int value) {//插入一个元素
        if (isFull()) {
            return false;
        }
        elements[rear] = value;
        rear = (rear + 1) % capacity;
        return true;
    }

删除一个元素: 

    public boolean deQueue() {//删除一个元素
        if (isEmpty()) {
            return false;
        }
        front = (front + 1) % capacity;
        return true;
    }

获取队首:

    public int Front() {//获取队首元素
        if (isEmpty()) {
            return -1;
        }
        return elements[front];
    }

获取队尾:

 public int Rear() {//获取队尾元素
        if (isEmpty()) {
            return -1;
        }
        return elements[(rear - 1 + capacity) % capacity];
    }

判断是否为空:

    public boolean isEmpty() {
        return rear == front;
    }

判断是否为满:

    public boolean isFull() {
        return ((rear + 1) % capacity) == front;
    }

循环队列的具体实现就结束了 。

接下来附上队列实现源码,希望对大家有所帮助:

Queue.c
#include"Queue.h"
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	/*return pq->phead == NULL;*/
	return pq->size == 0;
}
 
 
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
void QueueDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail=NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
	
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	if (pq->ptail == NULL)
	{
		assert(pq->phead==NULL);
		pq->phead = pq->ptail = newnode;
 
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}
 
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	//一个节点
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;
}
 
 
 
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}
 
int QueueSize(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq)); 
	return pq->size;
}
 

Queue.h 

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;
 
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;
 
void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

test.c

#include"Queue.h"
int main()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	printf("%d ", QueueFront(&q));
	QueuePop(&q);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
 
 
	QueueDestory(&q);
	return 0;
 
}

 

 

 

 

 

 

 

 


 

 


 


 

 

 

 

 

 

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值