在前面两节简单线性表(顺序存储)和链表(单链表、循环链表、双向链表、静态链表)的基础上进一步学习栈和队列就相对容易了,栈和队列都可以看成特殊的线性表结构,不同的是栈的删除和插入操作只能在表的一端操作,而队列的删除和插入也固定在表的两端。由于线性表又顺序存储和链式存储两种结构,因此栈和队列也分别有“顺序栈”和“链栈”以及“顺序队列”和“链式队列”之分。下面分别介绍栈和队列的这四种结构。
栈是一种后进先出(LIFO)的线性表。
我们用数组来实现顺序栈,并以下标为0的一端作为栈底。
下面直接用C++类模板实现:
/* 顺序栈
* 主要接口:
* 1、Push:进栈
* 2、Pop:出栈
* 3、GetTop:返回栈顶元素
*/
#define EMPTYSTACK -1
template
class SeqStack
{
private:
int m_ntop;//栈顶元素位置
T *m_pstack;//栈元素指针
int m_nMaxSize;//顺序栈大小
public:
SeqStack(int sz) :m_ntop(-1), m_nMaxSize(sz)
{
m_pstack = new T[sz];//为栈开辟空间
if (m_pstack == NULL)//空间开辟失败
{
cout << "栈创建失败!" << endl;
exit(1);
}
}
~SeqStack()
{
delete []m_pstack;//释放栈空间
}
//入栈
void Push(const T val);
//出栈
T Pop();
//获取栈顶元素
T GetTop() const;
//输出栈
void Print();
//置空栈
void MakeEmpty()
{
m_ntop = -1;
}
//判空
bool IsEmpty() const
{
return m_ntop == -1;
}
//判满
bool IsFull()
{
return m_ntop == (m_nMaxSize - 1);
}
};
//入栈
template
void SeqStack::Push(const T val)
{
if (IsFull())
{
cout << "栈已满!" << endl;
return;
}
m_pstack[++m_ntop] = val;
}
//出栈
template
T SeqStack::Pop()
{
if (IsEmpty())
{
cout << "栈为空!" << endl;
return EMPTYSTACK;
}
return m_pstack[m_ntop--];
}
//获取栈顶元素
template
T SeqStack::GetTop() const
{
if (IsEmpty())
{
cout << "栈为空!" << endl;
return EMPTYSTACK;
}
return m_pstack[m_ntop];
}
//输出栈
template
void SeqStack::Print()
{
cout << "top";
for (int i = m_ntop; i >= 0; i--)
{
cout << "--->" << m_pstack[i];
}
cout << "--->bottom" << endl;
}
链式栈用单链表来实现,将栈顶放在单链表的头部。除了插入和删除操作不同外,链式栈和单链表大部分操作相同。
//StackNode.h
//链式栈节点
templateclass LinkStack;
template
class StackNode
{
private:
friend class LinkStack;//将LinkStack声明为友元类
StackNode(const T item, StackNode *next = NULL) :m_data(item), m_pnext(next){}
~StackNode()
{
m_pnext = NULL;
}
private:
T m_data;
StackNode *m_pnext;
};
//LinkStack.h
/* 链式栈
* 主要接口:
* 1、Push:进栈
* 2、Pop:出栈
* 3、GetTop:返回栈顶元素
*/
#include "StackNode.h"
template
class LinkStack
{
private:
StackNode *m_ptop;
public:
LinkStack() :m_ptop(NULL){}
~LinkStack()
{
MakeEmpty();
}
void Push(const T val); //入栈
T Pop(); //出栈
T GetTop() const; //获取栈顶元素
void MakeEmpty(); //置空栈
void Print(); //输出栈
//判空
bool IsEmpty() const
{
return m_ptop == NULL;
}
};
//入栈
template
void LinkStack::Push(const T val)
{
m_ptop = new StackNode(val, m_ptop);
}
//出栈
template
T LinkStack::Pop()
{
if (IsEmpty())
{
cout << "栈为空!" << endl;
exit(1);
}
StackNode *pdel = m_ptop;
m_ptop = m_ptop->m_pnext;
T temp = pdel->m_data;
delete pdel;
return temp;
}
//获取栈顶元素
template
T LinkStack::GetTop() const
{
if (IsEmpty())
{
cout << "栈为空" << endl;
exit(1);
}
return m_ptop->m_data;
}
//置空栈
template
void LinkStack::MakeEmpty()
{
StackNode *pmove;
while (m_ptop != NULL)
{
pmove = m_ptop;
m_ptop = m_ptop->m_pnext;
delete pmove;
}
}
//输出栈
template
void LinkStack::Print()
{
StackNode *pmove = m_ptop;
cout << "top";
while (pmove != NULL)
{
cout << "--->" << pmove->m_data;
pmove = pmove->m_pnext;
}
cout << "--->bottom" << endl;
}
测试代码:
#include
#include "SeqStack.h"
#include "LinkStack.h"
using namespace std;
int main(void)
{
SeqStack mystack(10);
//LinkStack mystack;
int myarray[10] = { 2, 5, 8, 9, 22, 34, 56, 89, 99, 111 };
for (int i = 0; i < 10; i++)
{
mystack.Push(myarray[i]);
}
mystack.Print();
mystack.Pop();
mystack.Print();
cout << mystack.GetTop() << endl;
return 0;
}
top--->111--->99--->89--->56--->34--->22--->9--->8--->5--->2--->bottom
top--->99--->89--->56--->34--->22--->9--->8--->5--->2--->bottom
99
队列是一种先进先出(FIFO)的线性表。
顺序队列同样是基于数组实现的,数组下标为0的位置存储队头,队列后面的元素在数组中依次存储。入队操作就是在队尾追加一个元素。
出队操作有两种选择:
(1) 每次从队头移出一个元素,剩下元素均向前移动一位,这种做法对于较大规模大的队列显然是不行的。
(2) 直接移出队头的元素,其他元素位置不变,只要将数组下一个下标元素标记为队头即可(通过队头位指针可以实现)。
但是这种做法会产生“假溢出”现象,即随着队头元素的不断移出和队尾元素的不断追加,队头和队尾均到达了数组最大下标处,继续执行入队操作会产生数组越界的错误,看起来整个队列已满,但实际上队头前面的位置均空闲。
我们可以通过“循环队列”解决“假溢出”问题,即将队列首尾相接,在产生遇到上述情况时将队尾指针rear指向数组下标为0的位置。
这样又会产生一个新的问题,怎么判断队列已满?front == rear?显然不行,因为队列为空时也满足这个条件。
有以下三种方法可解决:
(1)设置一个标记变量flag,当front == rear 且flag = 0时队列为空,当front == rear 且flag = 1时队列已满;
(2)在数组中保留一个位置,当(rear+1) % QueueSize == front时可认为队列已满,QueueSize为队列的最大规模;
(3)最后一种方法是用一个计数器记录队列中元素的总数,当队列元素个数等于QueueSize时可认为队列已满。
下面上代码:
/* 顺序队列
* 主要接口:
* 1、Append:入队
* 2、Delete:出队
* 3、GetFront:获取队头元素
*/
#define EMPTYQUEUE -1
#define FULLQUEUE -2
template
class SeqQueue
{
private:
int m_nrear;
int m_nfront;
int m_ncount;
int m_nMaxSize;
T *m_pqueue;
public:
SeqQueue(int sz) :m_nrear(0), m_nfront(0), m_ncount(0), m_nMaxSize(sz)
{
m_pqueue = new T[sz];
if (m_pqueue == NULL)
{
cout << "队列创建失败!" << endl;
exit(1);
}
}
~SeqQueue()
{
delete[] m_pqueue;
}
bool Append(const T item); //入队
T Delete(); //出队
T GetFront(); //获取队头元素
void MakeEmpty(); //置空队
void Print(); //输出队
//判空
bool IsEmpty()
{
return m_ncount == 0;
}
//判满
bool IsFull()
{
return m_ncount == m_nMaxSize;
}
};
//入队
template
bool SeqQueue::Append(const T item)
{
if (IsFull())
{
cout << "队列已满!" << endl;
return FULLQUEUE;
}
m_pqueue[m_nrear] = item;
m_nrear = (m_nrear + 1) % m_nMaxSize;
m_ncount++;
return 1;
}
//出队
template
T SeqQueue::Delete()
{
if (IsEmpty())
{
cout << "队列为空!" << endl;
return EMPTYQUEUE;
}
T temp = m_pqueue[m_nfront];
m_nfront = (m_nfront + 1) % m_nMaxSize;
m_ncount--;
return temp;
}
//获取队头元素
template
T SeqQueue::GetFront()
{
if (IsEmpty())
{
cout << "队列为空!" << endl;
return EMPTYQUEUE;
}
return m_pqueue[m_nfront];
}
//置空队
template
void SeqQueue::MakeEmpty()
{
this->m_ncount = 0;
this->m_nfront = 0;
this->m_nrear = 0;
}
//输出队
template
void SeqQueue::Print()
{
cout << "front";
for (int i = 0; i < m_ncount; i++)
{
cout << "--->" << m_pqueue[(m_nfront + i + m_nMaxSize) % m_nMaxSize];
}
cout << "--->rear" << endl;
}
具体操作接口请看代码:
//QueueNode.h
//队列节点
template class LinkQueue;
template
class QueueNode{
private:
friend class LinkQueue;
QueueNode(const T item, QueueNode *next = NULL)
:m_data(item), m_pnext(next){}
private:
T m_data;
QueueNode *m_pnext;
};
//LinkQueue.h
/* 链式队列
* 主要接口:
* 1、Append:入队
* 2、Delete:出队
* 3、GetFront:获取队头元素
*/
#include "QueueNode.h"
#define EMPTYQUEUE -1
template class LinkQueue
{
private:
QueueNode *m_prear, *m_pfront;
public:
LinkQueue() :m_prear(NULL), m_pfront(NULL){}
~LinkQueue()
{
MakeEmpty();
}
void Append(const T item); //入队
T Delete(); //出队
T GetFront(); //获取队头元素
void MakeEmpty(); //置空队
void Print(); //输出队
//判空
bool IsEmpty() const
{
return m_pfront == NULL;
}
};
//入队
template
void LinkQueue::Append(const T item)
{
if (m_pfront == NULL)
{
m_pfront = m_prear = new QueueNode(item);
}
else
{
m_prear = m_prear->m_pnext = new QueueNode(item);
}
}
//出队
template
T LinkQueue::Delete()
{
if (IsEmpty())
{
cout << "队列为空!" << endl;
return EMPTYQUEUE;
}
QueueNode *pdel = m_pfront;
T temp = m_pfront->m_data;
m_pfront = m_pfront->m_pnext;
delete pdel;
return temp;
}
//获取队头元素
template
T LinkQueue::GetFront()
{
if (IsEmpty())
{
cout << "队列为空!" << endl;
return EMPTYQUEUE;
}
return m_pfront->m_data;
}
//置空队
template
void LinkQueue::MakeEmpty()
{
QueueNode *pdel;
while (m_pfront)
{
pdel = m_pfront;
m_pfront = m_pfront->m_pnext;
delete pdel;
}
}
//输出队
template
void LinkQueue::Print()
{
QueueNode *pmove = m_pfront;
cout << "front";
while (pmove)
{
cout << "--->" << pmove->m_data;
pmove = pmove->m_pnext;
}
cout << "--->rear" << endl << endl << endl;
}
测试代码:
#include
#include "SeqQueue.h"
#include "LinkQueue.h"
using namespace std;
int main(void)
{
//SeqQueue myqueue(10);
LinkQueue myqueue;
int myarray[10] = { 2, 5, 8, 9, 22, 34, 56, 89, 99, 111 };
for (int i = 0; i < 10; i++)
{
myqueue.Append(myarray[i]);
}
myqueue.Print();
myqueue.Delete();
myqueue.Print();
cout << myqueue.GetFront() << endl;
return 0;
}
输出结果:
front--->2--->5--->8--->9--->22--->34--->56--->89--->99--->111--->rear
front--->5--->8--->9--->22--->34--->56--->89--->99--->111--->rear
5
总结:线性类型数据结构的介绍暂时告一段落了,回顾简单线性表(顺序存储)和链表(单链表、循环链表、双向链表、静态链表),可知STL中的和双向链表对应,而双向队列
快速增删元素的特点。
以上。
ref:
《大话数据结构》