网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
前言
算法是程序设计的灵魂,我们最先接触的算法就是排序算法了,尤其是冒泡排序估计大家闭着眼都能写出来,对于其它的排序算法你还了解哪些?本文就带大家回顾一下算法界的十大排序算法。本次排序算法主要讲解算法的简单实现、时间复杂度和稳定性(稳定性指的是相同数据排序前后的顺序不变)。
推荐演示的时候使用网站https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html,网站随机数组有点长,我没找到自定义数组的功能,所以一部分动图采用别的网站制作的,没有的采用这个网站制作的。
❤️冒泡排序
🤍演示说明
冒泡排序是最常见的排序算法了,它是通过从前往后依次对比排序序列中相邻的两个值来进行的,对比的时候如果两个值的大小与排序的目的相反,则将他们交换过来。
🔊 看一下动画演示效果:
🔊 下面看一下代码实现
public static int[] sort(int[] array) {
//正常是比较length遍的,但是最后还剩一个的那次不算了所以是length-1遍
for (int i = 0; i < array.length - 1; i++) {
//遍历数组,对数组中相邻的两个数据进行比较并交换
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
return array;
}
🤍优化
正常情况下很少有将代码执行完全了之后才排序完成的,大多数情况下在执行过程中就完成了排序,所以完成了排序之后继续执行代码完全是浪费计算机了,是不是可以确定排序执行完成之后就不再执行了呢?
🔊 怎么确定排序完成了呢?咱可以在循环的时候加一个标志位,如果本轮替换循环结束都没有发生排序,那就说明此时已经排序完成了直接跳出循环就可以了。看一下代码实现:
public static int[] sort(int[] array) {
//正常是比较length遍的,但是最后还剩一个的那次不算了所以是length-1遍
for (int i = 0; i < array.length - 1; i++) {
//遍历数组,对数组中相邻的两个数据进行比较并交换
boolean flag = true;
for (int j = 0; j < array.length - 1 - i; j++) {
/\*或运算,如果有true则为true\*/
if (array[j] > array[j + 1]) {
flag = false;
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
//如果标志位没有被修改说明本次执行没有需要替换的值
if(flag){
break;
}
}
return array;
}
当然冒泡排序的优化很多优化后的性能非常好的方案(像双向排序等),咱本文就简单的介绍一下,主要作为初级入门的只是讲解。
🤍分析
空间复杂度咱就不分析了,一般情况不会出现因为空间复杂度原因导致内存溢出异常的。
🔊 时间复杂度
最好的情况: 如果当前需要排序的数组中的内容已经是排好序的,这时候for循环只需要执行一遍就可以了,此时的时间复杂度是O(n)。
最坏的情况: 如果当前需要排序的数组是倒序的,那么整个执行代码需要完整的走下来才能排序完成,最终走了两层for循环,此时的时间复杂度为O(n2)。
🔊 稳定性
冒泡排序中两两交换的时候使用的是大于或小于才交换的,没有等于交换,所以排序前后相同元素顺序没有改变,是稳定的。
❤️选择排序
🤍演示说明
选择排序也是一种常见的排序算法,它的思路是从一串数组中找出最大或最小的数据替换到对应的位置,例如如果按照升序排列每次循环找到最小的数据从左往右替换,直到最后数组变成有序的了。
🔊直接看一下演示效果:
🔊 下面看一下代码实现
public static int[] sort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
//一个临时变量每次循环记录最小值的下标位置
int min = i;
for (int j = i + 1; j < array.length; j++) {
//如果有比这个值小的就替换下标位置
if(array[j] < array[min]) {
min = j;
}
}
//数据交换,为了代码可读性放在了一个大括号里分割了下
{
int temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
return array;
}
🤍分析
选择排序可能也存在中间过程执行的时候出现排序已经完成的情况,但是我们无法判断是否排序完成,除非再加上冒泡排序的比较结合,那还不如直接使用冒泡呢。
🔊 时间复杂度
所以选择排序的时间复杂度没有最好和最坏情况的区分,所有情况都需要执行结束它的时间复杂度为两层for循环O(n2)。
🔊 稳定性
选择排序在排序过程中涉及到交换,而且交换不固定,可能相同元素被另一个元素替换走了,所以是不稳定的。
❤️插入排序
🤍演示说明
插入排序是把整个待排序的数组看成一个有序的数组和一个无序的数组两个数组,一开始第一个默认有序的,后面的所有的默认无序,把无序中的第一个取出放在有序数组顺序合适的位置。以此类推,看一下分析演示图:
🔊 下面看一下代码实现
public static int[] sort(int[] array) {
for (int i = 1; i < array.length; i++) {
int value = array[i]; //这是无序数组的第一个值
int j; //这是这个值要插入有序数组的位置
//开始遍历有序数组,找这个值要插入的位置j
//j>=0防止数组越界
//因为是有序数组,所以temp第一次大于某个值时,那么temp的下标就是那里了
for (j = i - 1; j >= 0 && value < array[j]; j--) {
//从有序数组的后面往前找,因为要插入所以数组需要后移,所以是倒序循环有序部分
System.out.println("value="+value+";temp="+array[j]);
array[j+1] = array[j];
}
//将数据插入到对应位置
array[j+1] = value;
}
return array;
}
插入排序可能稍微难一点,有点绕所以多加了行打印语句来分析,简单的来分析一点,后面的可以参照动画图自己分析,第一次执行value为5,5直接大于3了,所以没有进入循环,当前下标就是它的下标;第二次进入value=2,temp为5,2<5,5往后移,继续2与3比较,2<3,所以3往后移,循环结束,2放在0的位置。。。
🤍分析
插入排序也分为最好的结果和最坏的结果两种情况
🔊 时间复杂度
最好的结果: 最好的结果就是数据原来就是所需要的顺序,所以排序过程中中间的for循环进不去,最终时间复杂度为O(n)。
最坏的结果: 最坏的结果就是数据跟所需要的顺序相反,排序过程中中间的for循环每次都进行完整走一遍,最终时间复杂度为O(n2)。
🔊 稳定性
插入排序从无序数组插入的有序数组的过程中用的单纯大于小于比较,没有等于,所以相同元素的顺序没有发生改变,是稳定的。
❤️希尔排序
🤍演示说明
希尔排序是把序列按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量的逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个序列恰好被分为一组,算法便终止。
简单的来说希尔排序是把数据分为间隔的几组,分别对几组进行排序,然后缩小间隔继续排序,直到间隔为1的时候排序完成。希尔排序是对插入排序最差情况的优化,假设插入排序应该在前面的数据在数组中排在后面,排序的时间复杂度O(n2)性能太低,所以我们对其优化。
演示效果图,上面使用的网站没有希尔排序的演示设置,使用了另一个网站,数据有点多没找到自定义设置,所以时间有点长。
根据动图的演示效果可以看出来希尔排序是先把数组进行再次分组排序,尽量把数组放在对应的位置错位不是太大,最后一次进行插入排序的时候不需要移动这么多次。
🔊 下面看一下代码实现
public static int[] sort(int[] array) {
//增量
int gap = array.length / 2;
//如果增量为0则退出
while (gap > 0) {
//这还是那个插入排序,只不过每次的步进使用的gap增量
for (int i = gap; i < array.length; i++) {
int value = array[i];
int j;
//同样的使用的插入排序,j修改为了gap递进的
for (j = i - gap; j >= 0 && value < array[j]; j = j - gap) {
array[j + gap] = array[j];
}
//插入
array[j + gap] = value;
}
//增量每次减少,一般设置为/2,虽然--也没问题,但是考虑综合效率/2更好
gap = gap / 2;
}
return array;
}
🤍分析
希尔排序主要是为插入排序做了些准备工作,将插入排序的时间复杂度往中间集结了一下。
🔊 时间复杂度
希尔排序的时间复杂度与增量有关,Hibbard提出了增量序列{1,3,7,…,2k-1}(质数增量),这种序列的时间复杂度(最坏情形)为O(n1.5),而我们使用的2倍增量的时间复杂度最坏的情况下是O(n2)。
🔊 稳定性
希尔排序中涉及到了分组,而且分组是间隔分组,可能拆分两个相同元素的顺序,所以是不稳定的。
❤️快速排序
🤍演示说明
快速排序是通过一趟排序将想要排序的数组分割成两个独立的部分,其中一部分的所有数据比另一部分的所有数据要小,然后再按照快速排序递归继续处理,到最后整个数据都变成了有序数组了。每次排序都使用的冒泡排序,所以快速排序是对冒泡排序的优化。看一下实现思路:
演示图采用的单边循环法,来解释下演示图是什么思路:
选取第一个数据18为基准数,比18大的往右移动,比18小的往左移动。最后会以18位界限将数组分成两部分,左边比18小,右边比18大;然后分别对左右两边两个逻辑数组继续重复这样的操作,以此类推,直到最后整个数据的顺序排列完成。
🔊 下面看一下代码实现
public static int[] sort(int[] array, int startIndex, int endIndex) {
//结束条件,开始在索引大于结束索引
if (startIndex >= endIndex) {
return array;
}
// 基准元素位置
int pivotIndex = partition(array, startIndex, endIndex);
//将数组以基准元素分为左右两部分,继续执行
sort(array, startIndex, pivotIndex - 1);
sort(array, pivotIndex + 1, endIndex);
return array;
}
/\*分治 单边循环法\*/
private static int partition(int[] array, int startIndex, int endIndex) {
//选取第一个元素为基准数据
int pivot = array[startIndex];
//基准数据的下标
int mark = startIndex;
for (int i = startIndex + 1; i <= endIndex; i++) {
//如果值比基准数据小往左边放,基准坐标往右移动
if (array[i] < pivot) {
mark++;
int p = array[mark];
array[mark] = array[i];
array[i] = p;
}
}
array[startIndex] = array[mark];
array[mark] = pivot;
return mark;
}
/\*分治 双边循环法\*/
private static int partition(int[] array, int startIndex, int endIndex) {
//选取第一个元素为基准数据
int pivot = array[startIndex];
//基准数据的下标
int left = startIndex;
int right = endIndex;
while (left != right) {
//控制right左移比较
while (left < right && array[right] > pivot) {
right--;
}
//控制left右移比较
while (left < right && array[left] <= pivot) {
left++;
}
//交换left和right 指针所指向的元素
if (left < right) {
int p = array[left];
array[left] = array[right];
array[right] = p;
}
}
array[startIndex] = array[left];
array[left] = pivot;
return left;
}
🤍分析
🔊 时间复杂度
快速排序有单边排序和双边排序,演示图是演示的单边排序,双边排序是从左右两头对基准值进行比较,比单边排序更快一点。
快速排序的时间复杂度是O(n*logn),但是如果选取的基准值是整个数组中的极值时,时间复杂度会退化为O(n2)。
🔊 稳定性
快速排序分割了拆分了数组,有可能使相同元素的顺序改变是不稳定的。
❤️归并排序
🤍演示说明
归并排序也是一个采用分治算法的经典排序实现,它是将数组拆分成多个小的数组进行排序,然后再将小的数组逐渐合并排序,到最后实现数组的完全排序。
思路很简单,看一下演示图:将数组每两个分一组进行排序,然后两两合并四个一组排序,然后再合并8个一组排序。。。直到最后完成排序。
🔊 下面看一下代码实现
public static int[] sort(int[] array, int startIndex, int endIndex) {
//结束条件
if (startIndex >= endIndex) {
return array;
}
//折半
int midIndex = (startIndex + endIndex) / 2;
sort(array, startIndex, midIndex);
sort(array, midIndex + 1, endIndex);
//数组两两合并
return merge(array, startIndex, midIndex, endIndex);
}
private static int[] merge(int[] array, int startIndex, int midIndex, int endIndex) {
int[] tempArray = new int[endIndex-startIndex+1];
int index1 = startIndex;
int index2 = midIndex + 1;
int index = 0;
//对两个数组进行比较排序,并放入新的数组中
while (index1 <= midIndex && index2 <= endIndex) {
if (array[index1] <= array[index2]) {
tempArray[index++] = array[index1++];
} else {
tempArray[index++] = array[index2++];
}
}
//将右边剩余元素填充到数组中
while (index1 <= midIndex) {
tempArray[index++] = array[index1++];
}
//将左边剩余元素填充到数组中
while (index2 <= endIndex) {
tempArray[index++] = array[index2++];
}
//重新调整数组
for (int i = 0; i < tempArray.length; i++) {
array[i + startIndex] = tempArray[i];
}
return array;
}
🤍分析
🔊 时间复杂度
归并排序法与快速排序法相类似,都是采用了分治算法,所以其时间复杂度为O(n*logn);
🔊 稳定性
归并排序拆分数组之前就是按照原始数组的顺序拆分的,再排序过程中相同元素的顺序没有改变,所以是稳定的。
❤️计数排序
🤍演示说明
计数排序不是比较排序,而是基于一定长度的数组下标实现的排序算法,因为数据的下标是整数,对整数排序的时候可以将对应的值放在数组下标对应的位置,是一个比较简单的排序算法。
直接看下面的演示图,原理还是比较简单的,将数据放在数组对应下标为1,如果存在就继续++。
🔊 下面看一下代码实现
public static int[] sort(int[] array) {
//先获取数组的最大值
int max = array[0];
for (int i = 0; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
//数组是从0开始的
int[] currentArray = new int[max+1];
//存放在中间下标数组中
for (int i = 0; i < array.length; i++) {
currentArray[array[i]]++;
}
int[] resultArray = new int[array.length];
//对中间数组遍历放在新数组里面
for (int i = 0, k = 0; i < currentArray.length; i++) {
for (int j = 0; j < currentArray[i]; j++) {
resultArray[k++] = i;
}
}
return resultArray;
}
🤍分析
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
j++) {
resultArray[k++] = i;
}
}
return resultArray;
}
### 🤍分析
[外链图片转存中...(img-KRZI7BVh-1715825774830)]
[外链图片转存中...(img-apDzIUY7-1715825774831)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**