常用排序算法总结(直接插入排序、选择排序、冒泡排序、堆排序、快速排序、希尔排序、归并排序)

目录

一. 直接插入排序

二:选择排序

三:冒泡排序

四.堆排序

五:希尔排序

六:快速排序(递归与非递归)

七.归并排序(递归与非递归)


一. 直接插入排序

🌟排序思路

        直接插入排序的基本原理是将一条记录插入到已排好的有序表中,从而得到一个新的、记录数量增1的有序表,其思路就和我们摸扑克牌一样,每摸到一张牌按照大小把他插入到对应位置,这样等摸完全部的牌时,我们手里的牌就是有序的

动态图解:

💬特点

🚩时间复杂度:

        O(N^2)(若待排序表为有序的则时间复杂度为O(N))

🚩空间复杂度:

        空间复杂度为O(1)

🚩稳定性: 

        稳定

⚡代码演示: 

void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;//首先保存一下待插入的元素int tmp = a[end + 1];//比较的最坏情况时end=0while (end >= 0){//若待排序元素比a[end]小,则让a[end]向后移动腾位置if (tmp < a[end]){a[end + 1] = a[end];end--;}else{//由于本来数组就为有序数组,当不满足上述条件时,说明找到了待插入的位置break;}}//插入数据a[end + 1] = tmp;}
}

二:选择排序

🌟排序思路

 每次选出数组的最小值和最大值,分别置于数组头尾处,缩小排序范围重复操作

动态图解:

💬特点

🚩时间复杂度:

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

🚩空间复杂度:

        空间复杂度为O(1)

🚩稳定性:

        不稳定

⚡代码演示: 

void SelectSort(int* a, int n)
{int begin = 0, end = n - 1;while (begin < end){int mini = begin, maxi = begin;for (int i = begin + 1; i <= end; ++i){if (a[i] < a[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}Swap(&a[begin], &a[mini]);if (maxi == begin){maxi = mini;}Swap(&a[end], &a[maxi]);++begin;--end;}
}

三:冒泡排序

🌟排序思路

比较相邻元素的大小,将大的元素往后交换,每一轮比较能确定一个最大值的位置

动态图解:

💬特点

🚩时间复杂度:

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

🚩空间复杂度:

        空间复杂度为O(1)

🚩稳定性:

        稳定

 ⚡代码演示: 

/* 冒泡排序 */
void BubbleSort(int arr[], int length) 
{for (int i = 0; i < length; i++){for (int j = 0; j < length -  i - 1; j++){if (arr[j] > arr[j + 1]){int temp;temp = arr[j + 1];arr[j + 1] = arr[j];arr[j] = temp;}}}
}

四.堆排序

🌟排序思路

1.首先将待排序数组建为大堆,此时堆顶元素就为数组最大值了

2.交换堆顶和堆尾元素,此时最大元素就到了堆尾,目前数组最大元素就排好了,现在就假设堆里没有当前这个最大元素了,堆头下面的左右子树仍然是大堆,只需要再将堆顶元素向下调整到合适位置,剩下的n-1个元素还是大堆

3.堆头堆尾交换,向下调整,如此反复就可排序

ps.排序以升序为例,升序建大堆,降序建小堆      

💬特点

🚩时间复杂度:

       O(N*lgN)

🚩空间复杂度:

        O(1)

🚩稳定性:

        不稳定

⚡代码演示: 

#include<stdio.h>
typedef int HeapDataType;void swap(HeapDataType* a, HeapDataType* b)
{int tmp = *a;*a = *b;*b = tmp;
}
//大堆
void AdjustUp(HeapDataType* a, int child)
{int parent = (child - 1) / 2;while (child>0){if (a[parent] < a[child]){swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
//大堆
void AdjustDown(HeapDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child<size){//找较大的孩子if (a[child + 1] > a[child] && child+1<size){child = child + 1;}if (a[parent] < a[child]){swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapSort(HeapDataType* a, int size)
{//建堆for (int i = 1; i < size; i++){AdjustUp(a, i);}//排序:升序int end = size - 1;while (end>0){swap(&a[0], &a[end]);AdjustDown(a, end, 0);  //end指最后一个元素,同时end的值为前面元素的个数end--;}
}

 五:希尔排序

🌟排序思路

  1. 预排序:先将数组分组,将每个组排序,目的是让整个数组相对有序
  2. 插入排序:待数组相对有序后,进行直接插入排序,这样效率比较高

图解:

假设待排序数组为[9 8 7 6 5 4 3 2 1]

1.我们将他们每隔三个坐标分为一组,假设gap=3

2.对三个数组分别进行直接插入排序,使数组整体相对有序

3.对数组整体进行插入排序

 💬特点

🚩时间复杂度:

       O(N^1.3)

🚩空间复杂度:

        O(1)

🚩稳定性:

        不稳定

 ⚡代码演示: 

void ShellSort(int* a, int n)
{//首先将gap定为nint gap = n;/*while (gap > 1){gap = gap / 3 + 1;for (int j = 0; j < gap; j++){for (int i = j; i < n - gap; i += gap){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;}}}*/while (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;}}
}

六:快速排序(递归与非递归)

1.⚡hoare

🌟排序思路

        将数组第一个元素定位关键值,定义begin和end指针,先让end从后往前找到比关键值小的数,begin从前往后找比关键值大的数,然后交换两数,直到 begin==end,再让关键值和begin所指的元素交换,最后返回关键值所在位置,便于后续进行递归或非递归操作

动态图解:

⚡代码演示: 

void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//hoare
int PartSort1(int* a, int begin, int end)
{int left = begin, right = end;int keyi = begin;while (left < right){//右边找小while (left < right && a[right] >= a[keyi]){right--;}//左边找大while (left < right && a[left] < a[keyi]){left++;}swap(&a[left], &a[right]);}swap(&a[left], &a[keyi]);return left;
}

 2.⚡挖坑法

🌟排序思路

         首先将关键值定为数组第一个元素,并将坑位定为begin,先让end从后往前找到比关键值小的数,将这个数放到坑位,并更新坑位,再让begin从前往后找比关键值大的数,将这个数放到坑位,并更新坑位,直到 begin==end,再让关键值和坑位的元素交换,最后返回关键值所在位置

动态图解:

⚡代码演示: 

//挖坑法
int PartSort2(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int key = a[begin];int hole = begin;while (begin < end){//右边找小,填入坑内,更新坑位while (begin<end && a[end]>=key){--end;}a[hole] = a[end];hole = end;//左边找大,填入坑内,更新坑位while (begin<end && a[begin]<=key){++begin;}a[hole] = a[begin];hole = begin;}a[hole] = key;return hole;
}

 3.⚡双指针法

🌟排序思路

        将数组第一个元素定为关键值,定义两个指针prev和cur,先让prev指向数组的第一个元素,cur指向prev的下一个元素,cur的作用是找比关键值小的元素,若cur所指元素不小于关键值则cur++,直到cur所值元素小于关键值,此时,prev和cur之间的元素都是大于关键值的元素,若prev+1不是cur的话就可以让prev++所指元素与cur所指元素交换了,直到cur指向数组的最后一个元素

动态图解:


 

⚡代码演示:

//双指针法
int PartSort3(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int key = begin;int prev = begin;int cur = prev + 1;while (cur <= end){if (a[cur] < a[key] && ++prev != cur){swap(&a[prev], &a[cur]);}cur++;}swap(&a[prev], &a[key]);return prev;
}

💬特点

🚩时间复杂度:

最好情况:O(n*lgn)

最坏情况:O(n^2)

🚩空间复杂度

O(lgn)

🚩稳定性

不稳定

 🚩快速排序递归实现

⛲小优化:

        上述三个方法都是快速排序的单趟排序,但是上述排序还有一个小缺陷,因为三个方法都是固定第一个元素为关键值的,如果数组为有序的,那么从后往前找小就要遍历整个数组,效率会很小,所以通常会再写一个找中间值的函数:在数组开头结尾和中间三个数中找出一个大小在中间的数,并让这个数和数组第一个数交换,这样就会减少上述情况的发生

int GetMid(int* a, int begin, int end)
{int mid = (begin + end) / 2;if (a[begin] > a[mid]){if (a[mid] > a[end])return mid;else if (a[end] > a[begin])return end;elsereturn begin;}else{if (a[begin] > a[end])return begin;else if (a[end] > a[mid])return mid;elsereturn end;}
}void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//hoare
int PartSort1(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int left = begin, right = end;int keyi = begin;while (left < right){//右边找小while (left < right && a[right] >= a[keyi]){right--;}//左边找大while (left < right && a[left] < a[keyi]){left++;}swap(&a[left], &a[right]);}swap(&a[left], &a[keyi]);return left;
}//挖坑法
int PartSort2(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int key = a[begin];int hole = begin;while (begin < end){//右边找小,填入坑内,更新坑位while (begin<end && a[end]>=key){--end;}a[hole] = a[end];hole = end;//左边找大,填入坑内,更新坑位while (begin<end && a[begin]<=key){++begin;}a[hole] = a[begin];hole = begin;}a[hole] = key;return hole;
}
//双指针法
int PartSort3(int* a, int begin, int end)
{int mid = GetMid(a, begin, end);swap(&a[begin], &a[mid]);int key = begin;int prev = begin;int cur = prev + 1;while (cur <= end){if (a[cur] < a[key] && ++prev != cur){swap(&a[prev], &a[cur]);}cur++;}swap(&a[prev], &a[key]);return prev;
}
void QuickSort(int* a, int begin,int end)
{if (begin >= end)return;//三种方法任选其一即可int keyi = PartSort3(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

 🚩快速排序非递归实现

🌟实现思路:

1.创建一个栈,将数组的右边界下标和左边界下标依次入栈

2.循环弹出数组的左右边界下标,并对该区间进行单趟排序,确定关键值的下标,分为左右两个区间

3.若左区间元素个数大于一个,将左区间右边界下标和左边界下标依次入栈,右区间同理

4.重复操作步骤2 3直到栈为空

⚡代码演示: 

void QuickSortNonR(int* a, int begin, int end)
{ST s;STInit(&s);STPush(&s,end);STPush(&s,begin);while (!STEmpty(&s)){int left = STTop(&s);STPop(&s);int right = STTop(&s);STPop(&s);int key = PartSort1(a, left, right);if (left < key - 1){STPush(&s, key - 1);STPush(&s, left);}if (right > key + 1){STPush(&s, right);STPush(&s, key+1);}}STDestroy(&s);
}

 七.归并排序(递归与非递归)

一:⛲递归实现

🌟算法思路

归并排序是用分治思想,分治模式在每一层递归上有三个步骤:

分解(Divide): 将n个元素分成个含n/2个元素的子序列。
解决(Conquer):用合并排序法对两个子序列递归的排序。
合并(Combine):合并两个已排序的子序列已得到排序结果。
        该算法需要先将数组分解,直到每个子序列为一个元素,再将子序列两两合并排序,思路可以参考二叉树的后序递归

 💬特点

平均时间复杂度:O(nlogn)
最佳时间复杂度:O(n)
最差时间复杂度:O(nlogn)   
空间复杂度:O(n)

动图展示:

⚡代码演示: 

void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin >= end)return;//分解int mid = (begin + end) / 2;_MergeSort(a, begin, mid, tmp);_MergeSort(a, mid+1, end, tmp);// 归并int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while(begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, 0, n - 1, tmp);free(tmp);
}

二:⛲非递归实现

🌟算法思路:

        归并排序的递归实现是先将数组分解,直到每个子序列只有一个元素,在将子序列两两归并,非递归的实现思路则没有了分解的过程,直接将数组元素一个与一个归并,在两个与两个归并,在四个与四个归并.......,直到最后两组归并,数组变为有序数组

⚡代码演示:

void MergeSortNonR(int* a, int begin, int end)
{int n = end - begin + 1;int* tmp = (int*)malloc(sizeof(int) * (n));if (tmp == NULL){perror("malloc");return;}int gap = 1;//当gap小于n时两个组才都有元素,才需要归并while (gap < n){int j = 0;for (size_t i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//边界管理if (end1 >= n||begin2>=n){break;}if (end2 >= n){end2 = n - 1;}while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}//归并完后拷贝给原数组memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}free(tmp);
}

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

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

相关文章

【白皮书下载】GPU计算在汽车中的应用

驾驶舱域控制器 (CDC) 是汽车 GPU 的传统应用领域。在这里&#xff0c;它可以驱动仪表板上的图形&#xff0c;与车辆保持高度响应和直观的用户界面&#xff0c;甚至为乘客提供游戏体验。随着车辆屏幕数量的增加和分辨率的提高&#xff0c;对汽车 GPU 在 CDC 中进行图形处理的需…

Spark On Hive配置测试及分布式SQL ThriftServer配置

文章目录 Spark On Hive的原理及配置配置步骤在代码中集成Spark On Hive Spark分布式SQL执行原理及配置配置步骤在代码中集成Spark JDBC ThriftServer 总结 Spark On Hive的原理及配置 Spark本身是一个执行引擎&#xff0c;而没有管理metadate的能力&#xff0c;当我们在执行S…

Jenkins环境配置篇-更换插件源

作为持续集成的利器 Jenkins 已经得到了广泛地应用&#xff0c;仅仅作为一个工具&#xff0c;Jenkins 已然有了 自己的生态圈&#xff0c;支持其的 plugin 更是超过 1300。在实际中如何使用以及如何更好地使用 jenkins&#xff0c;一直是大家在实践并讨论的。本系列文章将会从如…

Beego之Beego快速入门

1、beego快速入门 1.1 新建项目 新建一个项目&#xff1a; [rootzsx src]# bee new quickstart 2023/02/19 15:55:50.370 [D] init global config instance failed. If you do not use this, just ignore it. open conf/app.conf: no such file or directory 2023/02/19 1…

接口自动化框架搭建-写在前面

从今天开始&#xff0c;我将带领大家一起学习接口自动化框架的搭建&#xff0c;在学习之前&#xff0c;我们先了解搭建一个接口自动化框架需要具备哪些知识&#xff0c;应该做哪些准备工作 测试开发工程师的入门条件 近几年比较流行测试开发岗位&#xff0c;很多小伙伴都不知…

消息队列介绍

什么是 MQ MQ(message queue)&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是 message 而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消息。在互联网架构中&#xff0c;MQ 是一种非常常 见的上下游“逻辑解耦…

SpringBoot 项目中后端实现跨域的5种方式!!!

文章目录 SpringBoot 项目中后端实现跨域的5种方式&#xff01;&#xff01;&#xff01;一、为什么会出现跨域问题二、什么是跨域三、非同源限制四、Java后端 实现 CORS 跨域请求的方式1、返回新的 CorsFilter(全局跨域)2、重写 WebMvcConfigurer(全局跨域)3、使用注解 (局部跨…

实战纪实 | 某配送平台zabbix 未授权访问 + 弱口令

本文由掌控安全学院 - 17828147368 投稿 找到一个某src的子站&#xff0c;通过信息收集插件wappalyzer&#xff0c;发现ZABBIX-监控系统&#xff1a; 使用谷歌搜索历史漏洞&#xff1a;zabbix漏洞 通过目录扫描扫描到后台&#xff0c;谷歌搜索一下有没有默认弱口令 成功进去了…

LeetCode:206. 反转链表

力扣链接 算法思想&#xff1a;由于单链表是单向的&#xff0c;想要对当前元素进行操作&#xff0c;需找到前一个元素。本题利用双指针&#xff0c;初始pre指针指向NULL&#xff0c;cur指针指向head.再对局部翻转之前&#xff0c;先把下一个结点存到temp指针中。当进行完如下代…

构建中国人自己的私人GPT

创作不易&#xff0c;请大家多鼓励支持。 在现实生活中&#xff0c;很多人的资料是不愿意公布在互联网上的&#xff0c;但是我们又要使用人工智能的能力帮我们处理文件、做决策、执行命令那怎么办呢&#xff1f;于是我们构建自己或公司的私人GPT变得非常重要。 先看效果 他的…

YOLOv8全网首发:新一代高效可形变卷积DCNv4如何做二次创新?高效结合SPPF

💡💡💡本文独家改进:DCNv4更快收敛、更高速度、更高性能,与YOLOv8 SPPF高效结合 收录 YOLOv8原创自研 https://blog.csdn.net/m0_63774211/category_12511737.html?spm=1001.2014.3001.5482 💡💡💡全网独家首发创新(原创),适合paper !!! 💡💡💡…

视频水平翻转,开启创意之旅

想象一下&#xff0c;一段普通的视频在瞬间被水平翻转&#xff0c;画面中的一切仿佛被镜子所反射&#xff0c;给人一种惊艳而又奇妙的感受。在这个充满无限可能的数字时代&#xff0c;我们不应被传统的视觉效果所束缚&#xff0c;尝试运用水平翻转技术&#xff0c;为你的作品注…

pytest文档内置fixture的request详情

前言 request 是 pytest 的内置 fixture &#xff0c; "为请求对象提供对请求测试上下文的访问权&#xff0c;并且在fixture被间接参数化的情况下具有可选的“param”属性。"这是官方文档对request的描述&#xff0c;可参考的文档不多。 一、FixtureRequest Fixtur…

Spring Bean 生命周期的执行流程?

一、问题解析 Spring 生命周期全过程大致分为五个阶段&#xff1a;创建前准备阶段、创建实例阶段、依赖注入阶段、容器缓存阶段和销毁实例阶段。 这张是 Spring Bean 生命周期完整流程图&#xff0c;其中对每个阶段的具体操作做了详细介绍&#xff1a; &#xff08;把这张图按…

【Linux】Linux进程间通信(四)

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;Linux &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【Linux】…

Lua 快速入门 · 教程笔记

Lua语言快速入门 教程笔记 前言1. Lua 语言介绍2. Lua 语言基础之基本语法声明变量声明方法使用 if - else使用 for使用 while 3. Lua 语言基础之表4. Lua 语言基础之数组插入元素移除元素获取表的长度全局表 5. Lua 语言面向对象之复制表的方式面向对象实现继承和重写父类方法…

Prompt高级技巧:Few-Shots、COT、SC、TOT、Step-Back

CRISPE框架 如图所示。所谓CRISPE框架&#xff0c;指的是&#xff1a; CR&#xff1a;Capacity and Role&#xff08;能力与角色&#xff09;。你希望 ChatGPT 扮演怎样的角色。I&#xff1a;Insight&#xff08;洞察&#xff09;&#xff0c;背景信息和上下文。S:&#xff08…

递归、搜索与回溯算法(专题二:深搜)

往期文章&#xff08;希望小伙伴们在看这篇文章之前&#xff0c;看一下往期文章&#xff09; &#xff08;1&#xff09;递归、搜索与回溯算法&#xff08;专题零&#xff1a;解释回溯算法中涉及到的名词&#xff09;【回溯算法入门必看】-CSDN博客 &#xff08;2&#xff09…

实验七 RMAN恢复管理器

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

利用appium自动控制移动设备并提取数据

安装appium-python-client模块并启动已安装好的环境 安装appium-python-client模块 在window的虚拟环境下执行pip install appium-python-client 启动夜神模拟器&#xff0c;进入夜神模拟器所在的安装路径的bin目录下&#xff0c;进入cmd终端&#xff0c;使用adb命令建立adb…