三傻排序和对数器

三傻排序、对数器

前置知识:无,三傻排序会的可以直接跳过,对数器一定要理解

三傻排序是所有排序中时间复杂度最差的(时间复杂度请看后面的内容),在实际的工作中,插入排序在数据量小的时候还会用到,其余两个基本上不会用到。

三傻排序

选择排序

选择排序的逻辑很简单,首先找到数组中最小的那个元素,其次,将它和数组的第一个元素交换(如果第一个元素就是最小的,那么就和自己交换)。再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此反复,直到整个数组排序。

设数组的长度为 n n n ,选择排序的算法流程如下图所示:

  1. 初始状态下,所有元素未排序,即未排序(索引)区间为 [ 0 , n − 1 ] [0, n-1] [0,n1]
  2. 选取区间 [ 0 , n − 1 ] [0, n-1] [0,n1] 中的最小元素,将其与索引 0 0 0 处的元素交换。完成后,数组前 1 个元素已排序。
  3. 选取区间 [ 1 , n − 1 ] [1, n-1] [1,n1] 中的最小元素,将其与索引 1 1 1 处的元素交换。完成后,数组前 2 个元素已排序。
  4. 以此类推,经过 n − 1 n - 1 n1 轮选择与交换后,数组前 n − 1 n - 1 n1 个元素已排序。
  5. 仅剩的一个元素必定是最大元素,无须排序,因此数组排序完成。
    在这里插入图片描述
    选择排序的算法实现:
void swap(int *num1, int *num2) {int tmp = *num1;*num1 = *num2;*num2 = tmp;
}void selectSort(std::vector<int> &nums) {int min_index = 0;for (int i = 0; i < nums.size(); ++i) {min_index = i;for (int j = i+1; j < nums.size(); ++j) {if (nums[min_index] > nums[j])min_index = j;}swap(&nums[i], &nums[min_index]);}
}

算法特性

  • 时间复杂度为 O ( n 2 ) O(n^2) O(n2)、非自适应排序:外循环共 n − 1 n - 1 n1 轮,第一轮的未排序区间长度为 n n n ,最后一轮的未排序区间长度为 2 2 2 ,即各轮外循环分别包含 n n n n − 1 n - 1 n1 … \dots 3 3 3 2 2 2 轮内循环,求和为 ( n − 1 ) ( n + 2 ) 2 \frac{(n - 1)(n + 2)}{2} 2(n1)(n+2)
  • 空间复杂度为 O ( 1 ) O(1) O(1)、原地排序:指针 i i i j j j 使用常数大小的额外空间。
  • 非稳定排序:如下图所示,元素 nums[i] 有可能被交换至与其相等的元素的右边,导致两者的相对顺序发生改变。

在这里插入图片描述

冒泡排序

冒泡排序的排序原理是:通过连续地比较与交换相邻元素实现排序。这个过程就像气泡从底部升到顶部一样,因此得名冒泡排序。

通过下面的动图可知,冒泡过程可以利用元素交换操作来模拟:从数组最左端开始向右遍历,依次比较相邻元素大小,如果“左元素 > 右元素”就交换二者。遍历完成后,最大的元素会被移动到数组的最右端。

在这里插入图片描述
冒泡排序的代码实现:

void swap(int *num1, int *num2) {int tmp = *num1;*num1 = *num2;*num2 = tmp;
}void bubbleSort(std::vector<int> &nums) {for (int i = nums.size() - 1; i >= 0; --i) {for (int j = 0; j < i; ++j) {if (nums[j] > nums[j + 1])swap(&nums[j], &nums[j+1]);}}
}

上面的代码还可以进行优化,如果数组中的元素是有序的,在一轮比较操作完成以后不会执行任何交换操作,我们可以直接返回结果。因此,可以增加一个标志位 flag 来监测这种情况,一旦出现就立即返回。经过优化,冒泡排序的最差时间复杂度和平均时间复杂度仍为 O ( n 2 ) O(n^2) O(n2) ;但当输入数组完全有序时,可达到最佳时间复杂度 O ( n ) O(n) O(n)

void swap(int *num1, int *num2) {int tmp = *num1;*num1 = *num2;*num2 = tmp;
}void bubbleSort(std::vector<int> &nums) {bool flag = true;for (int i = nums.size() - 1; i >= 0; --i) {for (int j = 0; j < i; ++j) {if (nums[j] > nums[j + 1]) {swap(&nums[j], &nums[j+1]);flag = false;}}if (flag)break;}
}

算法特性

  • 时间复杂度为 O ( n 2 ) O(n^2) O(n2)、自适应排序:各轮“冒泡”遍历的数组长度依次为 n − 1 n - 1 n1 n − 2 n - 2 n2 … \dots 2 2 2 1 1 1 ,总和为 ( n − 1 ) n / 2 (n - 1) n / 2 (n1)n/2 。在引入 flag 优化后,最佳时间复杂度可达到 O ( n ) O(n) O(n)
  • 空间复杂度为 O ( 1 ) O(1) O(1)、原地排序:指针 i i i j j j 使用常数大小的额外空间。
  • 稳定排序:由于在“冒泡”中遇到相等元素不交换。

插入排序

插入排序十分常见,如我们在打牌的时候,通常会将扑克牌划分为“有序”和“无序”两部分,并假设初始状态下最左 1 张扑克牌已经有序。在无序部分抽出一张扑克牌,插入至有序部分的正确位置;完成后最左 2 张扑克已经有序。不断循环步骤插牌,每一轮将一张扑克牌从无序部分插入至有序部分,直至所有扑克牌都有序。
在这里插入图片描述

通过上面的整理扑克牌可以直到插入排序的排序原理:在未排序区间选择一个基准元素,将该元素与其左侧已排序区间的元素逐一比较大小,如果小于已排序区间的元素则继续向前直到最左侧或大于已排序区间的元素,插入在当前位置。如此反复,直到所有元素完成排序。

插入排序的算法实现:

void swap(int *num1, int *num2) {int tmp = *num1;*num1 = *num2;*num2 = tmp;
}void insertSort(std::vector<int> &nums) {for (int i = 1; i < nums.size(); ++i) {for (int j = i - 1; j >= 0 && nums[j] > nums[j+1]; --j) {swap(&nums[j], &nums[j+1]);}}
}

算法特性

  • 时间复杂度为 O ( n 2 ) O(n^2) O(n2)、自适应排序:在最差情况下,每次插入操作分别需要循环 n − 1 n - 1 n1 n − 2 n-2 n2 … \dots 2 2 2 1 1 1 次,求和得到 ( n − 1 ) n / 2 (n - 1) n / 2 (n1)n/2 ,因此时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。在遇到有序数据时,插入操作会提前终止。当输入数组完全有序时,插入排序达到最佳时间复杂度 O ( n ) O(n) O(n)
  • 空间复杂度为 O ( 1 ) O(1) O(1)、原地排序:指针 i i i j j j 使用常数大小的额外空间。
  • 稳定排序:在插入操作过程中,我们会将元素插入到相等元素的右侧,不会改变它们的顺序。

插入排序的优势

插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) ,而我们即将学习的快速排序的时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn) 。尽管插入排序的时间复杂度更高,但在数据量较小的情况下,插入排序通常更快

这个结论与线性查找和二分查找的适用情况的结论类似。快速排序这类 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的算法属于基于分治策略的排序算法,往往包含更多单元计算操作。而在数据量较小时, n 2 n^2 n2 n log ⁡ n n \log n nlogn 的数值比较接近,复杂度不占主导地位,每轮中的单元操作数量起到决定性作用。

实际上,许多编程语言(例如 Java)的内置排序函数采用了插入排序,大致思路为:对于长数组,采用基于分治策略的排序算法,例如快速排序;对于短数组,直接使用插入排序。

虽然冒泡排序、选择排序和插入排序的时间复杂度都为 O ( n 2 ) O(n^2) O(n2) ,但在实际情况中,插入排序的使用频率显著高于冒泡排序和选择排序,主要有以下原因:

  • 冒泡排序基于元素交换实现,需要借助一个临时变量,共涉及 3 个单元操作;插入排序基于元素赋值实现,仅需 1 个单元操作。因此,冒泡排序的计算开销通常比插入排序更高
  • 选择排序在任何情况下的时间复杂度都为 O ( n 2 ) O(n^2) O(n2)如果给定一组部分有序的数据,插入排序通常比选择排序效率更高
  • 选择排序不稳定,无法应用于多级排序。

对数器

当我们在网上找到某个公司的面试题,想了好久,感觉自己会做,但是找不到在线测试;又或者在和朋友交流面试题时,有一个新的题,感觉自己会做,但也没有在线测试;或者在网上进行刷题练习时,前几个测试用例都过了,突然出现一个巨大无比的数据量,结果我们的代码报错,但是我们根本看不出哪里出错,甚至有的根本不提示哪个例子出错,无法 Debug 等情况下,如何验证自己实现的算法。

对数器就是一个针对没有测试环境或测试环境有限制的时候,自己对算法实现进行验证的一个工具,主要有以下几步:

  1. 实现一个随机样本产生器,生成大小随机以及元素随机的数组;
  2. 复制多个相同的样本,用于算法的验证
  3. 针对问题实现算法 A,这个算法的实现一般来说绝对正确并且容易实现,但是复杂度不好(一般最多是暴力法);
  4. 实现你想要验证的算法 B;
  5. 如果有一个随机样本使得结果不一致,可以进行打印输出等方式查看出错的原因,并修改算法;如果在一个很大的样本下,两个算法的结果都是一致的,则认为算法 a 是正确的。

对数器中样本大小可以手动控制,当算法出错时,我们可以将样本变小,使用这个较小的作物样本进行 Debug。最常用的排查方式有 print 大法、断点技术等。

下面我们使用对数器对之前的三个排序进行验证,代码示例如下:

#include <cstdlib>
#include <ctime>
#include <iostream>
#include <vector>using namespace std;template<typename T>
void mySwap(T& val1, T& val2) {T temp = val1;val1 = val2;val2 = temp;
}// 插入排序
template<typename T>
void insertionSort(vector<T>& vec) {if (vec.empty() || vec.size() < 2)return;for (int i = 1; i < vec.size(); ++i) {for (int j = i - 1; j >= 0 && vec[j] > vec[j+1]; --j) {mySwap(vec[j], vec[j+1]);}}
}// 冒泡排序
template<typename T>
void bubbleSort(vector<T>& vec) {if (vec.empty() || vec.size() < 2)return;bool flag = true;for (int i = vec.size() - 1; i > 0; --i) {for (int j = 0; j < i; ++j) {if (vec[j] > vec[j+1]) {mySwap(vec[j], vec[j+1]);flag = false;}}if (flag)break; }
}// 选择排序
template<typename T>
void selectionSort(vector<T>& vec) {if (vec.empty() || vec.size() < 2)return;int min_index = 0;for (int i = 0; i < vec.size() - 1; ++i) { min_index = i;for (int j = i + 1; j < vec.size(); ++j) {if (vec[j] < vec[min_index])min_index = j;}mySwap(vec[min_index], vec[i]);}
}// 生成一个随机数组
void generateRandomVector(vector<int>& nums, const int max_capacity, const int max_value) {int capacity = rand() % max_capacity;if (capacity == 0)return;nums.resize(capacity);for (int i = 0; i < capacity; ++i) {int val = rand() % max_value;nums[i] = val;}
}vector<int> copyVector(const vector<int>& nums) {return vector<int>(nums);
}bool isEqual(const vector<int>& nums1, const vector<int>& nums2) {if (nums1.size() != nums2.size())return false;for (int i = 0; i < nums1.size(); ++i) {if (nums1[i] != nums2[i])return false;}return true;
}void printArray(const vector<int> &nums) {for (auto i : nums) cout << i << " ";cout << '\n';
}int main() {srand((unsigned)time(0));const int kTestCounts = 1000000; // 测试次数const int kMaxCapcity = 100;  // 数组的最大容量const int kMaxValue = 100;    // 数组元素的最大值cout << "测试开始\n";vector<int> nums1;bool flag = true;for (int i = 0; i < kTestCounts; ++i) {// 1. 生成一个随机样本generateRandomVector(nums1, kMaxCapcity, kMaxValue);// 2. 复制样本vector<int> nums2 = copyVector(nums1);vector<int> nums3 = copyVector(nums1);// 3. 实现算法bubbleSort(nums1);selectionSort(nums2);insertionSort(nums3);// 4. 比较每个算法的结果,用于 Debugif (!isEqual(nums1, nums2) || !isEqual(nums2, nums3)) {cout << "-------nums1-------\n";printArray(nums1);cout << "-------nums2-------\n";printArray(nums2);cout << "-------nums3-------\n";printArray(nums3);flag = false;break;}}if (flag) {cout << "测试结束,算法正确\n";vector<int> nums;generateRandomVector(nums, kMaxCapcity, kMaxValue);printArray(nums);bubbleSort(nums);printArray(nums);} else {cout << "算法出错了!\n";}return 0;
}

输出结果(一种可能):

测试开始
测试结束,算法正确
44 87 65 29 40 8 14 35 59 64 97 40 82 84 83 21 11 0 66 86 22 65 86 57 28 88 56 2 91 85 82 88 72 99 17 65 60 31 0 19 95 49 11 29 86 95 3 49 47 21 35 21 39 74 30 67 62 86 69 6 23 3 94 96 55 11 13 15 94 13 34 89 14 97 71 0 44 74 49 92 
0 0 0 2 3 3 6 8 11 11 11 13 13 14 14 15 17 19 21 21 21 22 23 28 29 29 30 31 34 35 35 39 40 40 44 44 47 49 49 49 55 56 57 59 60 62 64 65 65 65 66 67 69 71 72 74 74 82 82 83 84 85 86 86 86 86 87 88 88 89 91 92 94 94 95 95 96 97 97 99

对数器的门槛其实是比较高的,因为往往需要在两种不同思路下实现功能相同的两个算法,暴力一个、想象中的最优解是另一个。后续的很多题目都会用到对数器,几乎可以验证任何方法,尤其在验证贪心、观察规律方面很有用。

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

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

相关文章

库存管理内训课件|39页PPT

文件是一份关于库存管理的内训课件&#xff0c;内容涵盖了库存管理的定义、分类、作用、存在的问题、管控目标以及具体管控措施。以下是对课件内容的总结&#xff1a; 1. 定义及分类 库存&#xff1a;为满足未来需求而暂时闲置的有价值的资源&#xff0c;与物品是否处于运动状…

【WebRTC】WebRTC的简单使用

目录 1.下载2.官网上的使用3.本地的使用 参考&#xff1a; 【webRTC】一、windows编译webrtc Windows下WebRTC编译 1.下载 下载时需要注意更新python的版本和网络连接&#xff0c;可以先试试ping google。比较关键的步骤是 cd webrtc-checkout set https_proxy127.0.0.1:123…

【设计模式系列】组合模式(十二)

目录 一、什么是组合模式 二、组合模式的角色 三、组合模式的典型应用 四、组合模式在Mybatis SqlNode中的应用 4.1 XML映射文件案例 4.2 Java代码使用案例 一、什么是组合模式 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;其核…

API网关 - JWT认证 ; 原理概述与具体实践样例

API网关主要提供的能力&#xff0c;就是协议转换&#xff0c;安全&#xff0c;限流等能力。 本文主要是分享 如何基于API网关实现 JWT 认证 。 包含了JWT认证的流程&#xff0c;原理&#xff0c;与具体的配置样例 API网关认证的重要性 在现代Web应用和微服务架构中&#x…

ArcGIS 地理信息系统 任意文件读取漏洞复现

0x01 产品简介 ArcGIS是由美国Esri公司研发的地理信息系统(GIS)软件,它整合了数据库、软件工程、人工智能、网络技术、云计算等主流的IT技术,旨在为用户提供一套完整的、开放的企业级GIS解决方案,它包含了一套带有用户界面组件的Windows桌面应用。可以实现从简单到复杂的…

一文了解Android SELinux

在Android系统中&#xff0c;SELinux&#xff08;Security-Enhanced Linux&#xff09;是一个增强的安全机制&#xff0c;用于对系统进行强制访问控制&#xff08;Mandatory Access Control&#xff0c;MAC&#xff09;。它限制了应用程序和进程的访问权限&#xff0c;提供了更…

如何看待AI技术的应用前景?

文章目录 如何看待AI技术的应用前景引言AI技术的现状1. AI的定义与分类2. 当前AI技术的应用领域 AI技术的应用前景1. 经济效益2. 社会影响3. 技术进步 AI技术应用面临的挑战1. 数据隐私与安全2. 可解释性与信任3. 技能短缺与就业影响 AI技术的未来发展方向1. 人工智能的伦理与法…

Java | Leetcode Java题解之第539题最小时间差

题目&#xff1a; 题解&#xff1a; class Solution {public int findMinDifference(List<String> timePoints) {int n timePoints.size();if (n > 1440) {return 0;}Collections.sort(timePoints);int ans Integer.MAX_VALUE;int t0Minutes getMinutes(timePoint…

讲讲 kafka 维护消费状态跟踪的方法?

大家好&#xff0c;我是锋哥。今天分享关于【讲讲 kafka 维护消费状态跟踪的方法&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; 讲讲 kafka 维护消费状态跟踪的方法&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 Kafka 中&#x…

多核架构的基本概念

目录 1.为什么使用多核 2.多核分类 2.1 同构和异构 2.2 SMP和AMP 3 小结 1.为什么使用多核 这个问题个人认为可以从两个方面来看&#xff1a; 性能问题 随着汽车ECU对集成化的要求越来越高&#xff0c;把多个ECU功能集中到一个多核MCU的需求也越来越明显。 以汽车制动…

GitHub | 发布到GitHub仓库并联文件夹的方式

推送到Github 推送步骤如果你只想更新单个文件&#xff0c;只需在第 4 步中指定该文件的路径即可。可能问题一 效果 推送步骤 更新 GitHub 仓库中的文件通常涉及以下步骤&#xff1a; 克隆仓库&#xff1a; 首先&#xff0c;你需要将 GitHub 上的仓库克隆到本地。使用 git …

【ArcGIS】绘制各省碳排放分布的中国地图

首先&#xff0c;准备好各省、自治区、直辖市及特别行政区&#xff08;包括九段线&#xff09;的shp文件&#xff1a; 通过百度网盘分享的文件&#xff1a;GS&#xff08;2022&#xff09;1873 链接&#xff1a;https://pan.baidu.com/s/1wq8-XM99LXG_P8q-jNgPJA 提取码&#…

【网络面试篇】HTTP(2)(笔记)——http、https、http1.1、http2.0

目录 一、相关面试题 1. HTTP 与 HTTPS 有哪些区别&#xff1f; 2. HTTPS 的工作原理&#xff1f;&#xff08;https 是怎么建立连接的&#xff09; &#xff08;1&#xff09;ClientHello &#xff08;2&#xff09;SeverHello &#xff08;3&#xff09;客户端回应 &a…

FastAPI 请求体解析:基础概念与综合应用

FastAPI 请求体解析&#xff1a;基础概念与综合应用 本文深入探讨了 FastAPI 中的请求体概念&#xff0c;强调使用 Pydantic 模型来声明请求体数据结构。通过具体示例&#xff0c;展示了如何定义请求体、可选参数及默认值&#xff0c;提升数据验证和类型提示的便利性。文章还说…

Python并发编程库:Asyncio的异步编程实战

Python并发编程库&#xff1a;Asyncio的异步编程实战 在现代应用中&#xff0c;并发和高效的I/O处理是影响系统性能的关键因素之一。Python的asyncio库是专为异步编程设计的模块&#xff0c;提供了一种更加高效、易读的并发编程方式&#xff0c;适用于处理大量的I/O密集型任务…

Golang--数组、切片、映射

1、数组 1.1 数组类型 var 数组名 [数组大小]数据类型 package main import "fmt"func main(){//1、定义一个数组var arr1 [5]intarr1[0] 100arr1[1] 200fmt.Println(arr1) //[100 200 0 0 0] } 1.2 数组的初始化方式 package main import "fmt" func …

在VS中安装chatGPT

2、在VSCode中打开插件窗口 3、输入ChatGPT 4、这里有个ChatGPT中文版&#xff0c;就它了 5、安装 6、这时候侧边栏多了一个chatGPT分页图标&#xff0c;点击它 7、打个招呼 8、好像不行 9、看一下细节描述 10、根据要求按下按下快捷键 Ctrl Shift P 11、切换成国内模式 12、…

Linux下的Debugfs

debugfs 1. 简介 类似sysfs、procfs&#xff0c;debugfs 也是一种内存文件系统。不过不同于sysfs一个kobject对应一个文件&#xff0c;procfs和进程相关的特性&#xff0c;debugfs的灵活度很大&#xff0c;可以根据需求对指定的变量进行导出并提供读写接口。debugfs又是一个Li…

Fooocus图像生成软件本地部署教程:在Windows上快速上手AI创作

文章目录 前言1. 本地部署Fooocus图像生成软件1.1 安装方式1.2 功能介绍 2. 公网远程访问Fooocus3. 固定Fooocus公网地址 前言 本篇文章将介绍如何在本地Windows11电脑部署开源AI生图软件Fooocus&#xff0c;并结合Cpolar内网穿透工具轻松实现公网环境远程访问与使用。 Foooc…

修改HarmonyOS鸿蒙图标和名字,打包后安装到真机,应用图标丢失变成透明,修改名字也不生效,还是默认的labeL解决方案教程

HarmonyOS鸿蒙打包hap 安装应用到桌面没有图标&#xff0c;用hdc安装到真机&#xff0c;打包后应用图标丢失变成透明&#xff0c;名字也还是默认的label的bug&#xff0c;以下是解决方案 以下是修改方案&#xff1a; 1、修改应用名字&#xff1a; 2、修改应用图标&#xff1a…