【数据结构与算法】内排序算法比较(C\C++)

news2024/9/25 15:27:30

实践要求

1. 问题描述

各种内部排序算法的时间复杂度分析结果只给出了算法执行时间的阶,或大概执行时间,试通过随机的数据比较各算法的关键字比较次数和关键字移动次数,以取得直观感受。


2. 基本要求

  1. 对以下10种常用的内部排序算法进行比较:直接插入排序、折半插入排序、二路插入排序、希尔排序、起泡排序、快速排序、简单选择排序、堆排序、归并排序、基数排序。
  2. 待排序表的表长不少于100;其中的数据要用伪随机数产生程序产生;至少要用5组不同的输入数据作比较;比较的指标为有关键字参加的比较次数和关键字的移动次数(关键字交换计为3次移动)。

3. 测试数据

由随机数产生器决定。


4. 实现提示

主要工作是设法在程序中的适当地方插入计数操作。程序还可以包括计算几组数据均值的操作。最后要对结果作出简单分析,包括对各组数据得出结果波动大小的解释。注意分块调试的方法。


实践报告

1. 题目分析

说明程序设计的任务,强调的是程序要做什么,此外列出各成员分工

程序设计任务:
比较十种不同的内排序算法


2. 程序设计

实现概要设计中的数据类型,对主程序、模块及主要操作写出伪代码,画出函数的调用关系

十种内排序算法


3. 测试结果

列出测试结果,包括输入和输出

在这里插入图片描述


4. 用户使用说明

给出主界面及主要功能界面

无需任何操作,自动测试


5. 附录

源程序文件清单:
sorce.cpp

6. 全部代码

source.cpp

#include <algorithm>
#include <iostream>
#include <random>
#include <vector>
using namespace std;

// 关键字参加的交换次数
long long comparison_times = 0;

// 关键字参加的移动次数
long long move_times = 0;

// 随机数生成器,生成int范围内的整数
std::random_device seed;
std::default_random_engine engine{seed()};
std::uniform_int_distribution<int> dis(INT_MIN, INT_MAX);

// 排序算法 - 冒泡排序,
// 通过相邻元素的比较和交换,在每一轮使一个元素移动到正确的位置。
// 时间复杂度O(n^2)
// 总体来说,选择排序每次交换都是使得最小值移至最前,效率略高一点。冒泡排序每次比较都可能发生交换,效率略低。
void bubbling_sort(vector<int> &arr) {
    for (int i = 0; i < arr.size(); ++i) {
        for (int j = 1; j < arr.size() - i; ++j) {
            ++comparison_times;
            if (arr[j] < arr[j - 1]) {
                swap(arr[j], arr[j - 1]);
                move_times += 3;
            }
        }
    }
}

// 排序算法 - 简单选择排序
// 每一轮从未排序的元素中选出最小的元素,使其移动到已排序的序列的末尾。
// 时间复杂度O(n^2)
void simple_selection_sort(vector<int> &arr) {
    for (int i = 0; i < arr.size(); ++i) {
        ++move_times;
        int min_index = i;
        for (int j = i; j < arr.size(); ++j) {
            ++comparison_times;
            if (arr[min_index] > arr[j]) {
                min_index = j;
                ++move_times;
            }
        }
        swap(arr[min_index], arr[i]);
        move_times += 3;
    }
}

// 排序算法 - 快速排序算法
void quick_sort(vector<int> &arr, int left, int right) {
    // 递归终止条件:区间只有0或1个元素,已然有序,无需继续划分
    if (right - left < 1) {
        return;
    }

    // 随机选取一个数作为基准数Pivot
    int index = left + rand() % (right - left + 1);
    int pivot = arr[index];
    ++move_times;

    // 初始化左右指针lt和gt,cnt用于遍历,lt表示小于pivot的最后一个元素,gt表示大于pivot的第一个元素
    int lt = left;
    int gt = right;
    int cnt = left;

    // 遍历数组,进行三向切分
    while (cnt <= gt) {
        // 当前元素小于pivot,则交换至左指针lt处,lt和cnt同时右移
        ++comparison_times;
        if (arr[cnt] < pivot) {
            swap(arr[cnt++], arr[lt++]);
            move_times += 3;
        }
        // 当前元素大于pivot,则交换至右指针gt处,gt左移
        else if (arr[cnt] > pivot) {
            swap(arr[gt--], arr[cnt]);
            move_times += 3;
        }
        // 等于pivot,直接跳过
        else {
            ++cnt;
        }
    }

    // 递归调用,继续对左右两部分进行快速排序
    quick_sort(arr, left, lt - 1);
    quick_sort(arr, gt + 1, right);
}

// 排序算法 - 归并排序:通过递归将数组划分为两部分,排序后合并得到最终结果。
void merge_sort(vector<int> &tmp, vector<int> &arr, int left, int right) {
    if (right - left < 1) {  // 递归终止条件,子数组长度为 1
        return;
    }
    int mid = left + ((right - left) >> 1);  // 取中间索引
    merge_sort(tmp, arr, left, mid);         // 对左半部分排序
    merge_sort(tmp, arr, mid + 1, right);    // 对右半部分排序

    int l = left, r = mid + 1, k = 0;  // 初始化变量
    while (l <= left && r <= right) {  // 双指针,取较小者
        ++comparison_times;
        ++move_times;
        if (arr[l] < arr[r]) {
            tmp[k++] = arr[l++];  // 将较小值放入 tmp,指针后移
        } else {
            tmp[k++] = arr[r++];
        }
    }
    while (l <= mid) {  // 将左半部分剩余元素放入 tmp
        ++move_times;
        tmp[k++] = arr[l++];
    }
    while (r <= right) {  // 将右半部分剩余元素放入 tmp
        ++move_times;
        tmp[k++] = arr[r++];
    }
    copy(tmp.begin(), tmp.begin() + (right - left + 1),
         arr.begin() + left);  // 将 tmp 拷贝回 arr
    move_times += right - left + 1;
}

// 排序算法 - 简单插入排序
void simple_insertion_sort(vector<int> &arr) {
    for (int i = 1; i < arr.size(); ++i) {
        for (int j = i - 1; j >= 0; --j) {
            ++comparison_times;
            if (arr[j + 1] < arr[j]) {
                move_times += 3;
                swap(arr[j + 1], arr[j]);
            } else {
                break;
            }
        }
    }
}

// 排序算法 - 折半插入排序
void half_insert_sort(vector<int> &arr) {
    for (int i = 1; i < arr.size(); ++i) {
        int left = 0;
        int right = i - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            ++comparison_times;
            if (arr[mid] > arr[i]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        for (int k = i; k >= left; --k) {
            swap(arr[left], arr[k]);
            move_times += 3;
        }
    }
}

// 排序算法 - 二路插入排序
// 将数组分成已排序和未排序两部分,每次从未排序的部分找一个最小的元素插入到已排序的部分中。
// 时间复杂度O(n^2)
void two_way_insertion_sort(vector<int> &arr) {
    for (int i = 1; i < arr.size(); ++i) {
        ++move_times;
        int key = arr[i];
        int j = i - 1;
        // 找到已排序部分第一个大于等于key的元素,并记录其索引
        while (j >= 0 && arr[j] > key && (++comparison_times)) {
            ++move_times;
            arr[j + 1] = arr[j];
            j--;
        }
        // 在已排序部分的正确位置插入key
        arr[j + 1] = key;
        ++move_times;
    }
}

// 排序算法 - 基数排序
// 针对每一位数进行排序,从低位到高位逐渐排序,实现整体有序。要求元素的表示形式从低位到高位是有意义的。
// 时间复杂度O(n*k),其中k是排序位数。
void radix_sort(vector<int> &arr) {
    // 获取最大数,确定排序位数
    int max_num = *max_element(arr.begin(), arr.end());
    comparison_times += arr.size();

    int num_digits = 0;
    while (max_num > 0) {
        max_num /= 10;
        num_digits++;
    }

    // 设置10个桶
    vector<vector<int>> buckets(10);
    // 按位排序,从个位开始
    for (int pos = 0; pos < num_digits; pos++) {
        // 将所有整数按指定位数放入桶中
        for (int num : arr) {
            int digit = 0;
            buckets[digit].push_back(num);
            ++move_times;
        }

        // 按桶顺序输出
        move_times += arr.size();
        arr.clear();

        for (auto &bucket : buckets) {
            for (int num : bucket) {
                ++move_times;
                arr.push_back(num);
            }
            bucket.clear();
        }
    }
}

// 排序算法 - 希尔排序
// 是插入排序的一种优化版本。它通过间隔为h的增量来比较并交换相隔h个元素,采用递减的h值,最终当h=1时,变成普通的插入排序。
// 时间复杂度O(nlogn)
void shell_sort(vector<int> &arr) {
    int h = 1;
    while (h < arr.size() / 3) {
        h = 3 * h + 1;  // 确定初始步长h
    }

    while (h >= 1) {
        for (int i = h; i < arr.size(); i++) {
            int j = i;
            int temp = arr[i];
            ++move_times;
            while (j >= h && arr[j - h] > temp && ++comparison_times) {
                ++move_times;
                arr[j] = arr[j - h];
                j -= h;
            }
            ++move_times;
            arr[j] = temp;
        }
        h /= 3;  // 步长缩小
    }
}

// 排序算法 - 堆排序,利用堆结构(可看成完全二叉树)的特点实现排序。
// 时间复杂度O(nlogn)
void heapify(vector<int> &arr, int n, int i) {
    int largest = i;    // 目前最大值的索引
    int l = 2 * i + 1;  // 左子节点索引
    int r = 2 * i + 2;  // 右子节点索引

    comparison_times += 2;
    if (l < n && arr[l] > arr[largest]) largest = l;
    if (r < n && arr[r] > arr[largest]) largest = r;
    if (largest != i) {
        swap(arr[i], arr[largest]);
        move_times += 3;
        heapify(arr, n, largest);
    }
}

void heap_sort(vector<int> &arr) {
    // 建立最大堆,将数组转换成最大堆
    for (int i = arr.size() / 2 - 1; i >= 0; i--) heapify(arr, arr.size(), i);

    // 交换根节点和最后一个节点,调整最大堆,重复此操作
    for (int i = arr.size() - 1; i >= 0; i--) {
        move_times += 3;
        swap(arr[0], arr[i]);
        heapify(arr, i, 0);
    }
}

void restore_status(vector<int> &tmp, vector<int> &arr,
                    long long &comparison_times, long long &move_times) {
    tmp = arr;
    comparison_times = 0;
    move_times = 0;
    return;
}

void print_statistical_results(const long long &comparison_times,
                               const long long &move_times) {
    cout << comparison_times << ' ' << move_times << '\n';
}

int main() {
    // ios::sync_with_stdio(false);
    int step = 10;
    for (int i = 0; i < 5; ++i, step *= 5) {
        vector<int> arr, tmp;
        for (int j = 0; j < step; ++j) {
            arr.push_back(dis(engine));
        }

        // 冒泡排序
        restore_status(tmp, arr, comparison_times, move_times);
        bubbling_sort(tmp);
        print_statistical_results(comparison_times, move_times);

        // 简单选择排序
        restore_status(tmp, arr, comparison_times, move_times);
        simple_selection_sort(tmp);
        print_statistical_results(comparison_times, move_times);

        // 快速排序
        restore_status(tmp, arr, comparison_times, move_times);
        quick_sort(tmp, 0, tmp.size() - 1);
        print_statistical_results(comparison_times, move_times);

        // 归并排序
        restore_status(tmp, arr, comparison_times, move_times);
        vector<int> temporary(arr.size());
        merge_sort(temporary, tmp, 0, arr.size() - 1);
        print_statistical_results(comparison_times, move_times);

        // 简单插入排序
        restore_status(tmp, arr, comparison_times, move_times);
        simple_insertion_sort(tmp);
        print_statistical_results(comparison_times, move_times);

        // 折半插入排序
        restore_status(tmp, arr, comparison_times, move_times);
        half_insert_sort(tmp);
        print_statistical_results(comparison_times, move_times);

        // 二路插入排序
        restore_status(tmp, arr, comparison_times, move_times);
        two_way_insertion_sort(tmp);
        print_statistical_results(comparison_times, move_times);

        // 基数排序
        restore_status(tmp, arr, comparison_times, move_times);
        radix_sort(tmp);
        print_statistical_results(comparison_times, move_times);

        // 希尔排序
        restore_status(tmp, arr, comparison_times, move_times);
        shell_sort(tmp);
        print_statistical_results(comparison_times, move_times);

        // 堆排序
        restore_status(tmp, arr, comparison_times, move_times);
        heap_sort(tmp);
        print_statistical_results(comparison_times, move_times);

        cout << endl;
    }
    cout << "end";
    return 0;
}


结束语

  因为是算法小菜,所以提供的方法和思路可能不是很好,请多多包涵~如果有疑问欢迎大家留言讨论,你如果觉得这篇文章对你有帮助可以给我一个免费的赞吗?我们之间的交流是我最大的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/712776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL更改表结构语句

一、MySQL表结构变更语句 1. 新增字段 语法&#xff1a; &#xff08;1&#xff09;在末尾添加字段 ALTER TABLE <表名> ADD <新字段名><数据类型>[约束条件]; &#xff08;2&#xff09;在开头添加字段 ALTER TABLE <表名> ADD <新字段名> <…

[C++]lambda

目录 前言&#xff1a; 1 认识lambda 2 lambda语法 3 lambda的类型 4 lambda的底层 前言&#xff1a; 本篇讲解了C11当中新添加的lambda语法&#xff0c;以及lambda的底层 1 认识lambda lambda的出现方便了很多我们写程序的地方&#xff0c;例如下面这个样例…

C++图形开发(2):最基本的图形界面

文章目录 1.构成2.内容介绍2.1 initgraph()2.2 _getch()2.3 closegraph() 3.总结 今天来简单介绍下最基本的图形界面~ 1.构成 输入以下内容并编译&#xff1a; 这就是一个最基本的图形界面了 #include<graphics.h> #include<conio.h>int main() {initgraph(600, …

rollup入门 - 学习笔记(1)

rollup打包工具 打包项目用webpack , 打包js库用rollup 下载rollup npm i rollup --save-dve 初始化项目 npm init -y 创建src/main.js文件 import welcomerollup from ../modules/mymodule welcomerollup(hello world) 创建module/mymodule.js文件 const welcomerollup (ms…

Python PDF生成和二进制流转换(FPDF)

文章目录 安装FPDF生成PDF文档生成PDF的二进制流 安装FPDF pip install fpdf22.7.4生成PDF文档 通过FPDF生成PDF文档的具体步骤&#xff1a; 初始化&#xff1a;fpdf库的操作主要由FPDF对象来处理&#xff0c;在生成PDF文档时&#xff0c;需要初始化FPDF对象。添加页面&…

引爆媒体关注的秘密武器:媒介易教你打造热门新闻故事!

在进行媒体邀约时&#xff0c;提供有吸引力的新闻价值和故事性是吸引媒体关注和获得采访机会的关键。媒体记者时常接收大量的邀约&#xff0c;因此需要与众多企业竞争&#xff0c;才能让自己的邀约脱颖而出。本文将探讨如何在媒体邀约中提供有吸引力的新闻价值和故事性&#xf…

Domain Admin域名和SSL证书过期监控到期提醒

基于Python3 Vue3.js 技术栈实现的域名和SSL证书监测平台 用于解决&#xff0c;不同业务域名SSL证书&#xff0c;申请自不同的平台&#xff0c;到期后不能及时收到通知&#xff0c;导致线上访问异常&#xff0c;被老板责骂的问题 核心功能&#xff1a;域名 和SSL证书 的过期…

【敬伟ps教程】亮度与色阶看懂直方图

文章目录 亮度/对比度色阶调整输入色阶调整输出色阶调节原色通道调整图层 亮度/对比度 控制明暗的视觉因素&#xff0c;三要素之一&#xff0c;明度 在拾色器中修改 HSB 中的 B&#xff0c;改为较低的值即可调整明度。明度较低时&#xff0c;RGB色值偏低&#xff0c;CMYK色值…

怎么看懂今日白银现货价格?这个技术信号了解一下

金叉&#xff0c;是某些技术指标的一种技术动作&#xff0c;通常预示着&#xff0c;今日白银现货价格开始上涨&#xff0c;并且出现了买入机会。但是&#xff0c;是不是金叉就一定有买入机会呢&#xff1f;在今日白银现货价格出现金叉时要如何处理呢&#xff1f;这都是我们要处…

UV统计量

UV统计 UV&#xff1a;Unique Visitor&#xff0c;即独立访客量&#xff0c;是指通过互联网访问、浏览该网页的自然人。一天内同一个用户多次访问该网站&#xff0c;只会记录一次。 PV&#xff1a;Page View&#xff0c;即页面访问量或点击量&#xff0c;用户每访问网站的一个…

d2l_第九章_RNN循环神经网络

x.1 Sequence model 经过前面的学习&#xff0c;我们已知数据大于算法。而以数据为驱动的前提下&#xff0c;我们提出了各种模型。为了适配表格数据&#xff0c;提出了MLP&#xff1b;为了适配图像数据提出了CNN&#xff1b;而对了适配序列数据&#xff0c;我们提出了RNN。 目…

C++笔记之call_once和once_flag

C笔记之call_once和once_flag code review! 文章目录 C笔记之call_once和once_flag1.call_once和once_flag2.std::call_once和单例模式 1.call_once和once_flag 2.std::call_once和单例模式

音频格式转换怎么操作?分享这几个MP3转换器给大家!

有一个名叫小灵的音乐爱好者&#xff0c;对音乐充满热情&#xff0c;每天都沉浸在动听的旋律中&#xff0c;借助耳机享受音乐的魅力。然而&#xff0c;最近她遇到了一个问题&#xff1a;她手头有一些喜欢的音乐文件&#xff0c;但却无法在自己的音乐播放器上播放&#xff0c;这…

【C++】C++教程、学习笔记

文章目录 一、C基础入门1 C初识1.1 第一个C程序1.1.1 创建项目1.1.2 创建文件1.1.3 编写代码1.1.4 运行程序 1.2 注释1.3 变量1.4 常量1.5 关键字1.6 标识符命名规则 2 数据类型2.1 整型2.2 sizeof关键字2.3 实型&#xff08;浮点型&#xff09;2.4 字符型2.5 转义字符2.6 字符…

如何优雅的避免Java中:NullPointerException(空指针异常)

目录 1&#xff1a;空指针问题 2&#xff1a;解决方法 第一种方式&#xff1a; 第二种方式&#xff1a; 第三种方式&#xff1a; 第四种方式&#xff1a; 1&#xff1a;空指针问题 Java是没有指针的&#xff0c;所以我们常说"Java 指针"就是指"Java 的引用…

[RoarCTF 2019]Easy Calc1

看这个输入框&#xff0c;所以我猜测可能是sql注入&#xff0c;弹出了一个对话框&#xff0c;说算不来&#xff0c;说明可能存在过滤 最后发现只要传入字母都会触发弹窗&#xff0c;应该跟题目设定有关系&#xff0c;这只是一个简单的计算器而已 查看了一下源码&#xff0c;发现…

联邦学习 (FL) 中常见的3种模型聚合方法

联邦学习 (FL) 中常见的3种模型聚合方法 联合学习 (FL) 是一种出色的 ML 方法&#xff0c;它使多个设备&#xff08;例如物联网 (IoT) 设备&#xff09;或计算机能够在模型训练完成时进行协作&#xff0c;而无需共享它们的数据。 “客户端”是 FL 中使用的计算机和设备&#x…

Java设计模式之一:装饰器模式

目录 一、什么是装饰器模式 二、装饰器模式如何使用 三、装饰器模式的优势和应用场景 一、什么是装饰器模式 装饰器模式是一种结构型设计模式&#xff0c;允许通过动态地将新功能添加到现有对象上&#xff0c;来扩展其行为或修改其外观&#xff0c;同时不改变其原始类的结构…

模型轻量化神器:3D格式转化工具HOOPS Exchange可将文件压缩小100倍!

​领先的CAD导入和导出库 使用用于CAD数据转换的HOOPS Exchange SDK将30多种CAD文件格式导入到您的应用程序中&#xff0c;具有对2D和3D CAD文件格式&#xff08;包括 CATIA、SOLIDWORKS、Inventor™、Revit™、Creo、 NX™、Solid Edge 等&#xff0c;全部通过单个API实现。 …

TikTok品牌出海:打造独特内容,提升品牌影响力

随着社交媒体的迅猛发展&#xff0c;TikTok作为全球最热门的短视频平台之一&#xff0c;为品牌出海提供了独特的机遇。然而&#xff0c;要在TikTok上成功推广品牌&#xff0c;关键在于创造出引人注目、有吸引力的内容。本文Nox聚星将和大家探讨在TikTok上&#xff0c;什么样的内…