数据结构——栈和队列

在前面两节简单线性表(顺序存储)和链表(单链表、循环链表、双向链表、静态链表)的基础上进一步学习栈和队列就相对容易了,栈和队列都可以看成特殊的线性表结构,不同的是栈的删除和插入操作只能在表的一端操作,而队列的删除和插入也固定在表的两端。由于线性表又顺序存储和链式存储两种结构,因此栈和队列也分别有“顺序栈”和“链栈”以及“顺序队列”和“链式队列”之分。下面分别介绍栈和队列的这四种结构。

1 栈

栈是一种后进先出(LIFO)的线性表。

数据结构——栈和队列_第1张图片

1.1 顺序栈

我们用数组来实现顺序栈,并以下标为0的一端作为栈底。

数据结构——栈和队列_第2张图片


下面直接用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;
}


1.2 链式栈

链式栈用单链表来实现,将栈顶放在单链表的头部。除了插入和删除操作不同外,链式栈和单链表大部分操作相同。

数据结构——栈和队列_第3张图片

//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



2 队列

队列是一种先进先出(FIFO)的线性表。

数据结构——栈和队列_第4张图片



2.1 顺序队列

顺序队列同样是基于数组实现的,数组下标为0的位置存储队头,队列后面的元素在数组中依次存储。入队操作就是在队尾追加一个元素。

数据结构——栈和队列_第5张图片

出队操作有两种选择:

(1) 每次从队头移出一个元素,剩下元素均向前移动一位,这种做法对于较大规模大的队列显然是不行的。

数据结构——栈和队列_第6张图片

(2) 直接移出队头的元素,其他元素位置不变,只要将数组下一个下标元素标记为队头即可(通过队头位指针可以实现)。

数据结构——栈和队列_第7张图片

但是这种做法会产生“假溢出”现象,即随着队头元素的不断移出和队尾元素的不断追加,队头和队尾均到达了数组最大下标处,继续执行入队操作会产生数组越界的错误,看起来整个队列已满,但实际上队头前面的位置均空闲。

数据结构——栈和队列_第8张图片

我们可以通过“循环队列”解决“假溢出”问题,即将队列首尾相接,在产生遇到上述情况时将队尾指针rear指向数组下标为0的位置。

数据结构——栈和队列_第9张图片

这样又会产生一个新的问题,怎么判断队列已满?front == rear?显然不行,因为队列为空时也满足这个条件。

数据结构——栈和队列_第10张图片


有以下三种方法可解决:

(1)设置一个标记变量flag,当front == rear 且flag = 0时队列为空,当front == rear 且flag = 1时队列已满;

(2)在数组中保留一个位置,当(rear+1) % QueueSize == front时可认为队列已满,QueueSize为队列的最大规模;

数据结构——栈和队列_第11张图片

(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;
}


2.2 链式队列

链式队列同样是基于单链表的,只不过该链表只能尾进头出。数据结构——栈和队列_第12张图片

具体操作接口请看代码:

//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中的/顺序表对应,STL中的和双向链表对应,而双向队列本身兼是连续或分段连续的数组,从而兼顾了快速获取查找和快速增删元素的特点。均是基于进行拓展而得。

数据结构——栈和队列_第13张图片


以上。


ref:

《大话数据结构》


你可能感兴趣的:(#,【数据结构与算法】)