算法篇 八大排序(冒泡 插入 选择 堆 希尔 快排 归并 计数)

news2025/6/6 21:05:52

目录

引言

1.冒泡排序

思路

代码实现 

2.选择排序

思路

代码实现(存在易错点)

3.插入排序

思路

 代码实现

4.希尔排序

思路

代码实现

5.堆排序

思路

代码实现

6.快速排序(快排)

一.三路划分

思路

代码实现

 二.自省排序

思路

代码实现

7.归并排序

1.递归法

思路

代码实现

2.非递归法

思路

代码实现

8. 计数排序

思路

代码实现 

总结(附排序训练OJ题)

引言

在计算机科学中,排序算法是非常基础且重要的内容。它可以将一组无序的数据按照特定的顺序(如升序或降序)进行排列。本文将详细介绍八大排序算法:冒泡排序、插入排序、选择排序、堆排序、希尔排序、快速排序、归并排序和计数排序,并结合代码进行分析。(本文展示代码均为升序)

先放一个前置的Swap交换函数方便函数调用:

//交换
void Swap(int* p1, int* p2)
{
    int tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

1.冒泡排序

思路

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。每一趟交换的结果是把最大一个数放在最后。(以升序为例)走访数列的工作是重复地进行直到没有再需要交换,也就是说该数组已经排序完成。

可定义一个flag变量,若第n(n>=0)次没有交换元素,则停止交换 。

时间复杂度:最好情况为 (O(N)),最坏情况为 (O(N^2))。

空间复杂度:(O(1))。

稳定性:稳定。

代码实现 

//冒泡排序
void BubbleSort(int* a, int n)
{
    for(int i = 0; i < n; i++)
    {
        int flag = 0;
        for(int j = 0; j < n - i; j++)
        {
            if(a[j+1] > a[j])
            {
                Swap(&a[j+1], &a[j]);
                flag = 1;
            }
        }
        if(flag == 0)
        {
            break;
        }
    }
}

2.选择排序

思路

选择排序是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

 但是实际操作的时候,每次排一个显得尤为繁琐,因此我们可以一趟选出最大值与最小值,以此将其放在开头与结尾,这样可以让效率得到大大提升。

时间复杂度:(O(N^2))。

空间复杂度:(O(1))。

稳定性:不稳定。(易错)

代码实现(存在易错点)

//选择排序
void SelectSort(int* a, int n)
{
    int begin = 0, end = n - 1;
    while(begin < end)
    {
        int min = begin, max = begin;
        for(int i = begin + 1; i <= end; i++)
        {
            if(a[i] < a[min])
            {
                min = i;
            }
            if(a[i] > a[max])
            {
                max = i;
            }
        }
        Swap(&a[begin], &a[min]);
        if(begin == max)
            max = min;
	    Swap(&a[end], &a[max]);
		++begin;
		--end;
    }
}

这里需要注意!如果max值出现在begin处,begin与min交换后需要把max 与min 的位置交换回来,以避免代码出现置换出错的问题。

3.插入排序

思路

插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

 时间复杂度:最好情况为 (O(N)),最坏情况为 (O(N^2))。

空间复杂度:(O(1))。

稳定性:稳定。

 代码实现

//插入排序 最好O(N) 最坏O(N ^ 2)
void InsertSort(int* a, int n)
{
    for(int i = 0; i < n - 1; i++)
    {
        int end = i;
        int tmp = a[end + 1];
        while(end >= 0)
        {
           if(tmp < a[end])
            {
                a[end + 1] = a[end];
                end--;
            }
            else
            {
                break;
            }
        }
        a[end + 1] = tmp;
    }
}

4.希尔排序

思路

先给定一个小于n的整数gap,将所有距离为gap的元素分为一组,对每一组进行插入排序,再取比gap小的一个整数,重复上述操作,直到该整数取为1,即为插入排序,排序完成。

时间复杂度:(O(N^1.3))。

空间复杂度:(O(1))。

稳定性:不稳定。

代码实现

此处与图示取gap不同,取更为优化的循环gap = gap / 3 + 1,这样也能保证最后一次为1。

void ShellSort(int* a, int n)
{
    int gap = n;
    while(gap > 1)
    {
        // +1保证最后一个gap一定是1
		// gap > 1时是预排序
		// gap == 1时是插入排序
        gap = gap / 3 + 1;
        for(int i = 0; i < n - gap; i++)
        {
            int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
        }
    }
}

5.堆排序

思路

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

将数组通过向下调整算法建成一个堆,然后通过循环把堆顶元素与堆尾元素交换,再将堆用向下调整算法重新调整,重复调整成升序(降序)。

时间复杂度:(O(NlogN))。

空间复杂度:(O(1))。

稳定性:不稳定。

代码实现

//向下调整
void AdjustDown(int* a, int n, int parent)
{
    int child = parent * 2 + 1;
    while(child < n)//找不到孩子
    {
        //找出小的孩子
        if(child + 1 < n && a[child + 1] > a[child])
        {
            child = child + 1;
        }
        //调整
        if(a[child] > a[parent])
        {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}
//堆排序(O(N*logN))
void HeapSort(int* a, int n)
{
    //降序,建小堆
    //升序,建大堆
    
    // for(int i = 1; i < n; i++)
    // {
    //     AdjustUp(a, i);//向上调整建堆(O(N*logN))
    // }
    for(int i = (n-1-1)/2; i >= 0; i--)
    {
        AdjustDown(a, n, i);//向下调整建堆(O(N))
    }

    int end = n - 1;
    //(O(N*logN))
    while(end > 0)
    {
        Swap(&a[0], &a[end]);
        AdjustDown(a, end, 0);
        --end;
    }
}

6.快速排序(快排)

时间复杂度:(O(NlogN)) 有序时退化成(O(N^2))。

空间复杂度:(O(logN))。(易错)

稳定性:不稳定。

快排里面的hoare法,双指针法以及一种非递归方法(还有三数取中,小区间优化两个优化策略)

可以看我这篇博文快速排序(Quick Sort)算法详解(递归与非递归)-CSDN博客

 本文介绍两种快排优化:

一.三路划分

思路

三路划分问题主要来解决数组中存在大量重复数的问题,把所有重复数放在同一个区域可以大大提高效率,不过这样的代价也会牺牲一些其他无重复值时的效率。

 这里的key可以使用随机数选key的策略以提高效率。

代码实现

/快速排序(递归) 三路划分 适用于多个相同值的情况
void QuickSort1(int* a, int left, int right)
{
    if(left >= right)
        return;

    //小区间优化
    if((right - left + 1) < 10)
    {
        InsertSort(a + left, right - left + 1);
    }
    else
    {
        随机数选key可以自行实现.
        int key = a[left];
        int cur = left + 1;
        int begin = left;
        int end = right;
        while(cur <= end)
        {
            if(a[cur] < key)
            {
                Swap(&a[begin], &a[cur]);
                begin++;
                cur++;
            }
            else if(a[cur] > key)
            {
                Swap(&a[cur], &a[end]);
                end--;
            }
            else
            {
                cur++;
            }
        }
        QuickSort1(a, left , begin - 1);
        QuickSort1(a, end + 1 , right);
    }
}

 二.自省排序

思路

这是快排在数据库中的基本代码,拥有足够的优化策略。

这里基本快排代码依旧使用hoare法的找小与找大与小区间优化(具体思路可看上述文章)。

这里要讲的是防止选key时出现二叉树(递归)层数过深的情况下引起的效率降低。

此处定义一个logn用于记录最佳情况下选key的递归深度。当函数递归深度大于两倍logn时使用其他排序方案来提高排序效率,这里使用n*logn的堆排序。

代码实现

//自省排序
void IntroSort(int* a, int left, int right, int depth, int defaultDepth)
{
    if(left >= right)
        return;
    if(right - left + 1 < 16)
    {
        InsertSort(a + left, right - left + 1);
        return;
    }  
    if(depth > defaultDepth)
    {
        HeapSort(a + left, right - left + 1);
        return;
    }
    depth++;
    //随机数取key
    int tmp = left + (rand() % (right - left + 1));
    Swap(&a[left], &a[tmp]);
    int key = left;
    int begin = left;
    int end = right;
    while(begin < end)
    {
        while(begin < end && a[end] > a[key])
        {
            end--;
        }
        while(begin < end && a[begin] <= a[key])
        {
            begin++;
        }
        Swap(&a[end], &a[begin]);
    }
    Swap(&a[key], &a[begin]);
    key = begin;
    IntroSort(a, left, key - 1, depth, defaultDepth);
    IntroSort(a, key + 1, right, depth, defaultDepth);
}
//快排升级版- 自省排序
void QuickSortPlus(int* a, int left, int right)
{
    int depth = 0;
    int logn = 0;
    int n = right - left + 1;
    for(int i = 1; i < n; i *= 2)
    {
        logn++;
    }
    // introspective sort -- ⾃省排序
IntroSort(a, left, right, depth, logn*2);
}

7.归并排序

时间复杂度:(O(NlogN))。

空间复杂度:(O(N))。

稳定性:稳定。

1.递归法

思路

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

将一个数组先通过取中递归的方式拆分成1个单元,再通过归并来使各个分序列有序。如图示

代码实现

//归并子函数
void _MergeSort(int* a, int* tmp, int left, int right)
{
    if(left >= right)
        return; 
    int mid = (left + right) / 2;
    _MergeSort(a, tmp, left, mid);
    _MergeSort(a, tmp, mid + 1, right);

    //归并
    int begin1 = left, end1 = mid;
    int begin2 = mid + 1, end2 = right;
    int count = left;
    while(begin1 <= end1 && begin2 <= end2)
    {
        if(a[begin1] <= a[begin2])
            tmp[count++] = a[begin1++];
        else
            tmp[count++] = a[begin2++];
    }
    while(begin1 <= end1)
        tmp[count++] = a[begin1++];
    while(begin2 <= end2)
        tmp[count++] = a[begin2++];
    memcpy(a + left, tmp + left, (right - left + 1) * sizeof(int));

}
//归并排序(递归)
void MergeSort(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    if(tmp == NULL)
    {
        perror("malloc fail!");
        exit(1);
    }
    _MergeSort(a, tmp, 0, n - 1);

    free(tmp);
    tmp = NULL;
}

2.非递归法

思路

定义一个gap来定义每组归并数据的数据个数,gap每次取二的倍数,再通过归并来实现排序 如图:

可能你会发现:如果数据个数不是二的倍数,那是不是会存在越界访问的风险?

举十个数据为例如图: 

可见后几位似乎都出现了越界,那么共会出现三种情况,可以把后两种归为一类,让最后一组数据不再归并,最后必然出现两组数归并的情况,并且最后一次归并begin2必然不越界,此时修正end2即可形成8.2归并,实现归并排序。

代码实现

//归并排序(非递归)
void MergeSortNonR(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    if(tmp == NULL)
    {
        perror("malloc fail!");
        exit(1);
    }
    //归并
    int gap = 1;
    while(gap < n)
    {
        for(int i = 0; i < n; i += gap * 2)
        {
            int begin1 = i, end1 = i + gap - 1;
            int begin2 = i + gap, end2 = i + gap * 2 - 1;

            //两个修正的解释:举10个数为例依次进行11.22.44归并,最后两个数不纳入归并,
            //最后一次归并begin2必然不越界,此时修正end2即可形成8.2归并,实现归并排序

            //第二组都越界不存在,这一组就不需要归并
            if(begin2 > n)
            {
                break;
            }
            //第二组的begin2没越界,end2越界,需要修正end2取值,重新归并
            if(end2 > n)
            {
                end2 = n - 1;
            }

            int count = i;
            while(begin1 <= end1 && begin2 <= end2)
            {
                if(a[begin1] <= a[begin2])
                    tmp[count++] = a[begin1++];
                else
                    tmp[count++] = a[begin2++];
            }
            while(begin1 <= end1)
                tmp[count++] = a[begin1++];
            while(begin2 <= end2)
                tmp[count++] = a[begin2++];
            memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));
        }
        gap *= 2;
    }

    free(tmp);
    tmp = NULL;
}

8. 计数排序

思路

计数排序是一种非比较排序算法。它的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中 该被开辟的数组空间大小为原数组max - min + 1。以该数组下标为数值,原数组有这个数则a[i]++。

时间复杂度:(O(N + range))。(range为开辟的数组空间大小)

空间复杂度:(O(range))。

代码实现 

//计数排序
// 时间复杂度:O(N+range)
// 只适合整数/适合范围集中
// 空间复杂度:O(range)
void CountSort(int* a, int n)
{
    int min = 0, max = 0;
    for(int i = 1; i < n; i++)
    {
        if(a[i] > a[max])
            max = i;
        if(a[i] < a[min])
            min = i;
    }
    int range = a[max] - a[min] + 1;
    int* count = (int*)calloc(range, sizeof(int));
    if(count == NULL)
    {
        perror("calloc fail!");
        exit(1);
    }
    for(int i = 0; i < n; i++)
    {
        count[a[i] - a[min]]++; // 计数
    }
    //排序
    int j = 0;
    for(int i = 0; i < range; i++)
    {
        while(count[i]--)
            a[j++] = i + a[min];
    }
    free(count);
    count = NULL;
}

总结(附排序训练OJ题)

读完本文,你可以在该OJ题上进行实操来加深印象:912. 排序数组 - 力扣(LeetCode)

不同的排序算法有不同的时间复杂度和空间复杂度,适用于不同的场景。在实际应用中,我们需要根据数据的特点和需求选择合适的排序算法。例如,当数据量较小且基本有序时,插入排序可能是一个不错的选择;而当数据量较大时,快速排序、归并排序和堆排序通常表现更好。计数排序则适用于数据范围较小且为整数的情况。

通过本文的介绍和代码实现,相信你对八大排序算法有了更深入的理解。希望这些知识能帮助你在实际编程中更好地处理排序问题。

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

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

相关文章

音视频之视频压缩编码的基本原理

系列文章&#xff1a; 1、音视频之视频压缩技术及数字视频综述 2、音视频之视频压缩编码的基本原理 一、预测编码&#xff1a; 1、预测编码的基本概念&#xff1a; 预测法是最简单、实用的视频压缩编码方法&#xff0c;经过压缩编码后传输的并不是像素本身的取样值&#xff0…

IDEA 包分层显示设置

方法一&#xff08;用的IntelliJ IDEA 2024.1.4版本&#xff09;&#xff1a; 找到项目视图设置入口&#xff1a;在左侧Project&#xff08;项目&#xff09;面板的顶部&#xff0c;有个三个点...的按钮 &#xff0c;点击它。 进入树形外观配置&#xff1a;在弹出的菜单中&…

深度学习和神经网络 卷积神经网络CNN

1.什么是卷积神经网络 一种前馈神经网络&#xff1b;受生物学感受野的机制提出专门处理网格结构数据的深度学习模型 核心特点&#xff1a;通过卷积操作自动提取空间局部特征&#xff08;如纹理、边缘&#xff09;&#xff0c;显著降低参数量 2.CNN的三个结构特征 局部连接&a…

PlayWright | 初识微软出品的 WEB 应用自动化测试框架

Playwright是微软大厂背书的跨平台 WEB 应用自动化测试框架&#xff0c;支持多开发语言&#xff08;TypeScript、JavaScript、.Net、Python、Java&#xff09;及多浏览器&#xff08;Chromium、WebKit、Firefox&#xff09;&#xff0c;同时支持移动端测试。 安装 playwright …

Mac电脑_钥匙串操作选项变灰的情况下如何删除?

Mac电脑_钥匙串操作选项变灰的情况下如何删除&#xff1f; 这时候 可以使用相关的终端命令进行操作。 下面附加文章《Mac电脑_钥匙串操作的终端命令》。 《Mac电脑_钥匙串操作的终端命令》 &#xff08;来源&#xff1a;百度~百度AI 发布时间&#xff1a;2025-06&#xff09;…

2025前端微服务 - 无界 的实战应用

遇饮酒时须饮酒&#xff0c;得高歌处且高歌 文章目录 什么是前端微服务主流框架概述无界 - 腾讯乾坤 - 阿里Micro-app Vue3项目引用⑴. 项目依赖安装⑵. main.ts 文件配置⑶. 路由配置⑷. 页面设置 隐藏子应用菜单及顶部信息栏子应用样式冲突问题虚拟路由⑴. 路由⑵. 页面 跨域…

【设计模式-4.8】行为型——中介者模式

说明&#xff1a;本文介绍行为型设计模式之一的中介者模式 定义 中介者模式&#xff08;Mediator Pattern&#xff09;又叫作调节者模式或调停者模式。用一个中介对象封装一系列对象交互&#xff0c;中介者使各对象不需要显式地互相作用&#xff0c;从而使其耦合松散&#xf…

行列式的性质

1 行列式使用如下性质定义 1&#xff09;单位矩阵行列式值为 1&#xff0c; &#xff0c;对于任意单位矩阵均成立&#xff1b; 2&#xff09;当矩阵交换一行后&#xff0c;行列式值改变符号&#xff0c;如置换矩阵的行列式值为 &#xff08;根据行交换次数决定&#xff09;&…

联软NSPM自动化策略管理 助力上交所加速国产化替代提升运维效率

在金融行业核心基础设施国产化浪潮与网络安全强监管的双重背景下&#xff0c;上海证券交易所&#xff08;以下简称“上交所”&#xff09;积极拥抱变革&#xff0c;携手长期合作伙伴联软科技&#xff0c;成功部署了联软安全策略管理系统&#xff08;NSPM&#xff09;。该项目不…

Flask + ECharts+MYSQL物联网数字化大屏

基于Flask+ECharts的物联网数字化大屏系统,包含中国地图实时数据更新功能。这个系统模拟了物联网设备在全国范围内的分布和运行状况,并实时更新数据。 一、系统架构设计 技术栈 后端:Flask(轻量级路由+API支持) 前端:ECharts(地图+动态图表)、WebSocket(实时更新)…

数据库系统概论(十六)数据库安全性(安全标准,控制,视图机制,审计与数据加密)

数据库系统概论&#xff08;十六&#xff09;数据库安全性 前言一、数据库安全性1. 什么是数据库安全性&#xff1f;2. 为何会存在安全问题&#xff1f; 二、安全标准的发展1. 早期的“开拓者”&#xff1a;TCSEC标准2. 走向国际统一&#xff1a;CC标准3. TCSEC和CC标准有什么不…

vue3从入门到精通(基础+进阶+案例)

Vue是什么&#xff1f; 渐进式JavaScript框架&#xff0c;易学易用&#xff0c;性能出色&#xff0c;适用场景丰富的Web前端框架 为什么要学习Vue Vue是目前前端最火的框架之一 Vue是目前企业技术栈中要求的知识点 Vue可以提升开发体验 。。。 Vue简介 Vue(发音为/vju/,…

【Linux 学习计划】-- 系统中进程是如何调度的(内核进程调度队列)

目录 回顾进程优先级与进程调度的引入 内核runqueue图例 关于queue[140]前100个位置 | 实时进程与分时进程 遍历需要调度的进程与bitmap的引入 active、expired指针 结语 回顾进程优先级与进程调度的引入 在我们之前的学习中&#xff0c;我们是有学习过进程优先级这个概…

C#、VB.net——如何设置窗体应用程序的外边框不可拉伸

以Visual studio 2015为例&#xff0c;具体操作如下&#xff1a; 1、将窗体的“FormBorderStyle”属性值修改为“FixedSingle”&#xff1a; 2、点击“格式”——“锁定控件”&#xff1a; 这样生成的程序边框即可固定住&#xff0c;无法拉伸。

基于SpringBoot的房屋租赁系统的设计与实现(thymeleaf+MySQL)

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

Spring Boot统一功能处理深度解析

第一章&#xff1a;为什么需要统一功能处理&#xff1f; 想象你正在开发一个电商系统&#xff0c;包含用户管理、商品管理、订单管理等模块。每个模块都需要&#xff1a; 用户身份验证操作日志记录异常统一处理数据格式标准化 如果每个模块都单独实现这些功能&#xff1a; …

世事无常,比较复杂,人可以简单一点

2025年6月5日日&#xff0c;17~28℃&#xff0c;一般 待办&#xff1a; 宣讲会 职称材料的最后检查 职称材料有错误&#xff0c;需要修改 期末考试试题启用 教学技能大赛PPT 遇见&#xff1a;部门宣传泰国博士项目、硕士项目、本科项目。 感受或反思&#xff1a;东南亚博士…

数据结构哈希表总结

349. 两个数组的交集 力扣题目链接(opens new window) 题意&#xff1a;给定两个数组&#xff0c;编写一个函数来计算它们的交集。 说明&#xff1a; 输出结果中的每个元素一定是唯一的。 我们可以不考虑输出结果的顺序。 public int[] intersection(int[] nums1, int[] num…

IDEA 开发PHP配置调试插件XDebug

1、安装PHP环境 为了方便&#xff0c;使用的PhpStudy。 安装路径&#xff1a;D:\resources\phpstudy_pro\Extensions\php\php7.3.4nts 2、下载Xdebug Xdebug: Downloads 选择对应的版本下载&#xff0c;本次使用的是7.3。 3、配置Xdebug 在php.ini中添加Xdebug配置。 D…

奇异值分解(SVD):线性代数在AI大模型中的核心工具

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;10年以上C/C, C#, Java等多种编程语言开发经验&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开…