# 数据结构开发(12)：栈与队列

## 1.栈的概念及实现

#### 1.1 顺序栈的实现

• 栈是一种特殊的线性表
• 栈仅能在线性表的一端进行操作
1. 栈顶(Top)：允许操作的一端
2. 栈底(Bottom)：不允许操作的一端

• 创建栈( Stack() )
• 销毁栈( ~Stack() )
• 清空栈( clear() )
• 进栈( push() )
• 出栈( pop() )
• 获取栈顶元素( top() )
• 获取栈的大小( size() )

StaticStack 设计要点：

• 类模板
1. 使用原生数组作为栈的存储空间
2. 使用模板参数决定栈的最大容量

Stack.h

``````#ifndef STACK_H
#define STACK_H

#include "Object.h"

namespace StLib
{

template <typename T>
class Stack : public Object
{
public:
virtual void push(const T& e) = 0;
virtual void pop() = 0;
virtual T top() const = 0;
virtual void clear() = 0;
virtual int size() const = 0;
};

}

#endif // STACK_H``````

StaticStack.h

``````#ifndef STATICSTACK_H
#define STATICSTACK_H

#include "Stack.h"
#include "Exception.h"

namespace StLib
{

template <typename T, int N>
class StaticStack : public Stack<T>
{
protected:
T m_space[N]; // 栈存储空间，N为模板参数
int m_top;    // 栈顶标识
int m_size;   // 当前栈大小
public:
StaticStack()
{
m_top = -1;
m_size = 0;
}

int capacity() const
{
return N;
}

void push(const T& e)
{
if( m_size < N )
{
m_space[m_top + 1] = e;
m_top++;
m_size++;
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No space in current stack ...");
}
}

void pop()
{
if( m_size > 0 )
{
m_top--;
m_size--;
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current stack ...");
}
}

T top() const
{
if( m_size > 0 )
{
return m_space[m_top];
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current stack ...");
}
}

void clear()
{
m_top = -1;
m_size = 0;
}

int size() const
{
return m_size;
}
};

}

#endif // STATICSTACK_H``````

main.cpp测试

``````#include <iostream>
#include "StaticStack.h"

using namespace std;
using namespace StLib;

int main()
{
StaticStack<int, 5> stack;

try
{
stack.pop();
}
catch(const Exception& e)
{
cout << e.message() << endl;
cout << e.location() << endl;
}

for(int i=0; i<5; i++)
{
stack.push(i);
}

while( stack.size() > 0 )
{
cout << stack.top() << endl;

stack.pop();
}

return 0;
}``````

``````No element in current stack ...
f:\allcode\qtcreator\datastructure\stlib\StaticStack.h:52
4
3
2
1
0``````

（StaticStack是有缺陷的。当存储的元素为类类型时，StaticStack的对象在创建时，会多次调用元素类型的构造函数，影响效率。）

``````#include <iostream>
#include "StaticStack.h"

using namespace std;
using namespace StLib;

class Test : public Object
{
public:
Test()
{
cout << "Test()" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
};

int main()
{
StaticStack<Test, 10> stack;

cout << stack.size() << endl;

return 0;
}``````

``````Test()
Test()
Test()
Test()
Test()
Test()
Test()
Test()
Test()
Test()
0
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()``````

#### 1.2 链式栈的实现

• 类模板，抽象父类 Stack 的直接子类
• 只在单链表成员对象的头部进行操作

``````#ifndef LINKSTACK_H

#include "Stack.h"

namespace StLib
{

template <typename T>
{
protected:
public:
void push(const T& e)
{
m_list.insert(0, e);
}

void pop()
{
if( m_list.length() > 0 )
{
m_list.remove(0);
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current stack ...");
}
}

T top() const
{
if( m_list.length() > 0 )
{
return m_list.get(0);
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current stack ...");
}
}

void clear()
{
m_list.clear();
}

int size() const
{
return m_list.length();
}
};

}

main.cpp测试

``````#include <iostream>

using namespace std;
using namespace StLib;

class Test : public Object
{
public:
Test()
{
cout << "Test()" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
};

int main()
{

cout << stack.size() << endl;

return 0;
}``````

``0``

• 符号匹配问题
1. 在C语言中有一些成对匹配出现的符号
1. 括号：( ), [ ], { }, < >
2. 引号：’ ‘, ” “

• 从第一个字符开始扫描
1. 当遇见普通字符时忽略
2. 当遇见左符号时压入栈中
3. 当遇见右符号时弹出栈顶符号，并进行匹配
• 结束
1. 成功：所有字符扫描完毕，且栈为空
2. 失败：匹配失败或所有字符扫描完毕但栈非空

``````#include <iostream>

using namespace std;
using namespace StLib;

bool is_left(char c)
{
return (c == '(') || (c == '{') || (c == '[') || (c == '<');
}

bool is_right(char c)
{
return (c == ')') || (c == '}') || (c == ']') || (c == '>');
}

bool is_quot(char c)
{
return (c == '\'') || (c == '\"');
}

bool is_match(char l, char r)
{
return ( (l == '(') && (r == ')') ) ||
( (l == '{') && (r == '}') ) ||
( (l == '[') && (r == ']') ) ||
( (l == '<') && (r == '>') ) ||
( (l == '\'') && (r == '\'') ) ||
( (l == '\"') && (r == '\"') );
}

bool scan(const char* code)
{
int i = 0;
bool ret = true;

code = (code == NULL) ? "" : code;

while( ret && (code[i] != '\0') )
{
if( is_left(code[i]) )
{
stack.push(code[i]);
}
else if( is_right(code[i]) )
{
if( (stack.size() > 0) && is_match(stack.top(), code[i]) )
{
stack.pop();
}
else
{
ret = false;
}
}
else if( is_quot(code[i]) )
{
if( (stack.size() == 0) || !is_match(stack.top(), code[i]) )
{
stack.push(code[i]);
}
else if( is_match(stack.top(), code[i]) )
{
stack.pop();
}
}

i++;
}

return ret && (stack.size() == 0);
}

int main()
{
cout << scan("abcd") << endl;
cout << scan("<a{b(\'x\')c}d>") << endl;
cout << scan("({)}") << endl;
cout << scan("if((stack.size()==0)||!is_match(stack.top(),code[i])){stack.push(code[i]);}else if(is_match(stack.top(),code[i])){stack.pop();}") << endl;

return 0;
}``````

``````1
1
0
1``````

• 使用单链表对象实现链式栈时，为什么选择在单链表的头部进行操作？如果选择在尾部进行操作是否也能实现栈的功能？

## 2.队列的概念及实现

#### 2.1 队列的顺序实现

• 队头( Front )：取出数据元素的一端
• 队尾( Rear )：插入数据元素的一端

• 创建队列( Queue() )
• 销毁队列( ~Queue() )
• 清空队列( clear() )
• 出队列( remove() )
• 获取队头元素( front() )
• 获取队列的长度( length() )

StaticQueue 设计要点：

• 类模板
1. 使用原生数组作为队列的存储空间
2. 使用模板参数决定队列的最大容量

StaticQueue 实现要点（循环计数法）：

• 关键操作
1. 进队列：`m_space[m_rear] = e; m_rear = (m_rear + 1) % N;`
2. 出队列：`m_front = (m_front + 1) % N;`
• 队列的状态
1. 队空：`( m_length == 0 ) && ( m_front == m_rear )`
2. 队满：`( m_length == N ) && ( m_front == m_rear )`

Queue.h

``````#ifndef QUEUE_H
#define QUEUE_H

#include "Object.h"

namespace StLib
{

template <typename T>
class Queue : public Object
{
public:
virtual void add(const T& e) = 0;
virtual void remove() = 0;
virtual T front() const = 0;
virtual void clear() = 0;
virtual int length() const = 0;
};

}

#endif // QUEUE_H``````

StaticQueue.h

``````#ifndef STATICQUEUE_H
#define STATICQUEUE_H

#include "Queue.h"
#include "Exception.h"

namespace StLib
{

template <typename T, int N>
class StaticQueue : public Queue<T>
{
protected:
T m_space[N]; // 队列存储空间，N为模板参数
int m_front;  // 队头标识
int m_rear;   // 队尾标识
int m_length; // 当前队列的长度
public:
StaticQueue()
{
m_front = 0;
m_rear = 0;
m_length = 0;
}

int capacity() const
{
return N;
}

{
if( m_length < N )
{
m_space[m_rear] = e;
m_rear = (m_rear + 1) % N;
m_length++;
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No space in current queue ...");
}
}

void remove()
{
if( m_length > 0 )
{
m_front = (m_front + 1) % N;
m_length--;
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current queue ...");
}
}

T front() const
{
if( m_length > 0 )
{
return m_space[m_front];
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current queue ...");
}
}

void clear()
{
m_front = 0;
m_rear = 0;
m_length = 0;
}

int length() const
{
return m_length;
}
};

}

#endif // STATICQUEUE_H``````

main.cpp测试

``````#include <iostream>
#include "StaticQueue.h"

using namespace std;
using namespace StLib;

int main()
{
StaticQueue<int, 5> queue;

for(int i=0; i<5; i++)
{
}

while( queue.length() > 0 )
{
cout << queue.front() << endl;

queue.remove();
}

return 0;
}``````

``````0
1
2
3
4``````

（StaticQueue也是有缺陷的。类似的，当数据元素为类类型，StaticQueue的对象在创建时，会多次调用元素类型的构造函数，影响效率。）

#### 2.2 队列的链式实现

• 类模板，抽象父类 Queue 的直接子类
• 在内部使用链式结构实现元素的存储
• 只在链表的头部尾部进行操作

``````#ifndef LINKQUEUE_H

#include "Queue.h"

namespace StLib
{

template <typename T>
{
protected:
public:
{
m_list.insert(e);
}

void remove()
{
if( m_list.length() > 0 )
{
m_list.remove(0);
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current queue ...");
}
}

T front() const
{
if( m_list.length() > 0 )
{
return m_list.get(0);
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current queue ...");
}
}

void clear()
{
m_list.clear();
}

int length() const
{
return m_list.length();
}
};

}

main.cpp测试

``````#include <iostream>

using namespace std;
using namespace StLib;

int main()
{

for(int i=0; i<5; i++)
{
}

while( lq.length() > 0 )
{
cout << lq.front() << endl;

lq.remove();
}

return 0;
}``````

``````0
1
2
3
4``````

（链式队列的性能比较差，并不高效。插入操作经历了遍历的过程！时间复杂度为O(n)。）

``````#ifndef LINKQUEUE_H

#include "Queue.h"
#include "LinuxList.h"
#include "Exception.h"

namespace StLib
{

template <typename T>
{
protected:
struct Node : public Object
{
T value;
};

int m_length;
public:
{
m_length = 0;

}

{
Node* node = new Node();

if( node != NULL )
{
node->value = e;

m_length++;
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No memory to add new element ...");
}
}

void remove()
{
if( m_length > 0 )
{

list_del(toDel);

m_length--;

}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current queue ...");
}
}

T front() const
{
if( m_length > 0 )
{
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current queue ...");
}
}

void clear()
{
while( m_length > 0 )
{
remove();
}
}

int length() const
{
return m_length;
}

{
clear();
}
};

}

main.cpp测试

``````#include <iostream>

using namespace std;
using namespace StLib;

class Test : public Object
{
public:
Test()
{
cout << "Test()" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
};

int main()
{

cout << lq.length() << endl;

return 0;
}``````

``0``

## 3.两个有趣的问题

#### 3.1 是否可以用栈实现队列？

• 栈和队列在实现上非常类似，是否可以用栈实现队列？

• 用栈实现队列等价于用“后进先出”的特性实现“先进先出”的特性！

• 准备两个栈用于实现队列：stack_instack_out
1. 当有新元素入队时：将其压入 stack_in
2. 当需要出队时：
1. `stack_out.size() == 0` :
1. stack_in 中的元素逐一弹出并压入 stack_out
2. stack_out 的栈顶元素弹出
2. `stack_out.size() > 0` :
1. stack_out 的栈顶元素弹出

``````#include <iostream>

using namespace std;
using namespace StLib;

template <typename T>
class StackToQueue : public Queue<T>
{
protected:

void move() const
{
if( m_stack_out.size() == 0 )
{
while( m_stack_in.size() > 0 )
{
m_stack_out.push(m_stack_in.top());
m_stack_in.pop();
}
}
}
public:
{
m_stack_in.push(e);
}

void remove()
{
move();

if( m_stack_out.size() > 0 )
{
m_stack_out.pop();
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current queue ...");
}
}

T front() const
{
move();

if( m_stack_out.size() > 0 )
{
return m_stack_out.top();
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current queue ...");
}
}

void clear()
{
m_stack_in.clear();
m_stack_out.clear();
}

int length() const
{
return m_stack_in.size() + m_stack_out.size();
}
};

int main()
{
StackToQueue<int> sq;

for(int i=0; i<5; i++)
{
}

while( sq.length() > 0 )
{
cout << sq.front() << endl;

sq.remove();
}

return 0;
}``````

``````0
1
2
3
4``````

（虽然能用栈实现队列，但是效率不高。）

#### 3.2 是否可以用队列实现栈？

• 反之，是否可以用队列实现栈？

• 本质为，用队列“先进先出”的特性实现栈“后进先出”的特性！

• 准备两个队列用于实现栈：queue_1 [in]queue_2 [out]
1. 当有新元素入栈时：将其加入队列 [in]
2. 当需要出栈时：
1. 将队列 [in] 中的 n-1 个元素出队列，并进入队列 [out]
2. 将队列 [in] 中的最后一个元素出队列（出栈）
3. 交换两个队列的角色：queue_1 [out]queue_2 [in]

``````#include <iostream>

using namespace std;
using namespace StLib;

template <typename T>
class QueueToStack : public Stack<T>
{
protected:

void move() const
{
int n = m_pIn->length() - 1;

for(int i=0; i<n; i++)
{
m_pIn->remove();
}
}

void swap()
{

temp = m_pIn;
m_pIn = m_pOut;
m_pOut = temp;
}
public:
QueueToStack()
{
m_pIn = &m_queue_1;
m_pOut = &m_queue_2;
}

void push(const T& e)
{
}

void pop()
{
if( m_pIn->length() > 0 )
{
move();

m_pIn->remove();

swap();
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current stack ...");
}
}

T top() const
{
if( m_pIn->length() > 0 )
{
move();

return m_pIn->front();
}
else
{
THROW_EXCEPTION(InvalidOperationException, "No element in current stack ...");
}
}

void clear()
{
m_queue_1.clear();
m_queue_2.clear();
}

int size() const
{
return m_queue_1.length() + m_queue_2.length();
}
};

int main()
{
QueueToStack<int> qs;

for(int i=0; i<5; i++)
{
qs.push(i);
}

while( qs.size() > 0 )
{
cout << qs.top() << endl;

qs.pop();
}

return 0;
}``````

``````4
3
2
1
0``````

（虽然能用队列实现栈，但是效率不高。）

## 4.小结

• 栈是一种特殊的线性表
• 栈只允许在线性表的一端进行操作
• StaticStack 使用原生数组作为内部存储空间
• StaticStack 的最大容量由模板参数决定
• 链式栈的实现组合使用了单链表对象
• 在单链表的头部进行操作能够实现高效的入栈和出栈操作
• “后进先出”的特性适用于检测成对出现的符号
• 栈非常适合于需要“就近匹配”的场合
• 队列是一种特殊的线性表，具有先进先出的特性
• 队列只允许在线性表的两端进行操作，一端进，一端出
• StaticQueue 使用原生数组作为内部存储空间
• StaticQueue 的最大容量由模板参数决定
• StaticQueue 采用循环计数法提高队列操作的效率
• StaticQueue 在初始化时可能多次调用元素类型的构造函数