栈
一种特殊的线性表,其只允许在在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一段称为栈顶,另一端称为栈底。不含任何元素的栈称为空栈,栈又称为后进先出的线性表。
顺序栈
顺序堆栈和顺序表数据成员相同,不同之处,顺序堆栈的入栈和出栈操作只允许对当前栈顶进行
特点:顺序栈的实现在于使用了数组这个基本数据结构,数组中的元素在内存中的存储位置是连续的,且编译器要求我们在编译期就要确定数组的大小,这样对内存的使用效率并不高,一来无法避免因数组空间用光而引起的溢出问题,二在系统将内存分配给数组后,则这些内存对于其他任务就不可用
共享栈
如果有两个同类型的栈,为他们各自开辟了数组空间,极有可能第一个栈已经满了,在进栈就溢出,而另一个栈还有很多存储空间空闲,对空间的利用率不是很高。
原理:既然是两个栈共享一段空间向中间靠拢。用数组的0号位置标记stack1的栈底,top1标记stack1的栈顶;用size-1标记stack2的栈顶,用top2标记栈stack2的栈顶,top1和top2向中间移动,只要top1和top2不见面则栈未满。
#include <iostream>
#include <assert.h>
using namespace std;
template<class T, int N>
class SharedStack
{
public:
SharedStack()
:_s1(0)
,_s2(N-1)
{}
void Push(const T& data, int which)
{
assert(which == 1 || which == 2);
assert(_s1 <= _s2);
if(which == 1)
arr[_s1++] = data;
else
arr[_s2--] = data;
}
void Pop(int which)
{
assert((which == 1 || which == 2) && (_s1 != 0 || _s2 != N-1));
if(which == 1)
_s1--;
else
_s2++;
}
bool Empty()
{
return _s1 == 0 && _s2 == N-1;
}
size_t Size(int which)
{
if(which == 1)
return _s1;
return N - _s2 - 1;
}
T& Top(int which)
{
assert((which == 1 || which == 2) && (_s1 != 0 || _s2 != N-1));
if(which == 1)
return arr[_s1-1];
return arr[_s2+1];
}
private:
T arr[N];
int _s1;
int _s2;
};
int main()
{
SharedStack<int, 5> ss;
ss.Push(1, 1);
ss.Push(2, 1);
ss.Push(3, 1);
cout<<ss.Top(1)<<endl;
cout<<ss.Size(1)<<endl;
cout<<ss.Empty()<<endl;
ss.Push(1, 2);
ss.Push(2, 2);
cout<<ss.Top(2)<<endl;
cout<<ss.Size(2)<<endl;
ss.Pop(1);
ss.Pop(1);
ss.Pop(1);
cout<<ss.Size(1)<<endl;
ss.Pop(2);
ss.Pop(2);
cout<<ss.Size(2)<<endl;
cout<<ss.Empty()<<endl;
return 0;
}
链式栈
使用了链表来实现栈,链表中的元素存储在不连续的地址,由于是动态申请内存,所以我们可以以非常小的内存空间开始,另外当某个项不使用时也可将内存返还给系统。
#include <iostream>
#include <assert.h>
using namespace std;
template<class T>
struct Node
{
Node(const T& data = T())
:data(data)
,next(0)
{}
T data;
Node* next;
};
template<class T>
class LinkStack
{
public:
LinkStack()
:_pNode(NULL)
{}
void Push(const T& data)
{
Node<T>* tmp = new Node<T>(data);
tmp->data = data;
if(_pNode != NULL)
tmp->next = _pNode;
_pNode = tmp;
}
void Pop()
{
assert(_pNode);
Node<T>* tmp = _pNode;
if(_pNode->next)
_pNode = _pNode->next;
else
_pNode = NULL;
free(tmp);
tmp = NULL;
}
bool Empty()
{
return NULL == _pNode;
}
size_t Size()
{
size_t size = 0;
Node<T>* tmp = _pNode;
while(tmp)
{
size++;
tmp = tmp->next;
}
return size;
}
T& Top()
{
assert(_pNode);
return _pNode->data;
}
~LinkStack()
{
while(_pNode)
{
Node<T>* tmp = _pNode;
if(_pNode->next)
_pNode = _pNode->next;
free(tmp);
tmp = NULL;
}
}
private:
Node<T>* _pNode;
};
int main()
{
LinkStack<int> ls;
ls.Push(1);
ls.Push(2);
ls.Push(3);
ls.Push(4);
cout<<ls.Top()<<endl;
cout<<ls.Size()<<endl;
cout<<ls.Empty()<<endl;
ls.Pop();
cout<<ls.Top()<<endl;
cout<<ls.Size()<<endl;
ls.Pop();
ls.Pop();
ls.Pop();
cout<<ls.Empty()<<endl;
return 0;
}
运行结果:
队列
只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。进行插入操作的一端称为队尾,通常称为入队列;进行删除操作的一端称为队头,通常称为出队列。队列具有先进先出的特性(FIFO)。
顺序队列
因多次入队列和出队列操作后出现的尚有存储空间但不能再进行入队列操作的溢出称作为假溢出。顺序队列最大存储空间已经存满而又要求进行入队列操作所引起的溢出称为真溢出。
循环队列
解决“假溢出”的办法就是后面满了,就从头再开始,也就是头尾相接的循环。 将头尾相接的顺序存储队列称为循环队列。
队列满和队列空判断
1.少用一个存储单元
如果少用一个存储空间,则队尾指针加1等于队头指针
此时判断队列空的条件为:rear == front
判断队列满的条件为:(rear+1)%MaxSize==front
#include <iostream>
#include <assert.h>
using namespace std;
template<class T, int N = 5>
class Queue
{
public:
Queue()
:_front(0)
,_rear(0)
{}
void Push(const T& data)
{
assert((_rear+1)%N != _front);
arr[_rear] = data;
_rear = (_rear+1)%N;
}
void Pop()
{
assert(_rear != _front);
_front = (_front+1)%N;
}
bool Empty()const
{
return _rear == _front;
}
size_t Size()const
{
return (_rear - _front + N)%N;
}
T& Front()
{
assert(_rear != _front);
return arr[_front];
}
const T& Front()const
{
assert(_rear != _front);
return arr[_front];
}
T& Back()
{
assert(_rear != _front);
return arr[(_rear-1+N)%N];
}
const T& Back()const
{
assert(_rear != _front);
return arr[(_rear-1+N)%N];
}
private:
T arr[N];
int _front;
int _rear;
};
int main()
{
Queue<int, 5> q;
q.Push(1);
q.Push(2);
q.Push(3);
q.Push(4);
cout<<q.Back()<<endl;
cout<<q.Front()<<endl;
cout<<q.Size()<<endl;
q.Pop();
q.Push(5);
cout<<q.Back()<<endl;
cout<<q.Front()<<endl;
cout<<q.Size()<<endl;
q.Pop();
q.Pop();
q.Pop();
q.Pop();
cout<<q.Empty()<<endl;
return 0;
}
2. 设置一个标记为
设置标记为flag,初始时置flag = 0;每当入队列操作成功,就置flag=1;每当出队列成功就置flag=0.
此时判断队列空的条件为:rear == front && flag == 0
判断队列满的条件为:rear == front&& flag == 1
#include <iostream>
#include <assert.h>
using namespace std;
template<class T, int N = 5>
class Queue
{
public:
Queue()
:_front(0)
,_rear(0)
,_flag(0)
{}
void Push(const T& data)
{
assert(_flag != 1 || _rear != _front);
arr[_rear] = data;
_rear = (_rear+1)%N;
_flag = 1;
}
void Pop()
{
assert(_flag != 0 || _rear != _front);
_front = (_front+1)%N;
_flag = 0;
}
bool Empty()const
{
return _flag == 0 && _rear == _front;
}
size_t Size()const
{
if(_rear == _front && _flag == 1)
return N;
else if(_rear == _front && _flag == 0)
return 0;
else
return (_rear - _front + N)%N;
}
T& Front()
{
assert(_flag != 0 || _rear != _front);
return arr[_front];
}
const T& Front()const
{
assert(_flag != 0 || _rear != _front);
return arr[_front];
}
T& Back()
{
assert(_flag != 0 || _rear != _front);
return arr[(_rear-1+N)%N];
}
const T& Back()const
{
assert(_flag != 0 || _rear != _front);
return arr[(_rear-1+N)%N];
}
private:
T arr[N];
int _front;
int _rear;
int _flag;
};
int main()
{
Queue<int, 5> q;
q.Push(1);
q.Push(2);
q.Push(3);
q.Push(4);
q.Push(5);
cout<<q.Back()<<endl;
cout<<q.Front()<<endl;
cout<<q.Size()<<endl;
q.Pop();
q.Push(6);
cout<<q.Back()<<endl;
cout<<q.Front()<<endl;
cout<<q.Size()<<endl;
q.Pop();
q.Pop();
q.Pop();
q.Pop();
q.Pop();
cout<<q.Empty()<<endl;
return 0;
}
3.设置一个计数器
设置计数器count,初始时置count=0,每当入队列操作成功,就是count加1,每当出队列成功,就使count减1。
此时队列空的判断条件为:count == 0
判断队列满的条件为:count>0 && rear ==front 或者count == MaxSize
#include <iostream>
#include <assert.h>
using namespace std;
template<class T, int N = 5>
class Queue
{
public:
Queue()
:_front(0)
,_rear(0)
,_count(0)
{}
void Push(const T& data)
{
assert(_count < N);
arr[_rear] = data;
_count++;
_rear = (_rear+1)%N;
}
void Pop()
{
assert(_count);
_count--;
_front = (_front+1)%N;
}
bool Empty()const
{
return _count == 0;
}
size_t Size()const
{
return _count;
}
T& Front()
{
assert(_count);
return arr[_front];
}
const T& Front()const
{
assert(_count);
return arr[_front];
}
T& Back()
{
assert(_count);
return arr[(_rear-1+N)%N];
}
const T& Back()const
{
assert(_count);
return arr[(_rear-1+N)%N];
}
private:
T arr[N];
int _front;
int _rear;
int _count;
};
int main()
{
Queue<int, 5> q;
q.Push(1);
q.Push(2);
q.Push(3);
q.Push(4);
q.Push(5);
cout<<q.Back()<<endl;
cout<<q.Front()<<endl;
cout<<q.Size()<<endl;
q.Pop();
q.Push(6);
cout<<q.Back()<<endl;
cout<<q.Front()<<endl;
cout<<q.Size()<<endl;
q.Pop();
q.Pop();
q.Pop();
q.Pop();
q.Pop();
cout<<q.Empty()<<endl;
return 0;
}
单是顺序存储,若不是循环队列,算法的时间性能不是很好,循环队列虽然能够解决问题,但循环队列又面临着数组可能会溢出的问题。
链式队列
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而异,将其称为链队列。
#include <iostream>
#include <assert.h>
using namespace std;
template<class T>
struct Node
{
Node(const T& data = T())
:data(data)
,next(0)
{}
T data;
Node* next;
};
template<class T>
class LinkQueue
{
public:
LinkQueue()
:_pHead(NULL)
,_pTail(NULL)
{}
void Push(const T& data)
{
if(_pTail)
{
_pTail->next = new Node<T>(data);
_pTail = _pTail->next;
}
else
{
_pHead = new Node<T>(data);
_pTail = _pHead;
}
}
void Pop()
{
assert(_pHead);
Node<T>* tmp = _pHead;
if(_pHead->next)
_pHead = _pHead->next;
else
{
_pHead = NULL;
_pTail = NULL;
}
free(tmp);
tmp = NULL;
}
bool Empty()
{
return NULL == _pHead;
}
size_t Size()
{
size_t size = 0;
Node<T>* tmp = _pHead;
while(tmp)
{
size++;
tmp = tmp->next;
}
return size;
}
T& Front()
{
assert(_pHead);
return _pHead->data;
}
T& Back()
{
assert(_pTail);
return _pTail->data;
}
~LinkQueue()
{
while(_pHead)
{
Node<T>* tmp = _pHead;
if(_pHead->next)
_pHead = _pHead->next;
free(tmp);
tmp = NULL;
_pHead = NULL;
}
}
private:
Node<T>* _pHead;
Node<T>* _pTail;
};
int main()
{
LinkQueue<int> lq;
lq.Push(1);
lq.Push(2);
lq.Push(3);
lq.Push(4);
cout<<lq.Front()<<endl;
cout<<lq.Back()<<endl;
cout<<lq.Size()<<endl;
cout<<lq.Empty()<<endl;
lq.Pop();
cout<<lq.Front()<<endl;
cout<<lq.Back()<<endl;
cout<<lq.Size()<<endl;
lq.Pop();
lq.Pop();
lq.Pop();
cout<<lq.Empty()<<endl;
return 0;
}
运行结果: