数据结构实验:字符串与堆的实现与应用
1. 字符串抽象数据类型(ADT)
在C++编程中,当使用值传递方式将参数传递给函数时,编译器会创建参数的副本。对于C++的预定义类型,编译器可以简单地进行按位复制。然而,对于
String
类的实例,这种按位复制方式会引发问题。
例如,当调用
dummy(testStr)
到函数
void dummy ( String valueStr );
时,按位复制会将
testStr.buffer
指针复制到
valueStr.buffer
,这意味着两个指针指向同一个字符串缓冲区。因此,对
valueStr
的更改也会影响
testStr
,这违反了值传递的约束。此外,当函数终止时,
String
类的析构函数会删除副本,同时也会删除
testStr
的字符串缓冲区。
为了解决这个问题,我们可以在
String
类中包含一个拷贝构造函数,编译器将使用我们定义的拷贝构造函数来替代默认的按位拷贝构造函数。
String ( const String &valueString ) throw ( bad_alloc )
实现步骤如下
:
1. 在
stradt.cpp
文件中实现该操作,并将其添加到文件中。
stradt.h
文件中
String
类的声明包含了该操作的原型。
2. 通过移除
testa.cpp
测试程序中以
//5
开头的行中的注释分隔符(和字符“5”)来激活测试5。
3. 填写测试5的测试计划,为每个字符串填写预期结果。
4. 执行测试计划。如果发现拷贝构造函数的实现有错误,进行修正并再次执行测试计划。
测试5(拷贝构造函数)的测试计划如下:
| 测试用例 | 字符串参数 | 预期结果 | 检查 |
| — | — | — | — |
| 简单字符串 | alpha | alpha | |
| 单字符 | a | a | |
在实际应用中,许多使用字符串的程序需要对字符串数据进行排序,这就需要开发用于比较字符串的关系运算符。以下是几个关系运算符的定义:
bool operator == ( const String &leftString, const String &rightString )
bool operator < ( const String &leftString, const String &rightString )
bool operator > ( const String &leftString, const String &rightString )
实现步骤如下
:
1. 使用C++的
strcmp()
函数(或自己的私有成员函数)作为基础,实现上述关系运算符,并将实现添加到
stradt.cpp
文件中。
stradt.h
文件中
String
类的声明包含了这些操作的原型。
2. 通过移除
testa.cpp
测试程序中以
//6
开头的行中的注释分隔符(和字符“6”)来激活测试6。
3. 填写测试6的测试计划,为每对字符串填写预期结果。
4. 执行测试计划。如果发现关系运算符的实现有错误,进行修正并再次执行测试计划。
测试6(关系运算符)的测试计划如下:
| 测试用例 | 字符串对 | 预期结果 | 检查 |
| — | — | — | — |
| 第二个字符串更大 | alpha epsilon | false | |
| 第一个字符串更大 | epsilon alpha | true | |
| 相同的字符串 | alpha alpha | true | |
| 第一个字符串嵌入在第二个字符串中 | alp alpha | true | |
| 第二个字符串嵌入在第一个字符串中 | alpha alpha | true | |
| 第一个字符串是单字符 | a alpha | true | |
| 第二个字符串是单字符 | alpha a | false | |
| 第一个字符串为空 | empty alpha | false | |
| 第二个字符串为空 | alpha empty | true | |
| 两个字符串都为空 | empty empty | true | |
2. 堆抽象数据类型(ADT)
树的表示方式不仅仅局限于链表结构,我们还可以使用数组来表示树。通过将二叉树的内容按层序复制到数组中,我们可以发现数组中位置与树中节点的关系。如果一个数据项存储在数组的第N个位置,那么它的左子节点存储在第2N + 1个位置,右子节点存储在第2N + 2个位置,父节点存储在第(N - 1) mod 2个位置。
在本实验中,我们关注一种特殊的树——堆。堆是一种满足以下条件的二叉树:
- 树是完全的,即除了可能的最底层外,每一层都是满的。如果最底层不满,那么所有缺失的数据项都在右侧。
- 树中的每个数据项都有一个对应的优先级值。对于每个数据项E,E的所有后代的优先级都小于或等于E的优先级。
堆的数据项是通用类型DT,每个数据项都有一个优先级,用于确定其在堆中的相对位置。数据项通常包含额外的数据,并且优先级不是唯一的。对象类型DT必须提供一个名为
pty()
的函数,用于返回数据项的优先级,并且必须能够使用六个基本关系运算符比较优先级。
堆的操作包括:
Heap ( int maxNumber = defMaxHeapSize )
~Heap ()
void insert ( const DT &newDataItem ) throw ( logic_error )
DT removeMax () throw ( logic_error )
void clear ()
bool isEmpty () const
bool isFull () const
void showStructure () const
实现步骤如下
:
1. 使用堆的数组表示实现堆ADT的操作。基于
heap.h
文件中的声明进行实现,
showb.cpp
文件中给出了
showStructure
操作的实现。
const int defMaxHeapSize = 10; // Default maximum heap size
template < class DT >
class Heap
{
public:
// Constructor
Heap ( int maxNumber = defMaxHeapSize ) throw ( bad_alloc );
// Destructor
~Heap ();
// Heap manipulation operations
void insert ( const DT &newDataItem ) // Insert data item
throw ( logic_error );
DT removeMax () throw ( logic_error ); // Remove max pty data item
void clear (); // Clear heap
// Heap status operations
int isEmpty () const; // Heap is empty
int isFull () const; // Heap is full
// Output the heap structure — used in testing/debugging
void showStructure () const;
private:
// Recursive partner of the showStructure() function
void showSubtree ( int index, int level ) const;
// Data members
int maxSize,
// Maximum number of data items in the
heap
size;
// Actual number of data items in the heap
DT *dataItems;
// Array containing the heap data items
};
-
将堆ADT的实现保存到
heap.cpp文件中,并确保对代码进行文档注释。
测试堆ADT的程序
testb.cpp
提供了以下命令:
| 命令 | 操作 |
| — | — |
| +pty | 插入具有指定优先级的数据项 |
| — | 从堆中移除具有最高优先级的数据项并输出 |
| E | 报告堆是否为空 |
| F | 报告堆是否已满 |
| C | 清空堆 |
| Q | 退出测试程序 |
测试步骤如下
:
1. 为堆ADT的实现准备测试计划,测试计划应涵盖各种大小的堆,包括空堆、满堆和单数据项堆。
2. 执行测试计划。如果发现实现有错误,进行修正并再次执行测试计划。
通过以上内容,我们可以看到字符串ADT和堆ADT的实现和测试过程,这些操作在实际编程中具有重要的应用价值。
3. 优先队列与任务调度器模拟
优先队列是一种线性数据结构,其中的数据项根据优先级按降序排列。只能访问队列前端(即优先级最高)的数据项,访问该数据项意味着将其从队列中移除(出队)。
优先队列ADT的数据项是通用类型DT,每个数据项有一个优先级,用于确定其在队列中的相对位置。数据项通常包含额外的数据,对象类型DT必须提供一个名为
pty()
的函数,用于返回数据项的优先级,并且必须能够使用六个基本关系运算符比较优先级。
优先队列ADT的操作包括:
Queue ( int maxNumber = defMaxQueueSize )
~Queue ()
void enqueue ( const DT &newDataItem ) throw ( logic_error )
DT dequeue () throw ( logic_error )
void clear ()
bool isEmpty () const
bool isFull () const
可以使用堆ADT来高效实现优先队列,通过堆的
insert
操作实现入队,通过
removeMax
操作实现出队。以下是从
Heap
类派生的
PtyQueue
类的声明:
const int defMaxQueueSize = 10; // Default maximum queue size
template < class DT >
class PtyQueue : public Heap<DT>
{
public:
// Constructor
PtyQueue ( int maxNumber = defMaxQueueSize );
// Queue manipulation operations
void enqueue ( const DT &newDataItem ) throw ( logic_error );
// Enqueue data data item
DT dequeue () throw ( logic_error );
// Dequeue data data item
};
ptyqueue.cpp
文件中给出了优先队列ADT的构造函数、入队和出队操作的实现,由于堆ADT和优先队列ADT的紧密关系,这些实现非常简短。其余操作从
Heap
类继承。
操作系统通常使用优先队列来管理对系统资源(如打印机、内存、磁盘、软件等)的访问。每次任务请求访问系统资源时,该任务会被放入与该资源关联的优先队列中。当任务出队时,它将被授予对资源的访问权限。
下面是模拟任务通过优先队列流动的过程:
1. 每分钟出队一个任务(假设该分钟至少有一个任务等待出队)。
2. 每分钟有0到2个任务入队,其中无任务入队的概率为50%,一个任务入队的概率为25%,两个任务入队的概率为25%。
3. 每个任务的优先级值为0(低)或1(高),两种优先级的概率相等。
可以使用以下算法模拟n分钟内任务通过队列的流动:
Initialize the queue to empty.
for ( minute = 0 ; minute < n ; ++minute )
{
If the queue is not empty, then remove the task at the front of the queue.
Compute a random integer k between 0 and 3.
If k is 1, then add one task to the queue. If k is 2, then add two tasks.
Otherwise (if k is 0 or 3), do not add any tasks to the queue. Compute the
priority of each task by generating a random value of 0 or 1.
}
实现步骤如下
:
1. 以
ossim.cs
文件中的程序框架为基础,创建一个程序,使用优先队列ADT实现上述任务调度器。程序应在每个任务出队时输出以下信息:任务的优先级、入队时间和在队列中等待的时间。
2. 使用该程序模拟任务通过优先队列的流动,并完成以下表格:
| 时间(分钟) | 低优先级(0)任务的最长等待时间 | 高优先级(1)任务的最长等待时间 |
| — | — | — |
| 10 | | |
| 30 | | |
| 60 | | |
- 分析优先队列任务调度器是否公平,即对于两个相同优先级的任务T1和T2,T1在时间N入队,T2在时间N + i(i > 0)入队,T2是否会在T1之前出队。如果存在不公平现象,思考如何消除该问题,使任务调度器公平。
4. 总结与流程回顾
通过上述内容,我们学习了字符串ADT、堆ADT和优先队列ADT的实现和应用,以及如何使用优先队列模拟任务调度器。下面是整个过程的流程图:
graph TD;
A[字符串ADT实现] --> B[拷贝构造函数实现];
B --> C[关系运算符实现];
C --> D[堆ADT实现];
D --> E[优先队列ADT实现];
E --> F[任务调度器模拟];
B --> G[测试拷贝构造函数];
C --> H[测试关系运算符];
D --> I[测试堆ADT];
E --> J[测试优先队列ADT];
F --> K[分析调度器公平性];
整个过程的关键步骤总结如下:
1.
字符串ADT
:
- 实现拷贝构造函数,解决按位复制问题。
- 实现关系运算符,用于字符串比较。
- 进行相应的测试。
2.
堆ADT
:
- 使用数组表示实现堆的操作。
- 编写测试计划并进行测试。
3.
优先队列ADT
:
- 从堆类派生优先队列类。
- 实现入队和出队操作。
4.
任务调度器模拟
:
- 基于优先队列ADT实现任务调度器。
- 模拟任务流动并记录相关数据。
- 分析调度器的公平性。
通过这些操作和模拟,我们可以更好地理解数据结构在实际编程中的应用,以及如何利用这些数据结构解决实际问题。
超级会员免费看

被折叠的 条评论
为什么被折叠?



