栈简单介绍
栈(Stack)是一种后进先出(LIFO,Last In First Out)的数据结构。它的操作特点是元素只能从一端进行添加和移除,这端叫做栈顶。常见操作包括:
- 压栈(push):将元素添加到栈顶。
- 出栈(pop):移除并返回栈顶元素。
- 查看栈顶元素(peek):获取栈顶元素但不移除。
栈常用于实现递归、括号匹配、浏览器的前进/后退功能等。
实验题目
铁路调度系统
实验内容
任务目标
“丁”字型铁路调度系统如下图所示,由垂直的两条铁轨组成,水平方向的为主铁轨,竖直方向的为辅助铁轨。辅助铁轨用于对车厢次序进行调整,它在主铁轨中间,把主铁轨分成左右两部分。主铁轨左边的车厢只能从左边开到右边;或者从主铁轨左边进入辅助铁轨;辅助铁轨上的车厢只可以进入主铁轨右边。
现在有n节车厢,编号为1、2、...、n,在主铁轨左边以随机顺序排列,要求通过调度系统,在主铁轨的右边顺序开出(即按1、2、...、n的顺序)。
基于栈和队列相关知识和代码实现,实现该铁路调度系统对车厢调度过程。
任务描述
- 可以手动输入待调度的车厢初始序列(编号之间用空格或换行分开),输出是否能调度成功,以及调度每一步的调度过程;并输出栈的总入栈(出栈)次数;
- 当n=100时,随机生成100个初始序列,测试这些初始序列能否被成功调度,并输出测试结果(包括100个初始序列以及其是否能被成功调度),并统计一下调度的成功率;
- 提供菜单,实现以上功能;
- 如要做的更好,可以考虑将调度过程动态显示,甚至是动画显示;
解决方案
设计思路
1. 数据结构选择:
使用了模板类 SeqStack 表示栈,用于模拟火车调度系统中的辅轨道。栈的实现使得车厢的入栈和出栈操作能够高效进行。
2. 主程序结构:
主程序采用了一个无限循环,允许用户选择手动输入车厢初始序列或随机生成100个初始序列并测试调度结果。这样的设计使得程序能够反复执行,并提供了不同的操作选择。
3. 手动输入调度:
用户通过输入车厢数和初始序列,模拟了手动输入待调度的车厢初始序列的场景。程序根据输入的顺序,模拟车厢的进入主轨道和辅轨道的过程,并输出每一步的调度过程。
4. 随机生成调度:
使用了 randperm 函数,该函数的实现未提供,但可以推测是用于生成随机的车厢初始序列。通过循环进行100次测试,每次测试都生成一个随机序列,并尝试进行调度,统计成功调度的次数和成功率。
算法设计
顺序栈模板类 的数据成员设计
template<class ElemType>
class SeqStack
{
protected:
// 顺序栈的数据成员:
int top; // 栈顶指针
int maxSize; // 栈的最大容量
ElemType* elems; // 元素存储空间
public:
// 顺序栈的方法声明及重载编译系统默认方法声明:
SeqStack(int size = DEFAULT_SIZE); // 构造函数
virtual ~SeqStack(); // 析构函数
int GetLength() const; // 求栈的长度
bool IsEmpty() const; // 判断栈是否为空
void Clear(); // 将栈清空
void Traverse(void (*Visit)(const ElemType&)) const; // 遍历栈
Status Push(const ElemType e); // 入栈
Status Top(ElemType& e) const; // 取顶元素
Status Pop(ElemType& e); // 出栈
SeqStack(const SeqStack<ElemType>& s); // 复制构造函数
SeqStack<ElemType>& operator =(const SeqStack<ElemType>& s); // 赋值语句重载
};
这段代码定义了一个模板类 `SeqStack`,表示一个基于数组实现的顺序栈。
1. 数据成员:
- - `top`:栈顶指针,指示当前栈顶元素的位置。
- - `maxSize`:栈的最大容量。
- - `elems`:动态数组,用于存储栈中的元素。
2. 构造函数和析构函数:
- - 构造函数初始化栈,可以指定最大容量,默认为 `DEFAULT_SIZE`。
- - 析构函数释放动态分配的内存。
3. 基本操作:
- - `GetLength`:返回栈的长度。
- - `IsEmpty`:判断栈是否为空。
- - `Clear`:清空栈。
- - `Traverse`:遍历栈中的元素,对每个元素执行传入的函数。
- - `Push`:将元素入栈。
- - `Top`:获取栈顶元素但不出栈。
- - `Pop`:将栈顶元素出栈。
4. 复制构造函数和赋值语句重载:
- - 复制构造函数用于根据已有的栈创建一个新的栈。
- - 赋值语句重载允许通过一个栈的内容赋值给另一个栈。
这个模板类提供了基本的栈操作,可以适用于不同类型的数据。使用时,可以通过实例化这个类来创建特定类型的顺序栈。
顺序栈模板类 的成员函数实现
template<class ElemType>
SeqStack<ElemType>::SeqStack(int size)
// 操作结果:构造一个最大容量为size的空栈
{
maxSize = size; // 栈的最大容量
if (elems != NULL) {delete[]elems;} // 释放已有存储空间
elems = new ElemType[maxSize]; // 分配新的存储空间
top = -1;
}
template<class ElemType>
SeqStack<ElemType>::~SeqStack()
// 操作结果:销毁栈
{
delete[]elems; // 释放栈的存储空间
}
template <class ElemType>
int SeqStack<ElemType>::GetLength() const
// 操作结果:返回栈中元素个数
{
return top + 1;
}
template<class ElemType>
bool SeqStack<ElemType>::IsEmpty() const
// 操作结果:如栈为空,则返回true,否则返回false
{
return top == -1;
}
template<class ElemType>
void SeqStack<ElemType>::Clear()
// 操作结果:清空栈
{
top = -1;
}
template <class ElemType>
void SeqStack<ElemType>::Traverse(void (*Visit)(const ElemType&)) const
// 操作结果:从栈顶到栈底依次对栈的每个元素调用函数(*visit)访问
{
for (int i = top; i >= 0; i--)
(*Visit)(elems[i]);
}
template<class ElemType>
Status SeqStack<ElemType>::Push(const ElemType e)
// 操作结果:将元素e追加到栈顶,如成功则返加SUCCESS,如栈已满将返回OVER_FLOW
{
if (top == maxSize - 1) // 栈已满
return OVER_FLOW;
else { // 操作成功
elems[++top] = e; // 将元素e追加到栈顶
return SUCCESS;
}
}
template<class ElemType>
Status SeqStack<ElemType>::Top(ElemType& e) const
// 操作结果:如栈非空,用e返回栈顶元素,函数返回SUCCESS,否则函数返回UNDER_FLOW
{
if (IsEmpty()) // 栈空
return UNDER_FLOW;
else { // 栈非空,操作成功
e = elems[top]; // 用e返回栈顶元素
return SUCCESS;
}
}
template<class ElemType>
Status SeqStack<ElemType>::Pop(ElemType& e)
// 操作结果:如栈非空,删除栈顶元素,并用e返回栈顶元素,函数返回SUCCESS,否则
// 函数返回UNDER_FLOW
{
if (IsEmpty()) // 栈空
return UNDER_FLOW;
else { // 操作成功
e = elems[top--]; // 用e返回栈顶元素
return SUCCESS;
}
}
template<class ElemType>
SeqStack<ElemType>::SeqStack(const SeqStack<ElemType>& s)
// 操作结果:由栈s构造新栈--复制构造函数
{
maxSize = s.maxSize; // 栈的最大容量
if (elems != NULL) delete[]elems; // 释放已有存储空间
elems = new ElemType[maxSize]; // 分配存储空间
top = s.top; // 复制栈顶指针
for (int i = 0; i <= top; i++) // 从栈底到栈顶对栈s的每个元素进行复制
elems[i] = s.elems[i];
}
template<class ElemType>
SeqStack<ElemType>& SeqStack<ElemType>::operator = (const SeqStack<ElemType>& s)
// 操作结果:将栈s赋值给当前栈--赋值语句重载
{
if (&s != this) {
maxSize = s.maxSize; // 栈的最大容量
if (elems != NULL) delete[]elems; // 释放已有存储空间
elems = new ElemType[maxSize]; // 分配存储空间
top = s.top; // 复制栈顶指针
for (int i = 0; i <= top; i++) // 从栈底到栈顶对栈s的每个元素进行复制
elems[i] = s.elems[i];
}
return *this;
}
这段代码是一个模板类 `SeqStack` 的实现,表示一个基于数组的顺序栈。
1. 构造函数:
- - 初始化栈的最大容量为给定的 `size`。
- - 释放已有的存储空间,然后分配新的存储空间。
2. 析构函数:
- - 释放动态分配的存储空间。
3. 获取栈长度:
- - 返回栈中元素的个数。
4. 判断栈是否为空:
- - 如果栈为空,返回 `true`;否则返回 `false`。
5. 清空栈:
- - 将栈清空,即将栈顶指针置为 -1。
6. 遍历栈:
- - 从栈顶到栈底依次对栈的每个元素调用传入的函数进行访问。
7. 入栈操作:
- - 将元素追加到栈顶,如果栈已满,则返回 `OVER_FLOW`。
8. 取栈顶元素:
- - 如果栈非空,将栈顶元素用参数返回,返回 `SUCCESS`;否则返回 `UNDER_FLOW`。
9. 出栈操作:
- - 如果栈非空,删除栈顶元素,将栈顶元素用参数返回,返回 `SUCCESS`;否则返回 `UNDER_FLOW`。
10. 复制构造函数:
- - 根据已有的栈 `s` 创建一个新的栈。
11. 赋值语句重载:
- - 将栈 `s` 的内容赋值给当前栈。
这个实现允许栈存储不同类型的元素,提供了基本的栈操作,并且支持复制构造和赋值语句的重载。
源程序代码 RailwayCarriage.cpp
#include "SeqStack.h" // 循环队列类
#include<vector>
#include<iostream>
int main(void)
{
int command;
while (1)
{
cout << "请选输入功能对应序号" << endl;
cout << "1.手动输入待调度的车厢初始序列(编号之间用空格或换行分开),输出是否能调度成功,以及调度每一步的调度过程;并输出栈的总入栈(出栈)次数;" << endl;
cout << "2.随机生成100个初始序列,测试这些初始序列能否被成功调度,并输出测试结果(包括100个初始序列以及其是否能被成功调度),并统计调度的成功率;" << endl;
cin >> command;
if (command == 1)
{
SeqStack<int> qa;
int n, x, d, No, num1, num2;
num1 = 0;//入栈次数
num2 = 0;//出栈次数
cout << "输入车厢数:";
cin >> n;
No = 1;
cout << "输入 " << n << " 节车厢的初始顺序:";
for (int i = 1; i <= n; i++) {
cin >> d;
if (d == No)
{
cout << "第 " << d << " 号车厢从主轨道左边进入主轨道右边." << endl;
No++;
}
else
{
qa.Push(d);
cout << "第 " << d << " 号车厢从主轨道左边进入辅轨道." << endl;
num1++;
}
while (qa.IsEmpty() == false)
{
if (qa.Top(x) == SUCCESS && x == No)
{
qa.Pop(x);
cout << "第 " << x << " 号车厢从辅轨道进入主轨道右边." << endl;
No++;
num2++;
}
else
break;
}
}
if (qa.IsEmpty())
cout << "调度完成." << endl;
else
cout << "调度无法完成." << endl;
cout << "总入栈次数" << num1 << endl;
cout << "总出栈次数" << num2 << endl;
}
if (command == 2)
{
SeqStack<int> qa;
int n, x, No, num1, num2, success_num = 0;//调度成功的次数
num1 = 0;//入栈次数
num2 = 0;//出栈次数
cout << "请输入单组数据的长度" << endl;
cin >> n;
int times = 100;
srand((int)time(0));
while (times--)
{
No = 1;
vector<int>temp;
randperm(temp, n);
for (int i = 0; i < n; i++)
{
if (temp[i] == No)
{
No++;
}
else
{
qa.Push(temp[i]);
num1++;
}
while (qa.IsEmpty() == false)
{
if (qa.Top(x) == SUCCESS && x == No)
{
qa.Pop(x);
No++;
num2++;
}
else
break;
}
}
if (qa.IsEmpty())
{
cout << "调度完成." << endl;
success_num++;
}
else
cout << "调度无法完成." << endl;
qa.Clear();
}
cout << success_num << "%" << endl;
}
system("pause");
system("cls");
}
system("pause");
return 0;
}
这是一个火车调度系统的主程序。
1. 包含头文件和库:
- - `Assistance.h` 和 `SeqStack.h` 是预定义的辅助软件包和循环队列类的头文件。
- - 使用了 `<vector>` 和 `<iostream>` 标准库。
2. 主函数:
- - 进入一个无限循环,允许用户选择功能。
- - 用户可以选择手动输入待调度的车厢初始序列,或者随机生成100个初始序列并测试调度结果。
- - 调用相应的函数完成用户选择的功能。
3. 手动输入待调度的车厢初始序列:
- - 使用 `SeqStack<int>` 类来模拟火车调度系统。
- - 用户输入车厢数 `n` 和初始顺序。
- - 车厢按照指定顺序进入主轨道和辅轨道,模拟调度过程。
- - 输出是否能够成功调度,以及每一步的调度过程。
- - 统计总入栈次数和总出栈次数。
4. 随机生成100个初始序列:
- - 使用 `randperm` 函数生成随机序列,该函数的实现未提供。
- - 循环进行100次测试,每次测试都会生成一个随机序列并尝试调度。
- - 输出每个初始序列以及是否能够成功调度。
- - 统计成功调度的次数,并输出成功率。
5. 总结:
- - 通过循环、条件判断和函数调用模拟了火车调度的过程。
- - 使用 `SeqStack` 类实现栈的功能,记录了总入栈次数和总出栈次数。
- - 通过手动输入和随机生成的方式测试了不同情况下的调度结果。
算法复杂度分析:
1. 手动输入待调度的车厢初始序列部分:
- - 用户输入操作:O(1)(假设输入操作的时间复杂度是常数时间)。
- - 遍历车厢序列:O(n)(n 为车厢的数量,需要遍历所有车厢)。
- - 主轨道和辅轨道的模拟操作:O(n)(与车厢数量成正比,每个车厢都需要相应的操作)。
- - 辅轨道的入栈和出栈操作:O(1)(由于使用了顺序栈 `SeqStack`,入栈和出栈的时间复杂度为常数时间)。
- 总体复杂度:O(n)
2. 随机生成100个初始序列并测试调度结果部分:
- - 循环测试操作:O(1)(假设循环测试的次数是常数)。
- - 生成随机序列:O(n)(假设 `randperm` 的复杂度与车厢数量成正比,需要遍历所有车厢)。
- - 主轨道和辅轨道的模拟操作:O(n)(与车厢数量成正比,每个车厢都需要相应的操作)。
- - 辅轨道的入栈和出栈操作:O(1)(由于使用了顺序栈 `SeqStack`,入栈和出栈的时间复杂度为常数时间)。
- 总体复杂度:O(n)
3. SeqStack 类的实现部分:
- - 栈的入栈和出栈操作:O(1)(由于采用顺序栈,入栈和出栈的时间复杂度为常数时间)。
- - 遍历栈中元素:O(n)(n 为栈中元素的数量,需要遍历所有元素)。
- 总体复杂度:O(n)
综合总结:
- - 整个程序的时间复杂度主要受到遍历车厢序列和栈操作的影响。
- - 在输入车厢序列和模拟调度过程时,时间复杂度主要与车厢数量成正比,为 O(n)。
- - 使用的顺序栈 `SeqStack` 在入栈和出栈操作上具有常数时间复杂度,因此对整体时间复杂度的影响较小。
实验结果示例
功能1.1:手动输入车厢初始序列(失败) |
功能1.2:手动输入车厢初始序列(成功) |
功能2.1:自动生成车厢初始序列(失败) |
功能3:动画演示 暂无 提供思路:Qt、ppt制作动画 |