在讲顺序队列之前,先来说说什么是队列。
一、队列的基本概念
队列,就是排队的那种队列。而在数据结构中,队列是一种逻辑结构,是一种特殊的线性表。特殊在——只能在固定的两端操作线性表。只要满足了这个条件,这种特殊的线性表就会呈现一种 “先进先出” 的逻辑,这种逻辑就被称为队列。
图解:
由于约定了只能在只能在 固定的两端 进行操作,于是给队列的插入、删除起了特殊的名称:
1. 队头:可以删除节点的一端;
2. 队尾:可以插入节点的一端;
3. 入队:将节点插入到队尾之后,函数名通常为enQueue( );
4. 出队:将队列的头节点从队列中剔除,函数名通常为outQueue( );
5. 取队头:取得队头的元素,但不出队,函数名通常为front( )。
现在就来讲顺序队列。
二、顺序队列
队列可以采用顺序存储,形成循环队列。之所以是循环队列,是因为可以通过更新队头、队尾的下标的信息,来循环地利用整个队列,入队和出队时不需要移动队列中的数据。
循环队列图解:
从图中可以看到,需要空出至少队列中的一个存储位置,来区分循环队列中的满队(队列为满)和空队(队列为空)。
满队和空队的约定:
1. 当 front 与 rear 相等的时候,队列为空;
2. 当 rear 循环+1 与 front 相等的时候,队列为满。
管理循环队列除了需要一块连续的内存之外,还需要记录队列的总容量、当前队列的元素个数、当前队头、队尾元素位置,如果有多线程还需要配互斥锁和信号量等信息。
(一)顺序循环队列的管理结构体设计
图解:
示例代码:
// 学生结构体
typedef struct student
{
int id; // 学生的学号
char name[128]; // 学生的姓名
}stu_t, *stu_p;
// 顺序循环队列管理结构体设计
typedef struct sequence_queue
{
stu_p data_p; // 顺序循环队列的内存的入口
int capacity; // 顺序循环队列的总容量
int front; // 顺序循环队列的队头的元素下标
int rear; // 顺序循环队列的队尾的元素下标
}sq_queue_t, *sq_queue_p;
(二)初始化顺序循环队列
图解:
示例代码:
/**
* @brief 初始化顺序循环队列
* @note None
* @param cap_size:队列的容量
* @retval 成功:返回指向队列的内存的指针
* 失败:返回NULL
*/
sq_queue_p SEQUENCE_QUEUE_Init( int cap_size )
{
// 1、申请顺序队列的堆内存空间(申请堆区1)
sq_queue_p p = malloc( sizeof( sq_queue_t ) );
bzero( p, sizeof( sq_queue_t ) );
// 2、给该内存空间(堆区1)进行赋值操作
if ( p != NULL)
{
// 堆区1里面的堆区2的空间
p->data_p = malloc( sizeof( stu_t ) * cap_size );
if ( p->data_p == NULL )
{
free(p);
return NULL;
}
// 堆区1的特征遍历
p->capacity = cap_size;
p->front = 0;
p->rear = 0;
}
else
return NULL;
// 3、成功返回0
return p;
}
(三)判断顺序循环队列是否为空
示例代码:
/**
* @brief 判断顺序循环队列是否为空
* @note None
* @param p:指向队列的管理结构体的指针
* @retval 如果队列为空: 返回true
* 队列为非空:返回false
*/
bool SEQUENCE_QUEUE_IfEmpty( sq_queue_p p )
{
return ( p->front == p->rear );
}
(四)判断顺序循环队列是否为满
示例代码:
/**
* @brief 判断顺序循环队列是否为满
* @note None
* @param p:指向队列的管理结构体的指针
* @retval 如果队列为满: 返回true
* 队列为非满:返回false
*/
bool SEQUENCE_QUEUE_IfFull( sq_queue_p p )
{
return ( ( p->rear + 1 ) % ( p->capacity ) == p->front );
}
(五)入队 --- 插入数据
示例代码:
/**
* @brief 入队
* @note None
* @param p: 指向顺序循环队列的管理结构体的指针
* data: 要赋值的数据
* @retval 成功: 返回0
* 失败: 返回非0
*/
int SEQUENCE_QUEUE_EnQueue( sq_queue_p p, stu_t data )
{
// 1、判断队列是否是满的
if ( SEQUENCE_QUEUE_IfFull(p) )
return -1;
// 2、在队列的队尾插入数据
p->data_p[ p->rear ].id = data.id;
strcpy( p->data_p[ p->rear ].name, data.name );
// 3、队尾标志+1
p->rear = ( p->rear+1 ) % ( p->capacity ); // 因为是循环队列,因此需要取余操作
// 4、成功返回
return 0;
}
(六)出队 --- 删除数据
示例代码:
/**
* @brief 出队
* @note None
* @param p:指向顺序循环队列的管理结构体的指针
* @retval 成功:返回0
* 失败:返回非0
*/
int SEQUENCE_QUEUE_OutQueue( sq_queue_p p )
{
// 1、判断队列是否是空的
if ( SEQUENCE_QUEUE_IfEmpty(p) )
return -1;
// 2、在队列的队头删除数据
bzero( &p->data_p[ p->front ], sizeof( stu_t ) ); // 清空数据(这一步其实可以不用)
p->front = ( p->front+1 ) % ( p->capacity ); // 因为是循环队列,因此需要取余操作
// 3、成功返回
return 0;
}
(七)获取队头的数据 --- 查询数据
示例代码:
/**
* @brief 获取队头的数据
* @note None
* @param p:指向顺序循环队列的管理结构体的指针
* @retval 成功:返回指向队列的队头的数据的指针
* 失败:NULL
*/
stu_p SEQUENCE_QUEUE_GetFrontData( sq_queue_p p )
{
// 1、判断队列是否是空的
if ( SEQUENCE_QUEUE_IfEmpty(p) )
return NULL;
// 2、将指针队头的数据的指针返回
return &( p->data_p[ p->front ] );
}
(八)遍历整个队列
示例代码:
/**
* @brief 遍历整个队列
* @note None
* @param p:指向顺序循环队列的管理结构体的指针
* @retval 成功:返回0
* 失败:返回非0
*/
int SEQUENCE_QUEUE_ShowQueue( sq_queue_p p )
{
// 1、判断队列是否是空的
if ( SEQUENCE_QUEUE_IfEmpty(p) )
return -1;
// 2、遍历整个顺序队列
printf("================顺序队列里的数据===============\n\n");
for ( int i = p->front; i != p->rear; i++, i = i % ( p->capacity ) ) // 由于是循环队列,因此需要取余操作
{
printf("sq_queue[%d] = 学生的ID:%d, 学生的姓名:%s\n", i, p->data_p[i].id, p->data_p[i].name);
}
printf("==============================================\n\n");
// 3、成功返回0
return 0;
}
(九)销毁队列
示例代码:
/**
* @brief 销毁队列
* @note None
* @param p:指向顺序循环队列的管理结构体的指针
* @retval None
*/
int SEQUENCE_QUEUE_UnInit( sq_queue_p p )
{
free( p->data_p );
free(p);
}
以上内容是我的课后笔记