数据结构—内部排序(上)

news2024/11/18 11:43:59

文章目录

  • 8.内部排序(上)
    • (1).排序基础
      • #1.为什么是内部排序
      • #2.排序的稳定性
    • (2).冒泡排序
      • #1.算法思想
      • #2.代码实现
      • #3.稳定性与时间复杂度分析
    • (3).选择排序
      • #1.算法思想
      • #2.代码实现
      • #3.稳定性与时间复杂度分析
    • (4).插入排序
      • #1.算法思想
      • #2.代码实现
      • #3.稳定性与时间复杂度分析
    • (5).希尔排序
      • #1.算法思想
      • #2.代码实现
      • #3.稳定性和时间复杂度分析
    • 小结

8.内部排序(上)

  所以其实学会对一组数据进行排序,是我们很自然的想法,比如我们在玩扑克牌的时候随机摸了一组牌,为了之后打起来方便,你可能也会按照花色和数字进行排序,所以,让我们来了解一下排序吧!

(1).排序基础

#1.为什么是内部排序

  与内部排序相对的,就是外部排序,在数据量极其庞大的情况下,我们可能无法一次性将待排序的数据全部载入内存再排序,而这种涉及到内存与外存数据交换的排序方式就称为外部排序,与之相对的就是内部排序,我们可以很简单的一次性把所有数据载入内存,用各种方式完成排序。

  外部排序的流程比较复杂,因此基于数据结构这门课的这系列博客当中,我们会简要介绍内部排序的基本内容。

#2.排序的稳定性

  排序算法是具有稳定性的说法的,在待排序的数据当中,基于排序规则相同的一系列数据如果能在排序完成后保持原有的顺序,则称这种排序算法是稳定的
  例如5, 1, 2(1), 4(1), 4(2), 2(2), 4(3),稳定排序的结果应当是:1,2(1), 2(2), 4(1), 4(2), 4(3), 5

(2).冒泡排序

#1.算法思想

  冒泡排序是相当自然的排序方法,假设从小到大排序,我们从左往右,依次比较相邻的元素,如果满足左边小于等于右边,就继续比较下面两个元素,如果顺序错误,则交换两个元素,继续交换

#2.代码实现

  如此继续下去可以保证每轮都能找出一个最大值,并且将它放在最后,这样的过程就好像一个个泡泡从水底逐渐冒到了水面上,故称为冒泡排序,它的代码实现如下:

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

void bubble_sort(vector<int>& v)
{
    bool isSort{ false };
    for (int i = v.size() - 1; i >= 0; i--) {
        for (int j = 0; j < i; j++) {
            if (v[j] > v[j + 1]) {
                int tmp{ v[j + 1] };
                v[j + 1] = v[j];
                v[j] = tmp;
                isSort = true;
            }
        }
        if (!isSort) break;
    }
}

int main()
{
    vector<int> v{ 3, 2, 3, 5, 6, 7, 8, 9, 1, 23 };
    bubble_sort(v);
    for (auto& i : v) {
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

#3.稳定性与时间复杂度分析

  我们每次将冒泡的上界减一,然后每次再找到位置错误的进行交换,一直这样循环下去即可。接下来分析一下冒泡排序的时间复杂度,我们有两重循环,可以得到:
T ( n ) = ∑ k = 1 n ( n − k ) = n ( n − 1 ) 2 ⇒ O ( T ( n ) ) = O ( n 2 ) T(n) = \sum_{k=1}^n (n-k) = \frac{n(n-1)}{2}\Rightarrow O(T(n))=O(n^2) T(n)=k=1n(nk)=2n(n1)O(T(n))=O(n2)
  所以冒泡排序是比较基本的 O ( n 2 ) O(n^2) O(n2)排序算法,然后是稳定性,其实这个好说,因为在冒泡排序中我们只涉及到前后两个顺序错误元素的交换,因此相同的元素在这个过程中是不会被交换的,所以冒泡排序是一种稳定的排序方式

(3).选择排序

#1.算法思想

  选择排序则要更接近我们自己尝试排序的过程,比如3, 2, 3, 5, 6, 7, 8, 9, 1, 23这个序列,我们首先找到最小值1,放到最前面:1, 3, 2, 3, 5, 6, 7, 8, 9, 23,然后从1后取第二个最小值放到第二个位置:1, 2, 3, 3, 5, 6, 7, 8, 9, 23,然后就这么做下去吧!每次找出最小值或者最大值,放到上一次放的下一个位置,就可以完成整个排序流程了,这就是插入排序。

#2.代码实现

  每次选择一个最小或最大的值出来,放到头或尾去,因此这个算法称为选择排序。

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

void selection_sort(vector<int>& v)
{
    for (int i = 0; i < v.size() - 1; i++) {
        int min_idx{ i };
        for (int j = i + 1; j < v.size(); j++) {
            if (v[min_idx] > v[j]) {
                min_idx = j;
            }
        }
        int tmp{ v[i] };
        v[i] = v[min_idx];
        v[min_idx] = tmp;
    }
}

int main()
{
    vector<int> v{ 3, 2, 3, 5, 6, 7, 8, 9, 1, 23 };
    selection_sort(v);
    for (auto& i : v) {
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

  这段代码主要也就做了两件事:找后续值的最小值以及放到对应的位置上去

#3.稳定性与时间复杂度分析

  还是先看看时间复杂度吧,这里就不推了,其实还是和冒泡排序相似的过程,选择排序的时间复杂度仍然是 O ( n 2 ) O(n^2) O(n2),所以我们想要突破 O ( n 2 ) O(n^2) O(n2)的时间复杂度,好像非常困难啊。

  然后是稳定性,选择排序实际上是一种不稳定的排序方式,比如我们前面写的代码,对于这个序列:1, 2, 3, 2, 1,当i走到1的时候,2会与后面的1进行一次交换,而这样做之后就破坏了原本两个2的顺序,这样就导致选择排序变得不稳定了起来,比如:
p16

(4).插入排序

#1.算法思想

  插入排序在我们玩扑克牌的时候比较常用,比如我们拿到了一副牌:A,7,4,2,Q,9,J,那我们可能会这么排,首先A不动,看到7,也不动,再到4,插到A和7之间,再把2插入A和4之间,就这么做下去,直到变成A,2,4,7,9,J,Q这样的顺序,这就是插入排序,我们依次向后遍历,然后找到对应元素在前面已经有序的序列中的位置,插入进去,这就是插入排序的基本流程

#2.代码实现

  我们这里把找到合适的位置和移动合在一起,可以减少一次遍历的开销

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

void insertion_sort(vector<int>& v)
{
    for (int i = 1; i < v.size(); i++) {
        int tmp{ v[i] };
        int j = i - 1;
        for (; j >= 0 && tmp < v[j]; j--) {
            v[j + 1] = v[j];
        }
        v[j + 1] = tmp;
    }
}

int main()
{
    vector<int> v{ 1, 2, 3, 2, 1 };
    insertion_sort(v);
    for (auto& i : v) {
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

#3.稳定性与时间复杂度分析

  插入排序的复杂度同样为 O ( n 2 ) O(n^2) O(n2),不过插入排序对于基本有序的序列,插入排序的移动和查找操作次数非常少,所以之后在设计你自己的排序方式的时候,可以尝试在基本有序的情况下使用插入排序优化,而在比较混乱的情况下采取更快的排序方式

  稳定性其实很好分析,我们的排序过程中不存在直接交换两个点的过程,只是依次向左交换,因此插入排序可以保证稳定性。

  还有一个点需要注意的点,插入排序对于链式存储也是比较容易的,因为不涉及到点的随机访问。对了,对于数组的实现,我们或许还可以使用二分查找的方式更快地找到插入的位置,不过时间复杂度是不会变的哦

(5).希尔排序

#1.算法思想

  希尔排序是一种基于插入排序的修改法则,我们在插入排序的过程中每次往前移动一个位置,而如果能一次移动更多位置,或许排序的速度就能被优化了。

  因此希尔排序(递减增量排序)就出现了,我们给出 d 0 , d 1 , d 2 , . . . , d t − 1 d_0,d_1,d_2,...,d_{t-1} d0,d1,d2,...,dt1这么一系列严格递减的正整数增量,且 d t − 1 = 1 d_{t-1}=1 dt1=1
,每次排序的时候,对整个序列按照增量为 d i d_i di进行分组,然后对不同的分组分别进行排序,直到最后再用增量为1的序列对所有数据进行一次排序。

  需要注意的是,我们可以保证这么排序一定至少不会让时间复杂度变高,因为插入排序本身对于基本有序的数据的排序时间复杂度就会更低。

#2.代码实现

  基本上,我们只需要在基本的插入排序前面加一层对于递减增减的序列就好了,然后把对应的j-1改成j-delta,就好了:

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

void shell_sort(vector<int>& v)
{
    vector<int> d{ 7, 5, 3, 1 };
    for (int delta = 0; delta < d.size(); delta++) {
        int h = d[delta];
        for (int i = h; i < v.size(); i++) {
            int tmp{ v[i] };
            int k{ i - h };
            for (; k >= 0 && tmp < v[k]; k -= h) {
                v[k + h] = v[k];
            }
            v[k + h] = tmp;
        }
    }
}

int main()
{
    vector<int> v{ 1, 2, 3, 2, 1, 2, 3, 2, 1 };
    shell_sort(v);
    for (auto& i : v) {
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

#3.稳定性和时间复杂度分析

  希尔排序的时间复杂度很难分析,它的时间复杂度基本基于你的增量序列选择,这里要注意,增量序列要避免出现因子,比如9和3同时出现就会多一次没有必要的排序。

  Knuth在1969年推荐了这样一个序列: d i + 1 = 3 d i + 1 d_{i+1}=3d_i+1 di+1=3di+1,经过分析,比较的次数不超过 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23)我们终于突破了 O ( n 2 ) O(n^2) O(n2)!

小结

  最近实在是太忙,今天只能暂时先把内部排序的上半部分发出来了,下半部分我们会讲讲归并排序、快速排序和基数排序的内容。

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

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

相关文章

文心一言 VS 讯飞星火 VS chatgpt (133)-- 算法导论11.2 5题

五、用go语言&#xff0c;假设将一个具有n个关键字的集合存储到一个大小为 m 的散列表中。试说明如果这些关键字均源于全域U&#xff0c;且|U|>nm&#xff0c;则U 中还有一个大小为n 的子集&#xff0c;其由散列到同一槽位中的所有关键字构成&#xff0c;使得链接法散列的查…

三分钟学完Git版本控制常用指令

基本指令 git clone [url] 克隆远程仓库到本地 git clone https://gitee.com/mayun2023a/mprpc.git2.git checkout -b xxx 切换至新分支xxx&#xff08;相当于复制了remote的仓库到本地的xxx分支上) 3.修改或者添加本地代码&#xff08;部署在硬盘的源文件上&#xff09; 4.g…

LabVIEW中如何在网络上使用远程VI服务器

LabVIEW中如何在网络上使用远程VI服务器 如何在网络上使用远程VI服务器&#xff1f; 解答: 首先&#xff0c;需要在远程的计算机上打开一个在VI服务器上的LabVIEW应用程序的引用。这可以通过“Open ApplicationReference“函数实现。然后用“Open VI Reference”函数打开一个…

【入门Flink】- 10基于时间的双流联合(join)

统计固定时间内两条流数据的匹配情况&#xff0c;需要自定义来实现——可以用窗口&#xff08;window&#xff09;来表示。为了更方便地实现基于时间的合流操作&#xff0c;Flink 的 DataStrema API 提供了内置的 join 算子。 窗口联结&#xff08;Window Join&#xff09; 一…

Acer宏碁Aspire A715-75G笔记本工厂模式原厂Windows10预装OEM系统2004带恢复功能

下载链接&#xff1a;https://pan.baidu.com/s/1nJFd25lElc1VAPf_RqSDYA?pwdd05h 提取码&#xff1a;d05h 原装出厂系统自带所有驱动、Office办公软件、出厂主题壁纸、系统属性Acer宏基专属的LOGO标志、 Acer Care Center、Quick Access等预装程序 所需要工具&#xff1a…

Linux文件系统(1)

Linux文件系统(1) &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;Linux &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容从系统层面重新认识我们的文件系统 文…

关于值传递和引用传递的问题记录

目录 1. 问题概述 1.1 测试 1.2 结果 2. ArrayList和Arrays.ArrayList 1. 问题概述 最近忙着写论文很久没更新了&#xff0c;趁现在有时间简单记录一下最近遇到的一个坑。 对于Java中的List<>类型的对象&#xff0c;按我以前理解是引用传递&#xff0c;但有一点要注…

Java12新增特性

前言 前面的文章&#xff0c;我们对Java9、Java10、Java11的特性进行了介绍&#xff0c;对应的文章如下 Java9新增特性 Java10新增特性 Java11新增特性 今天我们来介绍一下Java12版本的新增特性 版本介绍 Java 12是Java SE的第12个版本&#xff0c;于2019年3月19日发布。这个…

fastANI-基因组平均核酸一致性(ANI)计算

文章目录 简介安装使用Many to Man-使用基因组路径作为输入One to One 结果其他参数说明可视化两个基因组之间的保守区域并行化 简介 FastANI 是为快速计算全基因组平均核苷酸同一性&#xff08;Average Nucleotide Identity&#xff0c;ANI&#xff09;而开发的&#xff0c;无…

【第七章】软件设计师 之 程序设计语言与语言程序处理程序基础

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 1、前言 正规式 2、编译过程 编译型&…

深度解析找不到msvcp120.dll相关问题以及解决方法

​在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp120.dll丢失”。这个错误通常会导致某些应用程序无法正常运行&#xff0c;给用户带来很大的困扰。那么&#xff0c;如何解决msvcp120.dll丢失的问题呢&#xff1f;本文将为大家介绍…

xml schema中的sequence的含义

https://www.w3.org/TR/xmlschema-1/#element-sequence xml schema中的sequence的含义&#xff1a;包含的元素必须按规定的顺序出现。通过属性maxOccurs和minOccurs可以定义最多、最少出现的次数。最多可以定义不限制次数&#xff0c;最少可以定义0次。默认是最少出现1次&…

pyTorch Hub 系列#4:PGAN — GAN 模型

一、主题描述 2014 年生成对抗网络的诞生及其对任意数据分布进行有效建模的能力席卷了计算机视觉界。两人范例的简单性和推理时令人惊讶的快速样本生成是使 GAN 成为现实世界中实际应用的理想选择的两个主要因素。 然而&#xff0c;在它们出现后的很长一段时间内&#xff0c;GA…

02:2440---时钟体系

目录 一:时钟控制 1:基本概念 2:时钟结构图 3:结构图分析 4:总线 5:寄存器 A:FCLK--MPLLCON B:HCLK和PCLK--CLKDIVN C:注意 二:上电复位 1:上电复位 2:时钟选择 三:代码 一:时钟控制 1:基本概念 S3C2440A中的时钟控制逻辑可以产生所需的时钟信号&#xff0c;包括C…

node插件express(路由)的插件使用(二)——cookie 和 session的基本使用区别

文章目录 前言一、express 框架中的 cookie0、cookie的介绍和作用1. 设置cookie2.删除cookie3.获取cookie&#xff08;1&#xff09;安装cookie-parser&#xff08;2&#xff09;导入cookie-parser&#xff08;3&#xff09;注册中间件&#xff08;4&#xff09;获取cookie&…

类和对象(3):拷贝构造函数

引入&#xff1a; class Stack { public:Stack(int capacity 3){_a (int*)malloc(sizeof(int) * capacity);if (nullptr _a){perror("malloc");exit(-1);}_top 0;_capacity capacity;}~Stack(){free(_a);_top _capacity 0;_a nullptr;}private:int* _a;int _…

终止进程,GPU显存仍被占用 | kill -9彻底杀死进程

问题描述&#xff1a;在Linux终端把进程终止后&#xff0c;发现显存没有被释放出来&#xff01; 显示所有进程 ps aux|grep python杀死单个进程 kill -9 PID杀死多个进程 kill -9 PID PID PID...结果如下&#xff0c;显存已经被释放出来了&#xff01;

【操作系统】1.1 操作系统的基础概念、功能以及特性

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

gpt支持json格式的数据返回(response_format: ‘json_object‘)

Api.h5.chatCreateChatCompletion({model: gpt-3.5-turbo-1106,token: sk-f4fe8b67-fcbe-46fd-8cc9-fd1dac5d6d59,messages: [{role: user,content:使用json格式返回十二生肖&#xff0c;包含中文名和英文名&#xff0c;[{id:"1", enName:"", cnName: &quo…