一篇博客读懂排序

目录

一、常见的排序

二、冒泡排序 

2.1基本思想:

2.2代码:

三、插入排序

3.1基本思想:

3.2思路讲解:

3.3代码:

3.4时间复杂度:

四、希尔排序

4.1基本思路:

4.2思路讲解:

4.3代码:

4.4时间复杂度:

五、选择排序

5.1基本思路:

5.2思路讲解:

5.3代码:

5.4时间复杂度:

六、堆排序

6.1基本思路:

6.2思路讲解:

6.3代码:

6.4时间复杂度:

七、快速排序(hoare版本)

7.1基本思路:

7.2思路讲解:

7.3代码:

7.4时间复杂度:

八、快速排序(挖坑法和前后指针版本)

8.1挖坑法

8.1.1挖坑法讲解

8.1.2代码:

 8.2前后指针版本

8.1.1前后指针讲解

8.1.2代码: 

九、归并排序 

9.1基本思路:

9.2思路讲解:

9.3代码:

9.4时间复杂度:


一、常见的排序

二、冒泡排序 

2.1基本思想:

冒泡排序,何为冒泡,即把数据最大的每次都排到最后一个位置,像冒泡一样。

2.2代码:

void BubbleSort(int* a, int n)
{int i = 0;for (; i < n; i++){int j = 1;for (; j < n - i; j++){if (a[i - 1] > a[i])Swap(&a[i - 1], &a[i]);}}
}

三、插入排序

3.1基本思想:

  把待排序的数据逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

3.2思路讲解:

以上图数据、升序为例。
为了便于理解,我们分为两大阵营,已排和未排。我们在没排序之前,这些数据全都是未排阵营,接着我们开始比较,由于已排阵营没有数据,所以9就是第一个。然后拿未排阵营的33和9比较,33放在9之后。
接着就是10和33比较,10放在33前;10和9排序,10放在9后,此趟结束。

46和33比较,46放在33后,此趟结束。

23和46比较,放在46前;23和33比较,放在33前;23和10比较,放在10后,此趟结束。

我相信说了这几趟,大家就能基本明白,插入排序就是把没排序的和已排序的从后向前依次比较然后找到不满足条件的位置把自己放进去。

3.3代码:

首先,我们先完成单趟排序。
然后,我们介绍一下需要新引入的变量:

其中,因为我们需要把数组看成两部分,所以我们引入end作为两者的分界点;当挪动数据时,我们用前一个数据覆盖后一个数据,所以需要tmp记录被覆盖元素的值,即当前插入的元素。
当我们的插入元素不满足条件时,即它在前后两个元素之间,此时end移动到了前一个元素,所以后来我们需要把end+1赋值为插入的数值:

void InsertSort(int arr[], int n)
{int end = 0;int tmp = arr[end + 1];while (end >= 0){if (arr[end] > tmp){arr[end + 1] = arr[end]; end--;}else{break;}}arr[end + 1] = tmp;
}

我们把end作为了已排数列的最后一个元素的位置,由此可知我们的整套排序,end从0到n-1:

void InsertSort(int arr[], int n)
{int i = 0;for (; i < n - 1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (arr[end] > tmp){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = tmp;}
}

3.4时间复杂度:

最坏情况:O(N^2) —— 逆序
最坏情况:O(N) —— 顺序有序

四、希尔排序

4.1基本思路:

1.预排序:取间隔的数,为一组,对每一组进行插入排序
2.插入排序:对整体进行插入排序

4.2思路讲解:

4.3代码:

我们把插入排序的单趟间隔从1改为gap,完成希尔排序的第一步:

void ShellSort(int arr[], int n)
{int end = 0;int gap = 3;int tmp = arr[end + 1];while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;
}

把以上操作重复gap次即为所求代码: 

void ShellSort(int arr[], int n)
{int gap = 3;for (int j = 0; j < gap; j++){for (int i = j; i < n - gap; i += gap){int end = i;int tmp = arr[end + 1];while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}
}

 我们可以对上述代码进行优化,让它变为gap组同时排序:

void ShellSort(int arr[], int n)
{int gap = 3;for (int i = 0; i < n - gap; i++){int end = i;int tmp = arr[end + gap];while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}
}

我们这还只是完成了第一步,接下来我们要进行插入排序:

void ShellSort(int arr[], int n)
{int gap = 3;for (int i = 0; i < n - gap; i++){int end = i;int tmp = arr[end + gap];while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}int i = 0;for (; i < n - 1; i++){int end = i;int tmp = arr[end + 1];while (end >= 0){if (arr[end] > tmp){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = tmp;}
}

学到这里,其实这些分布的并非是真正的希尔排序,gap到底取多少的值?真的要分成两步?其实希尔排序的gap是随时变换的,最后gap的值为1时,才正好进行最后的插入排序,但gap的取值官方也未说明何时最好,我们只需要保证最后一次排序gap的值刚好为1即可:

void ShellSort(int arr[], int n)
{int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++){int end = i;int tmp = arr[end + gap];while (end >= 0){if (arr[end] > tmp){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}
}

4.4时间复杂度:

希尔排序的复杂度分析是个复杂的问题,它的计算还涉及到数学领域中尚未解决的难题,但有人指出当n在特定范围时,其复杂度接近O(n^1.3),当n->无穷大时,可以减少到O(n*(logn)^2)

五、选择排序

5.1基本思路:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

5.2思路讲解:

选择排序其实没有什么难点,就是挨个比较然后将最小的换到首位,但其实我们可以同时找最大和最小值,然后让其找到对应的位置后再继续找次大的和次小的。

但是有一点需要注意,那就是当最后我们交换时,如果begin位置和maxi位置重合,那么先交换mini和begin的话,就无法找到正确的maxi值,反之同理,所以我们要多加一层判断来确保。

5.3代码:

首先我们先看一下我们需要新添加的变量:
1.标记交换最小值位置的begin和交换最大值位置的end,当一次选择完成后,他们标记的位置就会向后和向前挪动1个单位。
2.标记最大值最小值位置的maxi和mini,我们要记录当前最大值与最小值的元素下标,当每趟结束后与先前记录的begin和end交换。

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

5.4时间复杂度:

最好的情况:O(n^2)
最坏的情况:O(n^2)

六、堆排序

6.1基本思路:

利用大堆或小堆,每次找到一个最大值或最小值作为根结点并输出,再次进行堆排序,找到次大或次小值,重复上述操作。

6.2思路讲解:


6.3代码:

void AdjustUp(int* a, int child)
{while (child > 0){int parent = (child - 1) / 2;if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;}else{return;}}
}
void AdjustDown(int* a, int size, int parent)
{//假设左孩子小int child = parent * 2 + 1;while (child < size){if (child + 1 < size && a[child] < a[child + 1]){child = child + 1;}if (a[parent] < a[child]){Swap(&a[parent], &a[child]);parent = child;child = child * 2 + 1;}else{return;}}
}
//升序
void HeapSort(int* a, int n)
{//建大堆for (int i = 1; i < n; i++){AdjustUp(a, i);}int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}

6.4时间复杂度:

O(N*logN)

七、快速排序(hoare版本)

7.1基本思路:

任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

7.2思路讲解:

我们需要用两个指针,一个从前走,一个从后走,依次筛选满足条件的值,若不满足条件则交换。
还有一个小细节:我们要让右边指针先走,这样左右指针相遇时永远指向比key小的值,这样让left和key交换才能完成排序。

下面我们先来完成单趟,选用一个key值作为基准值,小的排在左边,大的排在右边,这样一次可以把整个数组分为两部分,然后我们还要进行基准值位置的调整,此时的left位置就是key应在的位置。

然后我们再分别对左右两边进行同样的步骤,即递归,当只剩下一个元素时就可以结束我们的递归操作,此时我们的数组也全部排序完成。

7.3代码:

首先我们来看新加变量,left和right指针,负责分别从两侧检索,keyi为此次所选的基准值的下标,但是需要注意的是,我们的left指针和keyi指针不能指向一个位置,我们的keyi指向的元素是不能和right指向的值进行交换的!

void QuickSort(int* a, int n)
{int right = n - 1;int keyi = 0;int left = keyi + 1;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[keyi], &a[left]);
}

完成了单趟排序以后,我们要进行递归调用,所以此时我们函数体引用不便再使用n作为区间标记,而是需要改用begin和end作为区间的分割点,使我们调用函数更加准确,然后递归调用的区间就是[begin,keyi-1] key [key+1,end]

void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int keyi = begin;int right = end;int left = begin;//left和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[keyi], &a[left]);keyi = left;QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

7.4时间复杂度:

O(N*logN)

八、快速排序(挖坑法和前后指针版本)

8.1挖坑法

8.1.1挖坑法讲解

这个方法我原称其为左右横跳方法。这个坑一直在左右横跳,当左右指针相遇时,把Key值填入即可。而且我们可以发现左右指针的规律,没有指向坑的指针走。


8.1.2代码:

我们先把QuickSort的代码改一改,让它能更方便的测试我们的三个方法:
 

//hoare版本
int PartSort1(int* a, int begin, int end)
{int keyi = begin;int right = end;int left = 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[keyi], &a[left]);return left;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int keyi = PartSort1(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

我们再来看挖坑法:我们使用数组额外的新变量key来直接控制基准值,我认为这比用数组的某个下标控制更加稳定,然后每次都把坑用key的值填上也是为了更加稳定的控制:

//挖坑法
int PartSort2(int* a, int begin, int end)
{int hole = begin;int key = a[begin];while (begin < end){if (begin != hole){while (begin < end && a[begin] <= key){begin++;}a[end] = a[begin];a[begin] = key;hole = begin;}if (end != hole){while (begin < end && a[end] >= key){end--;}a[begin] = a[end];a[end] = key;hole = end;}}return hole;
}

 8.2前后指针版本

8.1.1前后指针讲解

其实画图把整个流程完整的走一遍我们就可以知道我们的目的是为了让prev和cur指针中间的值都是比key大的值,然后prev++负责记录下一个大的值,cur负责记录比key小的值,每次找到就交换,最后再把prev的值变为key即可。
如果看不懂,建议自己画一遍实操一下即可。

8.1.2代码: 

//前后指针版本
int PartSort3(int* a, int begin, int end)
{int key = a[begin];int prev = begin;int cur = begin + 1;while (cur <= end){if (a[cur] < key){prev++;Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[begin]);return prev;
}

 我们也可以在第一个大while循环中把if换成while:

int PartSort3(int* a, int begin, int end)
{int key = a[begin];int prev = begin;int cur = begin + 1;while (cur <= end){while (cur <= end && a[cur] > key){cur++;}if (cur <= end){prev++;Swap(&a[prev], &a[cur]);cur++;}}Swap(&a[prev], &a[begin]);return prev;
}

九、归并排序 

9.1基本思路:

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;
即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

9.2思路讲解:

我们的归并排序是运用分治法的一个排序,下面是分的过程:

 我们看到了控制分停止的条件就是begin>=end,下面我们看合的过程:

每次合都会把合到一起的几个元素排序,因为我们是数组, 所以我们还需要一个新数组负责挪动数据。 

9.3代码:

根据思路讲解,我们先来看分的过程:

if (begin >= end)return;//分int mid = (begin + end) / 2;MergeSort(a, begin, mid, tmp);MergeSort(a, mid + 1, end, tmp);

然后我们只需要完成一次合的过程:

    //合int begin1 = begin;int end1 = mid;int begin2 = mid + 1;int end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2 ){if (a[begin1] > a[begin2]){tmp[i++] = a[begin2++];}else{tmp[i++] = a[begin1++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}

最后把tmp复制到我们正式的数组上即可:

memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
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;int end1 = mid;int begin2 = mid + 1;int end2 = end;int i = begin;while (begin1 <= end1 && begin2 <= end2 ){if (a[begin1] > a[begin2]){tmp[i++] = a[begin2++];}else{tmp[i++] = a[begin1++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

9.4时间复杂度:

O(N*logN)
每层的时间复杂度是O(N),因为二分,一共有logN层。

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

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

相关文章

微信小程序如何获取当前日期时间

Hello大家好&#xff01;我是咕噜铁蛋&#xff0c;获取当前日期时间是小程序中经常会用到的一个功能。因此&#xff0c;在本文中&#xff0c;我通过科技手段给大家收集整理了下&#xff0c;今天我将向大家介绍如何在微信小程序中获取当前日期时间的方法&#xff0c;并分享一些实…

Overleaf(LaTeX文档在线编写平台)使用学习记录

一、LaTeX简概[1] LaTeX&#xff0c;是一种基于TEX的排版系统&#xff0c;是一种可以处理排版和渲染的标记语言。由美国计算机科学家莱斯利兰伯特在20世纪80年代初期开发&#xff0c;利用这种格式系统的处理&#xff0c;即使用户没有排版和程序设计的知识也可以充分发挥由TEX所…

离零售业智能体时代的真正开启还有多远?

AIGC&#xff08;生成式人工智能&#xff09;当道的2023年&#xff0c;将LLM&#xff08;大语言模型&#xff09;的各类生成式能力发挥到淋漓尽致、精彩纷呈的程度。各行各业一边在观望大语言模型不断扩宽的商业运用可能&#xff0c;一边在继续探寻能够不断拓宽企业往纵深发展的…

Mybatis----缓存

MyBatis是一个流行的Java持久化框架&#xff0c;它提供了一个灵活的缓存机制来提高查询性能。 MyBatis的缓存机制主要分为一级缓存和二级缓存。 一级缓存是指在同一个SqlSession中&#xff0c;查询结果会被缓存起来&#xff0c;当再次执行同样的查询时&#xff0c;直接从缓存中…

基于SSM的企业文档管理系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是何时&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML 我欲乘风归去 又恐琼楼玉宇 高处不胜寒 -苏轼 一、项目简介 现代经济快节奏发展以及不断完善升级的信息化技术&…

深度了解TCP/IP模型

网络通信是现代社会不可或缺的一部分&#xff0c;而TCP/IP模型作为网络通信的基石&#xff0c;扮演着至关重要的角色。本文将深入探讨TCP/IP模型的概念、结构及其在网络通信中的作用&#xff0c;为读者提供全面的了解。 一.TCP/IP模型简介 TCP/IP模型是一个网络通信协议体系&a…

机器学习 | 掌握Matplotlib的可视化图表操作

Matplotlib是python的一个数据可视化库&#xff0c;用于创建静态、动态和交互式图表。它可以制作多种类型的图表&#xff0c;如折线图、散点图、柱状图、饼图、直方图、3D 图形等。以渐进、交互式方式实现数据可视化。当然博主也不能面面俱到的讲解到所有内容&#xff0c;详情请…

【极数系列】Flink 初相识(01)

# 【极数系列】Flink 初相识&#xff08;01&#xff09; 引言 Flink官网&#xff1a;https://flink.apache.org/ Flink版本&#xff1a;https://flink.apache.org/blog/ Flink文档&#xff1a;https://ci.apache.org/projects/flink/flink-docs-release-1.12/ Flink代码库…

轻松互换文件夹名,高效批量改名!高手工具助您一臂之力!

在日常工作中&#xff0c;我们经常需要处理大量的文件夹&#xff0c;有时候需要将文件夹名称互换或进行批量改名。这时&#xff0c;一款高效、实用的高手工具就能派上用场。它不仅能帮助您轻松实现文件夹名互换&#xff0c;还能快速批量改名&#xff0c;让您的工作更加高效、轻…

Unity - 角色控制

Test_05 角色控制 创建一个3D对象作为角色&#xff0c;添加 “CharacterController” 组件来控制角色移动&#xff0c;绑定脚本"PlayerControl"。 PlayerControl public class PlayerControl : MonoBehaviour {private CharacterController player;void Start(){p…

RabbitMQ系列之入门级

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《RabbitMQ系列之入门级》。&#x1f3af;&#x…

防火墙基础1

防火墙简绍 什么是防火墙? 状态防火墙工作原理? 防火墙如何处理双通道协议? 防火墙如何处理nat? 路由交换终归结底是连通性设备。 网络在远古时期没有防火墙大家都是联通的&#xff0c;any to any。 防御对象&#xff1a; 授权用户 非授权用户 防火墙是一种隔离…

存储开发入门到进阶,这几本书一定要看!!

有些朋友是已经深耕存储多年&#xff0c;有的朋友是刚刚入门、或者说有兴趣但是迟迟不得入门。以下从笔者的经验出发&#xff0c;向大家推荐几本书&#xff0c;可以比较系统的补充一些编程的内功和存储的基础知识&#xff0c;向你展示一条存储通关之路。 语言 语言是第一个要…

55. 跳跃游戏 - 力扣(LeetCode)

题目描述 给定一个非负整数数组&#xff0c;你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个位置。 题目示例 输入&#xff1a;nums [2,3,1,1,4] 输出&#xff1a;true 解释&#xff1a;可以先跳 1 步&#x…

LFU算法

LFU算法 Least Frequently Used&#xff08;最不频繁使用&#xff09; Leetcode有原题&#xff0c;之前手写过LRU&#xff0c;数据结构还是习惯于用java实现&#xff0c;实现是copy的评论题解。 题解注释写的很清楚 大致就是说LFUCache类维护一个存放node的map&#xff0c;同…

基于springboot+vue的墙绘产品展示交易平台系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究背景…

微信小程序商城注册是个人还是公司的?

随着移动互联网的飞速发展&#xff0c;微信小程序已经成为了商家们开展电子商务的重要平台之一。微信小程序商城以其便捷的操作、庞大的用户基础和较低的成本投入&#xff0c;吸引了众多商家的关注。然而&#xff0c;对于想要进入这一领域的创业者来说&#xff0c;一个基础性的…

C# CefSharp 输入内容,点击按钮,并且滑动。

前言 帮别人敲了个Demo,抱试一试心态&#xff0c;居然成功了&#xff0c;可以用。给小伙伴们看看效果。 遇到问题 1&#xff0c;input输入value失败&#xff0c;里面要套了个事件&#xff0c;再变换输入value。后来用浏览器开发工具&#xff0c;研究js代码&#xff0c;太难了&a…

Authorization Failed You can close this page and return to the IDE

一.问题描述 注册JetBrains成功&#xff0c;并且通过了学生认证&#xff0c;但在activate pycharm时&#xff0c;却显示Authorization Failed You can close this page and return to the IDE如上图 二.原因&#xff1a; 可能是因为之前使用了破解版pycharm 三.解决方法&am…

GNSS技术与无人机协同:开启未来交通新篇章

随着科技的不断发展&#xff0c;全球导航卫星系统&#xff08;GNSS&#xff09;技术与无人机技术的协同应用成为未来交通系统的引人瞩目的新方向。创新微公司在这一领域的技术创新为实现GNSS技术与无人机的紧密协同提供了新的可能性。本文将深入探讨GNSS技术与无人机协同的前景…