实验九 查找与排序
一、教学要求
1.熟练掌握查找表及排序表的常用存储方式;
2.熟练掌握各种排序算法与查找算法的思想、及其实现;
3.能够运用排序、查找方法解决实际问题。
二、知识点提示
主要知识点:
1.动态查找与静态查找的主要方法;
2.不同的查找方法对应不同的存储结构,以及对是否有序的要求;
3.哈希表的构建与哈希函数的设计。
三、实验内容
1.实现顺序查找(加监视哨)、折半查找静态查找算法(根据数据结构课程内容完成即可),并在给定的一个整数数组中,演示查找与排序,具体要求如下:
- 2,3,7,6,28,41,17,23,9,47,89,2,25,101,8
- 首先根据以上输入顺序,创建查找表R1;
- 在初始查找表上按照顺序查找(加监视哨)方式分别查找7,23,11,分别给出关键码比较次数及查找结果,且如果查找成功,同时打印查找成功元素所在位置;
- 对查找表R1中的数据分别按照冒泡排序、快速排序、希尔排序三种排序算法进行排序,并分别打印出各个排序算法对关键码的比较次数和移动次数(关键码的交换计为3次移动),最后将排序结果保存到查找表R2中;
- 在R2中按照折半查找分别查找7,23,11,分别给出关键码比较次数及查找结果,如查找成功,同时打印查找成功元素所在位置。
(根据上面实验,可对比顺序查找和折半查找的效率;同时也可以比较冒泡排序、快速排序和希尔排序的效率)
四、数据结构及算法描述
·顺序查找算法:
1.基本思想:从序列的第一个元素开始,逐个比较每个元素,直到找到目标元素或遍历完整个序列。
2.哨兵的使用:为了提高效率,可以在序列的末尾添加一个“哨兵”元素,将其值设置为目标值。这样,内循环就不必每次都检查是否已到达序列的末尾,只需比较元素值是否与“哨兵”相等。
3.时间复杂度:最好和最坏情况下的时间复杂度都是O(n),其中n是序列的长度。
·冒泡排序算法:
1.基本思想:通过不断地比较相邻的元素并交换(如果它们的顺序错误),使得较大的元素“冒泡”到序列的末尾。
2.实现过程:多轮遍历,每轮都将当前未排序部分的最大元素移到正确的位置。
3.时间复杂度:最好情况是O(n),最坏和平均情况是O(n²)。
·快速排序算法:
1.基本思想:采用分治策略,选择一个“基准”元素,将序列分为两部分:小于基准的元素和大于基准的元素。然后递归地对这两部分进行快速排序。
2.实现过程:包括选择基准、分区(将小于和大于基准的元素分开)和递归排序子序列。
3.时间复杂度:最好和平均情况是O(n log n),最坏情况是O(n²)。
·希尔排序算法:
1.基本思想:改进自插入排序,通过比较相距一定间隔的元素来工作,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。
2.实现过程:初始时,间隔较大,子序列中的元素可以一步到位;随着间隔逐渐减小,前面的工作不会白费,最终一趟等同于插入排序,但此时序列已基本有序,所以插入排序会很快。
3.时间复杂度:最坏情况下是O(n²),但在实践中通常比冒泡和插入排序更快。
·折半查找算法:
1.基本思想:在有序序列中查找特定元素,每次比较都处于当前区间的中间位置,从而每次都能排除一半的元素。
2.实现过程:确定中间位置,比较目标值和中间值,根据比较结果继续在左半部分或右半部分进行折半查找。
3.时间复杂度:无论序列大小如何,查找所需的最大比较次数都是O(log n)。
五、程序流程图
六、源程序清单
#include <iostream>
#include <vector>
#include<String>
using namespace std;
int sequentialSearchWithSentinel(std::vector<int>& R1, int target) {
int n = R1.size();
R1[n - 1] = target; // 设置哨兵
int i = 0;
while (R1[i] != target) {
i++;
}
R1[n - 1] = 0;
if (i < n) {
return i;// 找到目标,返回其索引
}
else {
return -1; // 没有找到目标,返回-1
}
}
// 冒泡排序
void bubbleSort(vector<int>& data, int& compareCount, int& moveCount) {
int n = data.size();
compareCount = 0;
moveCount = 0;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
compareCount++;
if (data[j] > data[j + 1]) {
swap(data[j], data[j + 1]);
moveCount += 3;
}
}
}
}
// 快速排序
void quickSort(vector<int>& data, int left, int right, int& compareCount, int& moveCount) {
if (left >= right) {
return;
}
int pivot = data[left];
int i = left, j = right;
compareCount = 0;
moveCount = 0;
while (i < j) {
while (i < j && data[j] >= pivot) {
compareCount++;
j--;
}
if (i < j) {
data[i++] = data[j];
moveCount++;
}
while (i < j && data[i] <= pivot) {
compareCount++;
i++;
}
if (i < j) {
data[j--] = data[i];
moveCount++;
}
}
data[i] = pivot;
moveCount++;
quickSort(data, left, i - 1, compareCount, moveCount);
quickSort(data, i + 1, right, compareCount, moveCount);
}
// 希尔排序
void shellSort(vector<int>& data, int& compareCount, int& moveCount) {
int n = data.size();
compareCount = 0;
moveCount = 0;
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int temp = data[i];
int j;
for (j = i; j >= gap && data[j - gap] > temp; j -= gap) {
compareCount++;
data[j] = data[j - gap];
moveCount++;
}
data[j] = temp;
}
}
}
// 折半查找静态查找算法(已排序表)
pair<int, int> binarySearch(vector<int>& data, int key) {
int low = 0;
int high = data.size() - 1;
int compareCount = 0;
while (low <= high) {
int mid = (low + high) / 2;
compareCount++;
if (data[mid] == key) {
return make_pair(mid, compareCount);
}
else if (data[mid] > key) {
high = mid - 1;
}
else {
low = mid + 1;
}
}
return make_pair(-1, compareCount); // 表示未找到
}
int main() {
vector<int> R1 = { 2, 3, 7, 6, 28, 41, 17, 23, 9, 47, 89, 2, 25, 101, 8 };
std::cout << "*顺序查找(加监视哨)" << endl;
int target1 = 7;
int index1= sequentialSearchWithSentinel(R1, target1);
if (index1 != -1) {
std::cout << "查找关键码:" << target1 <<" "<< " 在R1中位置: " << index1 << std::endl;
}
else {
std::cout << "不在 R1中 " << target1 << std::endl;
}
int target2 = 23;
int index2 = sequentialSearchWithSentinel(R1, target2);
if (index2!= -1) {
std::cout << "查找关键码:" << target2 <<" 在R1中位置: " << index2<< std::endl;
}
else {
std::cout << "不在 R1中 " << target2<< std::endl;
}
int target3 = 11;
int index3 = sequentialSearchWithSentinel(R1, target3);
if (index3!= -1) {
std::cout << "查找关键码:" << target3 <<" 在R1中位置: " << index3 << std::endl;
}
else {
std::cout << "不在 R1中 " << target3<< std::endl;
}
std::cout << " " << endl;
// 冒泡排序
vector<int> R2_bubble = R1;
int bubbleCompareCount, bubbleMoveCount;
bubbleSort(R2_bubble, bubbleCompareCount, bubbleMoveCount);
std::cout << "冒泡排序:" << endl;
std::cout << "关键码比较次数:" << bubbleCompareCount << endl;
std::cout << "关键码移动次数:" << bubbleMoveCount << endl;
std::cout << " " << endl;
// 快速排序
vector<int> R2_quick = R1;
int quickCompareCount, quickMoveCount;
quickSort(R2_quick, 0, R2_quick.size() - 1, quickCompareCount, quickMoveCount);
std::cout << "快速排序:" << endl;
std::cout << "关键码比较次数:" << quickCompareCount << endl;
std::cout << "关键码移动次数:" << quickMoveCount << endl;
std::cout << " " << endl;
// 希尔排序
vector<int> R2_shell = R1;
int shellCompareCount, shellMoveCount;
shellSort(R2_shell, shellCompareCount, shellMoveCount);
std::cout << "希尔排序:" << endl;
std::cout << "关键码比较次数:" << shellCompareCount << endl;
std::cout << "关键码移动次数:" << shellMoveCount << endl;
std::cout << " " << endl;
std::cout << "*折半查找:" << endl;
vector<int> R2 = { 2, 2, 3, 6, 7, 8, 9, 17, 23, 25, 28, 41, 47, 89, 101 };
int key1 = 7;
pair<int, int> result1 = binarySearch(R2, key1);
std::cout << "查找关键码:" << key1 <<" ";
std::cout << "关键码比较次数:" << result1.second << " ";
if (result1.first != -1) {
std::cout << "在 R2 中的位置是:" << result1.first << endl;
}
else {
std::cout << "不在 R2 中" << endl;
}
int key2 = 23;
pair<int, int> result2 = binarySearch(R2, key2);
std::cout << "查找关键码:" << key2 <<" ";
std::cout << "关键码比较次数:" << result2.second << " ";
if (result2.first != -1) {
std::cout << "在 R2 中的位置是:" << result2.first << endl;
}
else {
std::cout << "不在 R2 中" << endl;
}
int key3 = 11;
pair<int, int> result3 = binarySearch(R2, key3);
std::cout << "查找关键码:" << key3<<" ";
std::cout << "关键码比较次数:" << result3.second << " ";
if (result3.first != -1) {
std::cout << "在 R2 中的位置是:" << result3.first << endl;
}
else {
std::cout << "不在 R2 中" << endl;
}
return 0;
}
七、程序运行测试
总结
在本次实验中,我们深入探讨了查找和排序算法的实现与效率,通过具体的编程实践,巩固了对查找表和排序表常用存储方式的理解,并掌握了顺序查找、折半查找、冒泡排序、快速排序和希尔排序等多种算法的思想及其实现。
一、查找算法
1.1 顺序查找(加监视哨)
顺序查找是一种简单而直观的查找方法,它逐一比较查找的目标与数组中的每个元素。为提高效率,我们在数组末尾添加了一个监视哨,以避免在查找过程中判断数组越界。在实验中,顺序查找的关键码比较次数、查找结果均被记录。具体查找过程中,我们对目标值7和23进行了成功查找,而对于不存在的值11则返回了未找到的提示。结果表明,顺序查找在小规模数据中表现良好,但随着数据规模的增大,效率下降明显。
1.2 折半查找
折半查找的前提是数组必须是有序的。因此,在进行折半查找之前,我们首先对查找表进行排序。折半查找通过每次将查找范围缩小一半来加快查找速度,极大地提高了查找效率。在实验中,经过排序后的查找表中成功找到了目标值7和23,但对于11仍未找到。折半查找的关键码比较次数显著少于顺序查找,尤其在大数据量的情况下更能体现其优势。
二、排序算法
2.1 冒泡排序
冒泡排序是最基础的排序算法,其核心思想是通过相邻元素的比较与交换,将最大的元素“冒泡”到数组末尾。虽然实现简单,但在效率上并不理想,尤其在数据量较大的情况下。实验中,我们记录了每次比较和移动的次数,并发现随着数据的增加,冒泡排序的效率明显下降。这表明冒泡排序在实际应用中不够高效,尤其对于大规模数据,应该考虑其他更优的排序算法。
2.2 快速排序
快速排序是一种更为高效的排序算法,采用分治法的思想。其基本过程是选取一个“基准”元素,将数组分为两个部分,使得左边部分的元素均小于基准,右边部分的元素均大于基准。通过递归对这两部分进行快速排序,最终得到有序数组。在实验中,快速排序的表现明显优于冒泡排序,关键码的比较次数和移动次数大幅降低,展现了其优秀的时间复杂度(平均O(n log n))。快速排序在大规模数据处理时的优越性得到了充分体现。
2.3 希尔排序
希尔排序是对插入排序的一种改进。它通过将整个数组分为若干个子数组,分别对每个子数组进行插入排序,然后逐渐缩小间隔,最终实现整体有序。希尔排序相比冒泡排序和简单的插入排序有着更好的性能表现。实验结果显示,希尔排序的关键码比较次数与移动次数均较低,尤其在较大的数据集上,其效率得到了明显提升。
三、效率对比
在本次实验中,我们通过对不同查找和排序算法的比较,清晰地看到了它们在效率上的差异。顺序查找在数据量较小的情况下表现良好,但在大规模数据时,折半查找的优势明显。在排序算法中,冒泡排序在效率上显得力不从心,而快速排序和希尔排序则在处理大数据时展现了强大的性能,特别是快速排序,其效率远超其他算法。
3.1 实际应用
在实际应用中,选择合适的查找与排序算法至关重要。对于小规模、无序的数据,顺序查找和冒泡排序可能足够使用,但对于需要频繁查找或大规模数据处理的场景,折半查找、快速排序和希尔排序则是更优的选择。结合具体数据特征与需求,合理选择算法,将有助于提高程序的整体性能。
四、总结
通过本次实验,我们不仅掌握了查找与排序算法的基本实现,更加深了对其原理与应用的理解。实验结果清晰地表明,算法选择的合理性直接影响程序的效率,深入了解各种算法的特性将为今后的编程实践提供重要的指导。希望在未来的学习中,能继续探索更高级的查找与排序算法,以提升算法设计与实现的能力。