#设计一个测试程序比较几种内部排序算法的关键字比较次数和移动次数。
源码及注释:
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <algorithm>
/*
说明:comparisons 比较次数
moves 移动次数 */
/* 定义排序算法函数 */
/***
* 冒泡排序
***********/
void bubbleSort(int arr[], int n) {
//定义比较次数和移动次数并初始化
int comparisons = 0;
int moves = 0;
//循环比较数组arr[]
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
comparisons++;
if (arr[j] > arr[j + 1]) {
//比较大小对换位置
std::swap(arr[j], arr[j + 1]);
moves += 3;
}
}
}
//输出比较次数和关键字移动次数
std::cout << "冒泡排序中关键字的比较次数: " << comparisons << ",关键字的移动次数: " << moves << std::endl;
}
/***
* 简单选择排序
***************/
void selectionSort(int arr[], int n) {
//定义比较次数和移动次数并初始化
int comparisons = 0;
int moves = 0;
//循环找出最小数下标
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
comparisons++;
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
//当前数与最小数交换
std::swap(arr[i], arr[minIdx]);
moves += 3;
}
//输出
std::cout << "选择排序中关键字的比较次数: " << comparisons << ",关键字的移动次数: " << moves << std::endl;
//std::cout << "\n";
}
/***
* 快速排序
* 定义左边界和右边界left right
* 比较次数以及移动次数comparisons moves
* 首先判断左边界 left 是否小于右边界 right,如果不小于,则表示数组范围已经排序完成,返回。
* 如果数组范围未排序完成,就找到中间数 pivot,这里采用了取中间元素的方式。
*****************************************/
void quickSort(int arr[], int left, int right, int& comparisons, int& moves) {
//如果左边小于右边
if (left < right) {
int pivot = arr[left + (right - left) / 2];
int i = left;
int j = right;
while (i <= j) {
while (arr[i] < pivot) {
i++;
comparisons++;
}
while (arr[j] > pivot) {
j--;
comparisons++;
}
//当找到需要交换的元素对时,进行交换,并更新指针 i 和 j,同时记录了移动次数 moves。
if (i <= j) {
std::swap(arr[i], arr[j]);
i++;
j--;
// 两次交换和一次加法
moves += 3;
}
}
//递归调用 quickSort 函数对左右两个子数组进行排序,直到排序完成。
quickSort(arr, left, j, comparisons, moves);
quickSort(arr, i, right, comparisons, moves);
}
}
/***
* 希尔排序
***********/
void shellSort(int arr[], int n) {
int comparisons = 0;
int moves = 0;
//首先使用一个循环来确定子序列的间隔 gap,
//初始间隔为数组大小的一半,然后每次循环将间隔减半,直到间隔为 1。
//在每个间隔下,使用插入排序算法对子序列进行排序。内部的插入排序算法和普通的插入排序类似,不同之处在于每次比较和移动都是以间隔 gap 为步长进行的。
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j;
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
//记录比较次数 comparisons 和移动次数 moves。
moves++;
comparisons++;
}
arr[j] = temp;
moves++;
}
}
//输出
std::cout << "希尔排序中关键字的比较次数: " << comparisons << ",关键字的移动次数: " << moves << std::endl;
}
/***
* 堆排序
***********/
void heapify(int arr[], int n, int i, int& comparisons, int& moves) {
//将当前节点 i 标记为最大节点 largest。
int largest = i;
//计算左子节点和右子节点的索引,左子节点索引为 2*i+1,右子节点索引为 2*i+2。
int l = 2 * i + 1;
int r = 2 * i + 2;
//进行比较。如果左子节点的索引小于 n,说明左子节点存在,此时增加比较次数。
//然后比较左子节点的值和当前最大节点的值,如果左子节点的值大于最大节点的值,
//则将最大节点索引更新为左子节点的索引。
if (l < n) {
comparisons++;
}
if (l < n && arr[l] > arr[largest]) {
largest = l;
}
//如果右子节点的索引小于 n,说明右子节点存在,此时增加比较次数。
//然后比较右子节点的值和当前最大节点的值,如果右子节点的值大于最大节点的值,
//则将最大节点索引更新为右子节点的索引。
if (r < n) {
comparisons++;
}
if (r < n && arr[r] > arr[largest]) {
largest = r;
}
//如果最大节点索引不等于初始节点 i,说明需要进行交换。
//交换数组中索引为 i 和 largest 的元素,并增加移动次数。
if (largest != i) {
std::swap(arr[i], arr[largest]);
moves += 3;
//递归调用 heapify 函数,对发生交换的子节点进行堆化。
heapify(arr, n, largest, comparisons, moves);
}
}
void heapSort(int arr[], int n) {
//初始化变量 comparisons 和 moves,用于记录比较次数和移动次数。
int comparisons = 0;
int moves = 0;
//循环对数组进行初始化堆操作。从数组的中间位置向前遍历,对每个节点调用 heapify 函数,
//将数组转换为最大堆。这一步确保整个数组满足堆属性。
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i, comparisons, moves);
}
//使用循环进行排序。在每次循环中,将堆顶元素(数组的第一个元素)与当前未排序部分的最后一个元素交换,
//然后减小堆的大小并重新调用 heapify 函数,将剩余部分重新堆化。这样就逐步将最大的元素移动到数组的末尾,实现排序。
for (int i = n - 1; i > 0; i--) {
std::swap(arr[0], arr[i]);
moves += 3;
heapify(arr, i, 0, comparisons, moves);
}
//输出结果
std::cout << "堆排序算中的关键字比较次数: " << comparisons << ",关键字移动次数: " << moves << std::endl;
}
/***
* 直接插入排序
***************/
void insertionSort(int arr[], int n) {
//初始化比较次数和移动次数
int comparisons = 0;
int moves = 0;
//使用一个循环从数组的第二个元素开始遍历,将当前元素视为“待插入的元素”。
//在内部循环中,将“待插入的元素”保存在变量 key 中,然后从当前位置向前查找,找到合适的位置将 key 插入。
//如果当前元素大于 key,则将当前元素后移一位,直到找到合适的位置。
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
//自增 记录比较次数和移动次数
moves++;
comparisons++;
}
arr[j + 1] = key;
moves++;
}
//输出关键字比较次数和关键字移动次数,用于评估直接插入排序的性能。
std::cout << "直接插入排序中关键字的比较次数: " << comparisons << ",关键字的移动次数: " << moves << std::endl;
}
/***
* 测试函数
* 用于测试排序算法的性能。
*************************/
void testSortingAlgorithm(void (*sortFunc)(int[], int), const std::string& sortName) {
//测试次数
const int numTests = 5;
//数组长度
const int arrLength = 100;
//数组 comparisons 和 moves 用于存储比较次数和移动次数。
int comparisons[numTests];
int moves[numTests];
//使用一个循环进行多次测试。在每次循环中,生成一个长度为 arrLength 的随机整数数组,并输出该数组。
for (int i = 0; i < numTests; i++) {
int arr[arrLength];
// 生成随机数据
/*for (int j = 0; j < arrLength; j++) {
arr[j] = rand() % 1000; // 生成 0 到 999 之间的随机数
}*/
// 生成随机数据
std::cout << "随机数组 " << i + 1 << ": ";
for (int j = 0; j < arrLength; j++) {
// 生成 0 到 999 之间的随机数
arr[j] = rand() % 1000;
// 生成数组之间用空格隔开
std::cout << arr[j]<<" ";
}
std::cout << std::endl;
std::cout << "\n";
// 复制数组用于比较排序前后的关键字移动次数
int arrCopy[arrLength];
std::copy(arr, arr + arrLength, arrCopy);
// 进行排序并记录比较次数和移动次数
sortFunc(arr, arrLength);
// 输出排序后的数组
std::cout << "排序后数组 " << i + 1 << ": ";
for (int j = 0; j < arrLength; j++) {
std::cout << arr[j] << " ";
}
std::cout << std::endl;
std::cout << "\n";
// 将比较次数和移动次数存储到数组中
// comparisons[i] = /* 某种方式记录比较次数 */;
// moves[i] = /* 某种方式记录移动次数 */;
}
}
// 生成随机数组用于测试快速排序
void generateRandomArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
// 生成 0 到 999 之间的随机数
arr[i] = rand() % 1000;
}
}
// 测试函数
/*void testQuickSort() {
const int n = 10; // 数组大小
int arr[n];
srand(time(NULL)); // 设置随机数种子
generateRandomArray(arr, n); // 生成随机数组
quickSort(arr, 0, n - 1); // 调用快速排序函数
}*/
// 测试函数
void testQuickSort() {
// 数组大小
const int n = 100;
// 测试次数
const int numTests = 1;
for (int t = 0; t < numTests; t++) {
int arr[n];
// 设置随机数种子
srand(time(NULL));
// 生成随机数组
generateRandomArray(arr, n);
std::cout << "原始数组: ";
for (int i = 0; i < n; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
int comparisons = 0;
int moves = 0;
// 调用快速排序函数
quickSort(arr, 0, n - 1, comparisons, moves);
// 输出结果
std::cout << "快速排序中关键字的比较次数: " << comparisons << ",关键字的移动次数: " << moves << std::endl;
std::cout << "排序后数组: ";
for (int i = 0; i < n; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl << "\n";
}
}
int main() {
// 设置随机种子
srand(static_cast<unsigned int>(time(0)));
//定义选择序号以及对应操作
int index;
do {
std::cout << "*请选择测试(冒泡排序0,简单选择排序1,希尔排序2,直接插入排序3,堆排序4,快速排序5):";
//输入
std::cin >> index;
switch (index)
{
case 0:
//执行测试冒泡排序
testSortingAlgorithm(bubbleSort, "Bubble Sort");
std::cout << "\n";
break;
case 1:
//简单选择排序
testSortingAlgorithm(selectionSort, "Selection Sort");
std::cout << "\n";
break;
case 2:
//希尔排序
testSortingAlgorithm(shellSort, "ShellSort Sort");
std::cout << "\n";
break;
case 3:
//直接插入排序
testSortingAlgorithm(insertionSort, "InsertionSort Sort");
std::cout << "\n";
break;
case 4:
//堆排序
testSortingAlgorithm(heapSort, "Heap Sort");
std::cout << "\n";
break;
case 5:
//快速排序
testQuickSort();
std::cout << "\n";
break;
default:
std::cout << "无效的选择" << std::endl;
break;
}
}
while (index >= 0 && index <= 5);
return 0;
}