概述
在前一篇文章中,我们已经用数组实现了队列。在本篇文章中,我们将使用链表来实现队列。使用链表的优点是:动态增长,扩容时更加平滑。缺点是:略微复杂,需要额外管理所有节点。队列相关的操作,仍然是下面6个接口。
Enqueue:向队尾添加一个元素。
Dequeue:从队首移除顶部元素,并返回该元素。
Front:查看队首元素,但不移除它。
Rear:查看队尾元素,但不移除它。
IsEmpty:检查队列是否为空。
Size:获取队列中元素的数量。

实现原理
我们使用一个带头指针和尾指针的单链表来实现队列。其中,头指针对应链表的头部,尾指针对应链表的尾部。入队操作在尾部插入新节点,出队操作从头部删除节点。所有操作均维护头指针和尾指针的正确性,确保队列行为符合FIFO规则。
在下面队列CLinkedListQueue的实现中,我们声明了两个成员变量m_pFront和m_pRear。m_pFront代表队首,m_pRear代表队尾。
cpp
class CLinkedListQueue
{
public:
CLinkedListQueue();
~CLinkedListQueue();
void Enqueue(int nValue);
int Dequeue();
int Front();
int Rear();
bool IsEmpty();
int Size();
private:
struct Node
{
int nData;
Node* pNext;
};
Node* m_pFront;
Node* m_pRear;
};
Enqueue操作
入队就是在链表的尾部插入一个新节点,详细步骤如下。
1、创建一个新节点。
2、如果队列为空,将m_pFront和m_pRear都指向新节点。
3、如果队列非空,将当前尾节点m_pRear的pNext指向新节点pNode,并更新尾指针为pNode。
具体如何实现,可参考下面的示例代码。
cpp
void CLinkedListQueue::Enqueue(int nValue)
{
Node* pNode = new Node();
pNode->nData = nValue;
pNode->pNext = NULL;
if (IsEmpty())
{
m_pFront = m_pRear = pNode;
}
else
{
m_pRear->pNext = pNode;
m_pRear = pNode;
}
}
Dequeue操作
出对就是删除链表头部节点,并返回其值,详细步骤如下。
1、如果队列为空,抛出异常。
2、取出队首节点的值,保存为临时变量。
3、将队首指针更新为下移一位后的指针,如果新的队首指针为空,则将队尾指针也设置为空。
4、释放旧节点的内存。
5、返回上面的临时变量。
具体如何实现,可参考下面的示例代码。
cpp
int CLinkedListQueue::Dequeue()
{
if (IsEmpty())
{
throw underflow_error("queue is empty");
}
Node* pTemp = m_pFront;
int nValue = pTemp->nData;
m_pFront = m_pFront->pNext;
if (m_pFront == NULL)
{
m_pRear = NULL;
}
delete pTemp;
return nValue;
}
完整实现
Front操作、Rear操作、IsEmpty操作、Size操作都比较简单,这里就不再赘述了。CLinkedListQueue类的完整实现,可参考下面的示例代码。
cpp
#include <iostream>
#include <stdexcept>
using namespace std;
class CLinkedListQueue
{
public:
CLinkedListQueue();
~CLinkedListQueue();
void Enqueue(int nValue);
int Dequeue();
int Front();
int Rear();
bool IsEmpty();
int Size();
private:
struct Node
{
int nData;
Node* pNext;
};
Node* m_pFront;
Node* m_pRear;
};
CLinkedListQueue::CLinkedListQueue() : m_pFront(NULL), m_pRear(NULL)
{
NULL;
}
CLinkedListQueue::~CLinkedListQueue()
{
while (!IsEmpty())
{
Dequeue();
}
}
void CLinkedListQueue::Enqueue(int nValue)
{
Node* pNode = new Node();
pNode->nData = nValue;
pNode->pNext = NULL;
if (IsEmpty())
{
m_pFront = m_pRear = pNode;
}
else
{
m_pRear->pNext = pNode;
m_pRear = pNode;
}
}
int CLinkedListQueue::Dequeue()
{
if (IsEmpty())
{
throw underflow_error("queue is empty");
}
Node* pTemp = m_pFront;
int nValue = pTemp->nData;
m_pFront = m_pFront->pNext;
if (m_pFront == NULL)
{
m_pRear = NULL;
}
delete pTemp;
return nValue;
}
int CLinkedListQueue::Front()
{
if (IsEmpty())
{
throw underflow_error("queue is empty");
}
return m_pFront->nData;
}
int CLinkedListQueue::Rear()
{
if (IsEmpty())
{
throw underflow_error("queue is empty");
}
return m_pRear->nData;
}
bool CLinkedListQueue::IsEmpty()
{
return m_pFront == NULL;
}
int CLinkedListQueue::Size()
{
int nSize = 0;
Node* pTemp = m_pFront;
while (pTemp != NULL)
{
++nSize;
pTemp = pTemp->pNext;
}
return nSize;
}
int main()
{
CLinkedListQueue q;
q.Enqueue(66);
q.Enqueue(77);
q.Enqueue(88);
cout << "Front element: " << q.Front() << endl;
cout << "Rear element: " << q.Rear() << endl;
cout << "Dequeue: " << q.Dequeue() << endl;
cout << "Dequeue: " << q.Dequeue() << endl;
cout << "Current size: " << q.Size() << endl;
cout << "Is empty: " << (q.IsEmpty() ? "True" : "False") << endl;
return 0;
}