IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 数据结构之SWUSTOJ965循环队列and Leetcode622设计循环队列。对循环队列的一点思考。(数组和链表两种方式实现循环队列) -> 正文阅读

[数据结构与算法]数据结构之SWUSTOJ965循环队列and Leetcode622设计循环队列。对循环队列的一点思考。(数组和链表两种方式实现循环队列)

今天在刷学校OJ题目的时候做到了这道关于循环队列的题目,说实话这个题刚开始真的没有任何思路,实在是想不出来很好的解决办法,但是当我想起一道在LeetCode上做的一道循环队列的题目时突然对循环队列这个东西有了一点思考,但是即使是做过LeetCode的这道题也还是没能想起这个题重点思路,所以决定好好把循环队列的解题方法和关键思路重新巩固梳理一下。

题目:

学校的OJ题目已经把难度降低了很多,但是题目描述还是有一些模糊,但是这里的输入判断方式需要注意一下,入队和出队都只需要判断第一个字符就可,这题的思路不做过多的介绍,因为大致都是仿照LeetCode上面的这题,基本思路一模一样,后面会对LeetCode的题目进行思路分析。

代码:

#include<iostream>
using namespace std;
typedef struct SList
{
	int* data;
	int n;//空间个数,注意实际放的元素个数是n-1个
	int front;
	int rear;
}SL;
SL* SListCreate(int n)
{
	SL* p = (SL*)malloc(sizeof(SL));
	p->n = n;
	p->data = (int*)malloc(sizeof(int)*n);
	p->front = 0;
	p->rear = 0;
	return p;
}
bool IsQueueFull(SL* ps)//判断队列是否为满
{
	if ((ps->rear + 1) % ps->n == ps->front)//队列为满的判断条件
		return true;
	else
		return false;
}
bool IsQueueEmpty(SL* ps)//判断队列是否为空
{
	if (ps->rear == ps->front)
		return true;
	else
		return false;
}
void  SListOperation(SL* ps)
{
	char a[5] = { 0 };//用第一个字符判断是入队操作还是出队操作
	cin >> a;
	if (a[0] == 'i')
	{
		//说明要入队
		int val;
		cin >> val;
		if (!IsQueueFull(ps))//入队前先判满
		{
			ps->data[ps->rear] = val;
			ps->rear++;
			ps->rear = (ps->rear) % (ps->n);//防止越界
		}
	}
	else if(a[0] == 'o')
	{
		//说明要出队
		if (!IsQueueEmpty(ps))//入队前先判空
		{
			ps->front++;
			ps->front = (ps->front) % (ps->n);//防止越界
		}
	}
}
void SListPrint(SL* ps)//剩下队列元素的打印
{
	while (ps->front != ps->rear)
	{
		printf("%d ", ps->data[ps->front]);
		ps->front++;
		ps->front = (ps->front) % (ps->n);
	}
}
int main()
{
	int n = 0;
	cin >> n;
	SL* ret = SListCreate(n);
	int k = 0;
	cin >> k;
	while (k)
	{
		SListOperation(ret);
		k--;
	}
	SListPrint(ret);
	return 0;
}

622. 设计循环队列 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/design-circular-queue/

用数组的方式实现循环队列:

先来看一看用数组实现循环队列的代码,下面的代码注释比较少,如果代码不好理解,下面的图解思路一定可以解开你的疑惑!

typedef struct 
{
    int* a;
    int k;//队列长度为k
    int front;//头的下标
    int rear;//尾的下标
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    if(obj->front==obj->rear)
        return true;
    else
        return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    if((obj->rear+1)%(obj->k+1)==obj->front)
        return true;
    else
        return false;
}
MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* q=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));//先给结构体开空间
    q->a=(int*)malloc(sizeof(int)*(k+1));//开k+1个数组空间
    q->front=q->rear=0;//给front和rear赋初值0
    q->k=k;

    return q;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }
    else
    {
        obj->a[obj->rear]=value;
        obj->rear++;
        obj->rear=obj->rear%(obj->k+1);//防止tail越界
        return true;
    }
}
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
   if(myCircularQueueIsEmpty(obj))
        return false;
    else
    {
        obj->front++;
        obj->front=obj->front%(obj->k+1);
        return true;
    }
}
int myCircularQueueFront(MyCircularQueue* obj) 
{
    //这一步非常容易忽略,但是题干中也有明确地提示到
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
    {
        return obj->a[obj->front];
    }
}

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
    {
        //return obj->a[obj->rear-1];//第一直觉的写法,思考这里有什么问题
        int i=(obj->rear+obj->k)%(obj->k+1);
        return obj->a[i];
    }
}
void myCircularQueueFree(MyCircularQueue* obj) 
{
   free(obj->a);//应当先将a这块开辟的空间free掉,也很容易忽略
   free(obj);//再free掉这个结构体空间
}

思路:当我们刚开始拿到这个题的时候,如果对循环队列没有概念的情况下,第一反应大家都一定会去想,需要存放k个数据,我们应该怎么去开辟内存空间呢?我在思考这个题目的时候,也真的是没能想到去开辟k+1个空间的办法,那么知道这个开辟办法过后,我们应当去思考这样设计的目的是什么?为什么设计k个空间行不通呢?

·第一步,也就是我们需要首先正确并且合理地去创建一个结构体,保证在建立这个结构体里面包含的一些内容在每个函数接口都各有各的作用,并且不可或缺,十分重要!下面就来看一看我的这个设计思路:

typedef struct 
{
    int* a;
    int k;//队列长度为k
    int front;//头的下标
    int rear;//尾的下标
} MyCircularQueue;

因为是创建一个数组实现的循环队列,那么首先,我们需要创建一个front位置的下标,用于记录出队列的那个下标,需要用上一个rear位置的下标,用于记录每一次入队列的位置坐标,最后呢这个队列长度k呢,因为题目中只有myCircularQueueCreate这个接口提供了k的值,但是我们后面还需要使用这个k值,那么我们将k存入这个结构体中,以后每一次使用直接从结构体去取即可。

·第二步

结构体建立好了我们就需要思考队列的创建问题了。

MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* q=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));//先给结构体开空间
    q->a=(int*)malloc(sizeof(int)*(k+1));//开k+1个数组空间
    q->front=q->rear=0;//给front和rear赋初值0
    q->k=k;

    return q;
}

上面这段代码中,先给结构体开辟空间,front和rear赋初值都给0,然后k赋值给结构体中的k。以上三个步骤不容易出错。但是这个数组却开辟了k+1个空间?这是为何呢?接下来画图说明一下:

上面的思路证明了如果我们开辟k个空间来存放k个数据设计循环队列明显是行不通的,那么我们就要换个思路。大家可以联系以下生活实际,比如说,队列在什么时候起到关键作用,他的意义是什么。两个关键词:判断队列是否为空!判断队列是否为满? ? ? ? ? ? ? ? ? ?

为了可以实现判空和判满,我们需要设计一个新的开辟空间的思路,下面通过一封图来理解一下:

通过上图可以看出需要判空和判满我们则需要开辟k+1个空间来实现循环队列,那么我们再来举一个具体的实例看看:

?通过上面的实例就可以完成对判空判满两个接口函数的实现:

bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    if(obj->front==obj->rear)
        return true;
    else
        return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    if((obj->rear+1)%(obj->k+1)==obj->front)
        return true;
    else
        return false;
}

·第三步:实现完判空和判满接下来我们就需要来写一写入队和出队的接口了。

依旧通过具体的例子来看看:

?

通过上面图解的分析,我们就可以完成入队和出队接口函数的代码:

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }
    else
    {
        obj->a[obj->rear]=value;
        obj->rear++;
        obj->rear=obj->rear%(obj->k+1);//防止tail越界
        return true;
    }
}
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
   if(myCircularQueueIsEmpty(obj))
        return false;
    else
    {
        obj->front++;
        obj->front=obj->front%(obj->k+1);
        return true;
    }
}

·第四步:完成了入队出队操作后,我们接下来看看如何去找到队首的元素和队尾的元素,注意这里有一个这题非常容易出错的点!

?接下来就可以完成我们队首元素和队尾两个元素的接口函数代码:

int myCircularQueueFront(MyCircularQueue* obj) 
{
    //这一步非常容易忽略,但是题干中也有明确地提示到
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
    {
        return obj->a[obj->front];
    }
}

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
    {
        //return obj->a[obj->rear-1];//第一直觉的写法,思考这里有什么问题
        int i=(obj->rear+obj->k)%(obj->k+1);
        return obj->a[i];
    }
}

·第五步:最后还需要写一个释放空间的代码:

这里也需要注意一下!

释放空间的时候,应当首先去释放开辟的数组空间,再去释放结构体

这里的先后顺序不能搞反了!

因为一旦先释放掉结构体后,就没法去找到开辟的数组了,所以要记得先释放malloc开辟的数组,然后再释放结构体!

void myCircularQueueFree(MyCircularQueue* obj) 
{
   free(obj->a);//应当先将a这块开辟的空间free掉,也很容易忽略
   free(obj);//再free掉这个结构体空间
}

用链表的方式实现循环队列:

首先还是先来看看代码:

typedef struct SList
{
    int data;
    struct SList* next;
}SL;
typedef struct 
{
    int k;
    SL* front;
    SL* rear;
} MyCircularQueue;

MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* q=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    SL* cur=(SL*)malloc(sizeof(SL));
    q->k=k;
    q->front=cur;
    q->rear=cur;
    while(k)
    {
        SL* newnode=(SL*)malloc(sizeof(SL));//开辟一个新节点
        cur->next=newnode;
        cur=newnode;
        cur->next=q->front;
        k--;
    }//在入队出队之前首先要把空间所有的节点都开辟好,只是不存放有效数据而已
    return q;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    if(obj->front==obj->rear)
        return true;
    else
    return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    if(obj->rear->next==obj->front)
        return true;
    else
        return false;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    if(myCircularQueueIsFull(obj))
        return false;
    else
    {
        obj->rear->data=value;
        obj->rear=obj->rear->next;
        return true;
    }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
        return false;
    else
    {
        obj->front=obj->front->next;
        return true;
    }
}

int myCircularQueueFront(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
        return obj->front->data;
}

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
    {
        SL* prev=obj->front;
        while(prev->next!=obj->rear)
        {
            prev=prev->next;
        }
        return prev->data;
    }
}
void myCircularQueueFree(MyCircularQueue* obj) 
{
    while(obj->front!=obj->rear)
    {
        SL* cur=obj->front->next;
        free(obj->front);
        obj->front=cur;
    }
    free(obj->front);
    free(obj);
}

?

上面分别用数组的方式和链表的方式实现了循环队列的问题,个人认为用链表的方式实现逻辑上更为简单一些,不过在结构体的创建上和空间的开辟方面相对复杂一些。数组的方式在逻辑分析上稍微会多绕一些弯,不过结构体建立空间开辟还算比较简单。两种方法在时空上各有千秋。

最后看完循环队列的两道OJ题后,就在想为什么会有大佬弄出循环队列这个东西,比较好奇其中的原理和设计者的设计目的,所以去查阅了一下相关的大神博客关于循环队列的文章。

循环队列是队列的一种顺序存储结构。循环队列的引入,目的是为了克服假溢出时大量移动数据元素 。?

循环队列的优点是相对于直线队列来讲的,直线队列在元素出队后,头指针向后移动,导致删除元素后的空间无法在利用,即使元素个数小于空间大小,依然无法再进行插入,即所谓的“假上溢”当变成循环队列之后,删除元素后的空间仍然可以利用,最大限度的利用空间。

1、循环队列的优点:
  可以有效的利用资源。用数组实现队列时,如果不移动,随着数据的不断读写,会出现假满队列的情况。即尾数组已满但头数组还是空的;循环队列也是一种数组,只是它在逻辑上把数组的头和尾相连,形成循环队列,当数组尾满的时候,要判断数组头是否为空,不为空继续存放数据。
  2、循环队列的缺点:
  循环队列中,由于入队时尾指针向前追赶头指针;出队时头指针向前追赶尾指针,造成队空和队满时头尾指针均相等。因此,无法通过条件front==rear来判别队列是"空"是"满"
  3、拓展知识:
  为充分利用向量空间,克服"假溢出"现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 18:07:49  更:2022-04-18 18:08:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 7:36:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码