上一篇我们讲了栈,使用了数组来实现栈,今天我们来讲队列。
队列
队列的定义
队列是只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的规则,进行插入操作的一端成为队尾,进行删除的一端是队头。
队列的实现
队列的实现我们一般用单链表,其实双链表也行,但是单链表肯定是更能提高效率,所以我们使用单链表来实现。
队列的一般接口
趋势就是我们写链表的那几个接口,然后还变简单了,所以我们是很容易实现的。
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
实现队列前的准备工作
队列是由节点来组成的,第一个结构体是定义的节点,第二个结构体是定义的队列,包括队头和队尾。
接口实现
//定义节点
typedef struct QueueNode
{
struct QueueNode* next;
QDataType val;
}QNode;
//队列的结构体
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
1.初始化队列
初始化队列做的就是队头和队尾都让他们为空指针,队列大小为0。
// 初始化队列
void QueueInit(Queue* q)
{
assert(q);
q->phead = q->ptail = NULL;
q->size = 0;
}
2.队尾入队列
如图所示,我们入队列是头插,出队列是尾删,那就简单多了。看代码。
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail!");
return;
}
newnode->next = NULL;
newnode->val = data;
if (q->ptail == NULL)
{
q->phead = q->ptail = newnode;
q->size++;
}
else
{
q->ptail->next = newnode;
q->ptail = q->ptail->next;
q->size++;
}
}
首先开辟空间,然后判断开辟的这个是否为空,然后插入进去,值得注意的是,当这个队列为空的时候我们也要分情况,就像上面代码写的。
3.队头出队列
上面就说了,我们出队列是尾删。看代码。
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
assert(q->phead);//先保证进来的不是空的
if (q->phead->next == NULL)//删除
{
free(q->phead);
q->phead = q->ptail = NULL;
}
else
{
QNode* next = q->phead->next;
free(q->phead);
q->phead = next;
}
q->size--;//队列大小减小
}
但是我们碰到了这种情况怎么办,我们让phead为空,释放这个节点,但是ptail不处理,那就回让他成为野指针。所以就有了上面的if语句。这是这个代码唯一要解释的地方。
4.获取队列头部的元素
这个很简单,直接返回队头的值就行了。
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->phead);
return q->phead->val;
}
5.获取队列尾部的元素
这个也和上面一样,所以我们直接看代码。
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->ptail);
return q->ptail->val;
}
6.获取队列中有效元素个数
这个也很简单,早在准备工作的时候我们就为队列定义了大小了,所以也可以直接返回。
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
7.检测队列是否为空,如果为空返回非零结果,如果非空返回0
判空就是返回一个关系式,如果是对的就返回对,如果是错的就返回错。
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{
assert(q);
return q->phead == NULL;
}
8.销毁队列
销毁队列就是和链表一样将节点一个一个销毁,这个流程应该已经很熟悉了吧。看代码。
// 销毁队列
void QueueDestroy(Queue* q)
{
QNode* cur = q->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
q->phead = q->ptail = NULL;
q->size = 0;
}
总结
其实不管是栈还是队列都是对顺序表实现的一种考验,看我们前面学的扎不扎实,如果已经学的比较熟练了,就会发现其实这些还更加简单呢。不管怎么样,动脑筋,写代码就是王道。