十大排序算法【原理】【步骤】【动图】【C++实现】

news2024/11/25 10:47:27

十大排序算法

排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:

img

在这里插入图片描述

1.冒泡排序

算法思想

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。

算法步骤

1.比较相邻的元素。如果第一个元素比第二个元素大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

动图演示

img

代码实现

//	冒泡排序
void bubbleSort(vector<int>&nums)
{
	int len = nums.size();
	for (int i = 0; i < len-1; i++)
	{
		for (int j = 0; j < len - 1-i; j++)
		{
			if (nums[j] > nums[j + 1])
			{
				swap(nums[j], nums[j + 1]);
			}
		}
	}
}

2.快速排序(面试常考)

算法思想

快速排序使用分治的思想,选择一个基准,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行递归排序,以达到整个序列有序的目的。快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

  • 先在当前区间利用partition()找到一个基点,将小于这个基点的数放在基点左侧,大于基点的数放在基点右侧
  • 然后再分别对这个基点左侧区间和右侧区间进行上述处理,递归下去即可完成排序。

算法步骤

  • 1.从数列中挑出一个元素,称为 “基准”(pivot);
  • 2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 3.递归地(recursive)对小于基准值元素的子数列和大于基准值元素的子数列进行排序;

动图演示

img

代码实现

//快速排序
void quickSort(vector<int>&nums,int begin,int end)
{
	if (begin >= end) return;
	int low = begin, high = end, key = nums[begin];
	while (low < high)
	{
		while (low < high && nums[high] >= key)
		{
			high--;
		}
		nums[low] = nums[high];
		while (low < high && nums[low] <= key)
		{
			low++;
		}
		nums[high] = nums[low];
	}
	nums[low] = key;
	quickSort(nums, begin, low - 1);
	quickSort(nums, low + 1, end);
}

//另一种写法
int partition(vector<int> &num, int low, int high)
{
    int point = num[low];
    while (low < high)
    {

        while (low < high && num[high] >= point) // 右侧大于等于point不处理
        {
            high--;
        }
        swap(num[low], num[high]); // 将右侧边界的小于point的点与左侧边界交换
        while (low < high && num[low] <= point)
        {
            low++;
        }
        swap(num[low], num[high]);
    }
    return low;
}

void quickSort(vector<int> &num, int low, int high)
{

    if (low >= high)
        return;
    int index = partition(num, low, high);
    quickSort(num, low, index - 1);
    quickSort(num, index + 1, high);
}

3.插入排序

算法思想

插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

算法步骤

  • 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
  • 从头到尾依次从未排序序列中取出一个元素,,在已经排序的元素序列中从后向前扫描,将取出的元素插入有序序列的适当位置。
  • 如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面,保持相应顺序不变。

动图演示

img

代码实现

//插入排序
void insertSort(vector<int>& nums)
{
	int len = nums.size();
	for (int i = 1; i < len; i++)
	{
		if (nums[i] < nums[i - 1])
		{
			int index = i - 1;
			int temp = nums[i];

			while (index >= 0 && temp < nums[index])
			{
				nums[index + 1] = nums[index];
				index--;
			}
			nums[index + 1] = temp;
		}
		
	}
}

4.希尔排序

算法思想

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。

算法步骤

  • 选择一个增量序列 t1,t2,……,tk,其中ti>tj, tk=1;
  • 按增量序列个数k,对序列进行k趟排序;
  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

动图演示

图片来源于网络

代码实现

void shellSort(vector<int> &q){
    int gap = q.size() / 2;
    while(gap){
        for(int i = gap; i < q.size(); i += gap){
            int t = q[i], j;
            for(j = i - gap; j >= 0; j -= gap){
                if(q[j] > t)
                    q[j+gap] = q[j];
                else
                    break;
            }
            q[j+gap] = t;
        }
        gap /= 2;
    }
}

5.选择排序

算法思想

选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给>二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。所以选择排序不是一个稳定的排序算法。

算法步骤

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  2. 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
  3. 以此类推,直到所有元素均排序完毕
  4. 时间负复杂度:O(n^2),空间O(1),非稳定排序,原地排序

动图演示

img

代码实现

//选择排序

void selectionSort(vector<int> &q){
    for(int i = 0; i < q.size(); i++){
        for(int j = i + 1; j < q.size(); j++){
            if(q[i] > q[j])
                swap(q[i], q[j]);
        }
    }
}

6.堆排序

算法思想

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:一般升序采用大顶堆,降序采用小顶堆

  • 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列 arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
  • 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列 arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。

算法步骤

  • 1.构造初始堆H[0……n-1];。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
  • 2.把堆首(最大值)和堆尾互换;,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。直到堆的尺寸为 1。

动图演示

img

代码实现

void adjust_heap(vector<int> &nums, int start, int end)
{
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end)
    {
        if (son + 1 <= end && nums[son] < nums[son + 1])
        {
            son++;
        }
        if (nums[dad] > nums[son])
        {
            return;
        }
        else
        {
            swap(nums[dad], nums[son]);
            dad = son;
            son = dad * 2 + 1;
        }
    }
}

void heap_sort(vector<int> &nums, int len)
{
    //构建大顶堆
    for (int i = len / 2 - 1; i >= 0; i--)
    {
        adjust_heap(nums, i, len - 1);
    }
    for (int i = len - 1; i > 0; i--)
    {
        swap(nums[0], nums[i]); //将堆顶元素与末尾元素进行交换
        adjust_heap(nums, 0, i - 1); //重新对堆进行调整
    }
}

7.归并排序

算法思想

**归并排序是比较稳定的排序方法。**该方法使用二分和递归,采用的也是分治的思想。该算法是采用分治法的一个非常典型的应用。

  • 将当前区间不断二分,直到左右区间中只有一个数字时,视其为有序区间,两两进行合并,
  • 然后将两两的有序区间不断合并,最终完成排序

算法步骤

二路归并排序:

  • 1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  • 2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  • 3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  • 4.重复步骤 3 直到某一指针达到序列尾;
  • 5.将另一序列剩下的所有元素直接复制到合并序列尾。

动图演示

这里写图片描述
在这里插入图片描述

代码实现

void Merge(vector<int> &num, int low, int mid, int high)
{
    // low到mid 和 mid+1到high都是有序的
    vector<int> temp(high - low + 1); // 辅助数组,存放排好序的数据
    int i = low;                      // 从low 到mid
    int j = mid + 1;                  // 从mid+1到high
    int k = 0;
    while (i <= mid && j <= high) // 从小到大合并
    {
        if (num[i] < num[j])
        {
            temp[k++] = num[i++];
        }
        else
        {
            temp[k++] = num[j++];
        }
    }
    while (i <= mid)
    {
        temp[k++] = num[i++];
    }
    while (j <= high)
    {
        temp[k++] = num[j++];
    }
    for (int i = 0; i < temp.size(); i++)
    {
        num[low + i] = temp[i];
    }
}

void mergeSort(vector<int> &num, int low, int high)
{
    if (low >= high)
        return;
    int mid = low + ((high - low) >> 1);
    mergeSort(num, low, mid); // 分治法,先二分为左右两个区间,递归下去
    mergeSort(num, mid + 1, high);
    Merge(num, low, mid, high); // 然后将排好序的左右区间合并
}

8.计数排序

9.桶排序

10.基数排序

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

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

相关文章

【面试题32】include和require的区别及用法

文章目录 一、前言二 、include和require的区别三、include和require的用法介绍3.1 include的用法3.2 require的用法及示例 四、include和require的用法示例4.1 包含文件4.2 包含文件并将结果赋值给变量4.3 动态包含文件4.4 使用绝对路径包含文件4.5 包含文件失败处理4.6 包含文…

MATLAB 的函数计算与作图

基本初等函数的输入系统运算与操作函数的输入函数值的计算1. 数值计算方式2. 符号计算方式2.1 函数&#xff1a;sym2.2 函数&#xff1a;syms 函数的作图1. 一般函数 yf(x) 的作图&#xff08;二维&#xff09;作图基本形式作多重线作图的线型和颜色作图的网格和标记、图例、字…

React Dva项目创建Model,并演示数据管理与函数调用

本文的话 我们讲一下定义Model 也就是Dva中redux的部分 我们打开一个刚创建的Dva项目 看到 src下的models 下 就是Model部分 这里 他给我们了一个案例 如果用 react-redux 管理 模块多了之后会看着比较乱 或 很麻烦 但是 大家会发现 在Model中 他将这些都放在一起了 只需要创建…

css属性

1、形状相关的 宽、高、边线&#xff08;粗细、线样式、颜色&#xff09;、弧度、前景色、背景色、透明度 圆角矩形&#xff1a; 随着radius的增加&#xff0c;角会越来越圆&#xff0c;当设置为高的一半&#xff08;高200是直径&#xff09;时&#xff0c;就会成圆角 宽高都是…

请求响应-日期时间参数的接受

日期参数 由于从前端发送的请求中&#xff0c;日期的格式可能各不相同&#xff0c;使用DateTimeFormat注解完成日期参数格式的转换具体关键代码如下&#xff1a; 在postman中发出对应请求携带对应参数结果如下&#xff1a; 参数名称要与方法中的形参名称一致&#xff0c;免得…

oracle排序问题

记录工作中遇到的问题让工作更加顺利! 文章目录 1.排序1.1数字字符串排序问题解决1.2自定义处理NULL 1.排序 工作中遇到一个需要排序的地方&#xff0c;遇到两个函数 DENSE_RANK和 RANK &#xff1b;RANK 函数是按照一个字段或值排序后返回绝对位置&#xff08;即相同值排名相…

HCIA云计算1

KVM是所有云平台的底座&#xff0c;云下面是虚拟化云台&#xff0c;虚拟化只提供基础架构&#xff0c;云可以提供服务&#xff0c;云是大杂烩。 OpenStack 开源云操作系统 KVM 开源虚拟化 Linux KVM OpenStack 大部分云厂商都是基于OpenStack 做二次开发 VRM理解成物理服务器…

推荐10个Flutter开源项目

作为跨平台应用开发的领头羊,Flutter从已发布就受到广大开发者的追捧。使用Flutter技术开发的应用不仅体验上无限接近原生应用,在开发效率上也是其他技术无法比拟的。随着其开发者社区的不断壮大,Flutter生态系统已经相当强大,并且众多开源应用程序也相继诞生。这些开源应用…

杨氏模量——从宏观(应力-应变曲线)到微观(原子键)尺度解释杨氏模量

杨氏模量&#xff08;Young’s Modulus&#xff09;是三个主要弹性常数之一&#xff0c;与剪切模量&#xff08;shear modulus&#xff09;、体积模量&#xff08;bulk modulus&#xff09;一起用于描述材料在载荷下如何变形 以下展示了拉伸试验的应力应变曲线 如果施加的应力…

3Ds max入门教程:创建雪地

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 在本教程中&#xff0c;您将学习在 3ds Max 9 中制作雪地。在本教程中&#xff0c;我们将使用一些原始平面、粒子系统和纹理&#xff0c;看看您可以从中制作出多么有效和逼真的场景。 好的&#xff0c;首先…

Linux学习之变量赋值

变量的命名规则&#xff1a; 变量的名字只能由字母、数字和下划线组成。 不能以数字开头。 为变量赋值的过程&#xff0c;称为变量替换。 变量赋值的方式有以下几种&#xff1a; 变量名变量值 使用let为变量赋值 将命令赋值给变量 将命令结果赋值给变量&#xff0c;使用$()或者…

前端框架Layui实现动态树效果(书籍管理系统左侧下拉列表)

目录 一、前言 1.什么是树形菜单 2.树形菜单的使用场景 二、案例实现 1.需求分析 2.前期准备工作 ①导入依赖 ②工具类 BaseDao&#xff08;通用增删改查&#xff09; BuildTree(完成平级数据到父子级的转换) ResponseUtil&#xff08;将数据转换成json格式进行回显&…

能“出汗”,会“呼吸”的户外行走机器人

美国亚利桑那州立大学(ASU)科学家研制出了世界上第一个能像人类一样出汗、颤抖和呼吸的户外行走机器人模型。这个机器人名叫ANDI&#xff0c;是一个能模仿人类出汗的热敏“热模型”。 ANDI 身上不仅有可以使它行走的关节&#xff0c;还有其他机器人身上都没有的东西——它浑身…

高并发的哲学原理(一)-- 找出单点,进行拆分

人列计算机 《三体》中&#xff0c;刘慈欣设计了一个用人进行二进制运算的计算机&#xff0c;使用了三千万名士兵(晶体管)&#xff1a; 计算机名&#xff1a;秦一号 CPU&#xff1a;秦始皇最精锐的五个军团 挥舞旗帜进行二进制运算 用三个士兵来组成与门、或门、与非门、或非门…

Python批量实现word中查找关键字

一、背景 在日常办公和文档处理中&#xff0c;我们常常需要在大量的Word文档中查找特定的关键字&#xff0c;然后进行接下来的操作&#xff0c;比如关键字替换等。手动逐个打开并搜索文档显然是费时费力的。因此&#xff0c;利用Python编写一个批量实现Word中查找关键字的程序可…

18、气象学中风场的绘制

文章目录 前言一、批量读取数据二、绘制2022年的平均风场三、绘制每个季节的平均风场四、绘制每个月的风场 前言 数据及代码下载链接➡️&#xff1a;如何绘制自定义颜色的风场图 一、批量读取数据 import os import xarray as xrfolder_path "./" file_pattern …

22、ThreadLocal的原理和使用场景

ThreadLocal的原理 每一个thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals&#xff0c;它存储本线程中所有 ThreadLocal对象及其对应的值 ThreadLocalMap 由一个个Entry对象构成 Entry继承自WeakReference<ThreadLocal<?>>&#xff0c;一个Entry…

Qt6 绘制矩形和一些字符串函数讲解

Qt6 绘制矩形和一些字符串函数讲解 【1】Qt 6 模拟C的cout输出QTextStream类简介举例 &#xff08;标准输出&#xff09; 【2】Qt 6 绘制移动的矩形事件运行效果UI界面头文件.h源文件.cpp 【1】Qt 6 模拟C的cout输出 只教方法&#xff0c;更多内容请学习官方文档 QTextStream…

使用FreeMarker自定义生成word文档

使用FreeMarker自定义生成word文档 最终生成word文档如下&#xff1a; 实现思路&#xff1a; 按照要生成的文档模板格式&#xff0c;创建一个新的word&#xff08;doc&#xff09;文档&#xff0c;将其调整成所需格式&#xff0c;然后处理其中需要动态填充的数据&#xff0…

stable diffusion如何确保每张图的面部一致?

可以使用roop插件&#xff0c;确定好脸部图片后&#xff0c;使用roop固定&#xff0c; 然后生成的所有图片都使用同一张脸。 这款插件的功能简单粗暴&#xff1a;一键换脸。 如图所示&#xff1a; 任意上传一张脸部清晰的图片&#xff0c;点击启用。 在其他提示词不变的情况下…