@萌新也能快速入门,八大排序的详细分析过程~~!
1、分析前言,来自@笔笔的独白~~
排序,作为算法基础,一定要做到完全掌握,万不可抱着:太low了而不屑于去学的心态。须知,无论多么复杂的算法都是由繁入简,一步一步优化才得到的。且抛开这些不谈,当你以后面试的时候也有可能被问到,所以,在学习算法的过程中,无论是多么简单的算法,也一定要做到心平气和的接收它,正所谓:海纳百川,有容乃大!说的就是这个道理!!!
2、独白完了,一起进入正题吧~~
2.1、冒泡排序
算法介绍: 冒泡排序是一个特别形象的排序算法,就和它的名字一样,根据排序的规则,不断的将最大(或最小)的值往上冒,就像水泡一样,大的水泡逐渐往上冒,小的水泡则是会沉下去。
算法分析: 假定现在有一个数组arr[] = {5,4,3,2,1},我们现在要将它按从小到大的顺序排列,那么按照冒泡的思想,我们就应该让较大的数(大水泡)往上冒,而小的数(小水泡)往下沉,而实际上,我们实现这一过程的方式不过是从第一个数开始,让其不断的去与后面的每一个元素进行比较、交换,如此,一轮比较下来,我们就能够得到一个最大的数,其位置处于数组的最末尾,第二轮,如法炮制,但因为第一轮已经确定了最大的数,所以第二轮我们实际上是从剩下的元素中找出第二大的数,让它处于数组中倒数第二的位置,然后就是其它轮比较,不断找出最大的值让其往上冒,当最后一轮交换完毕,数组也就排序好了。为了照顾和我新手宝宝(╹▽╹),笔笔还是画个图分析分析:
画图分析:
代码实现:
/**
* 冒泡排序
* @param arr 需要排序的数组
*/
public static void bubbling(int arr[]) {
//n个数只需要比较n-1次
for(int i=0;i<arr.length-1;i++) {
//每一次遍历都会将一个最大的数往上冒,所以比较次数也会递减
for(int j=0;j<arr.length-1-i;j++) {
if(arr[j]>arr[j+1]) {
//异或交换数据
arr[j] = arr[j]^arr[j+1];
arr[j+1] = arr[j]^arr[j+1];
arr[j] = arr[j]^arr[j+1];
}
}
}
}
2.2、选择排序
算法介绍: 选择排序和冒泡在思想上基本一致,冒泡是将大的数一点点的往上冒,而选择,则是每一轮都从当前剩余的元素中选择一个最大(或最小的值放在最前面或后面),实际上,冒泡中大的数据往上冒也是选择的一种体现,其和选择的区别在于,冒泡是一点点的往上冒,选择是选择一个最大的直接交换过去。
算法分析: 和冒泡本质一样,就不过多赘述,这里主要分析一样选择和冒泡最主要的区别,假设有一个数组,现在第一轮遍历,我们要选择一个最小的值放在该数组的第一个元素,那么我们可以设置一个变量去存储最小值的索引(空间换时间)最后遍历完了直接让第一个元素和该最小值进行一次交换,后面第二个、第三个…元素也是如此,它每一轮都只进行一次交换,相较于冒泡的每一轮都与后面的每一个元素进行交换而言,选择排序的速度无疑是要更快的。
动画分析:(自己实在是懒的画了😂引用大佬的图真香:https://blog.youkuaiyun.com/bluesliuf/article/details/89055198)
代码实现:
/**
* @function 完整版选择排序
* @param arr 需要排序的数组
*/
public static void fullEditionSelectSort(int arr[]) {
boolean flag = false;
for (int j = 0; j < arr.length - 1; j++) {
// 假定的当前最小值
int min = arr[j];
// 假定的当前最小索引:说是最小,实际是用在交换的时候存入较大的数据用的
int minIndex = j;
// 从j的后一个数开始比较
for (int i = j + 1; i < arr.length; i++) {
if (arr[j] > arr[i]) {
flag = true;
min = arr[i]; // 重置最小值
minIndex = i; // 重置最小索引
}
}
// 交换,保存的较小值的索引存入当前较大的数据j
// 再把arr[j]设置为较小的数
if (flag) {
arr[minIndex] = arr[j];
arr[j] = min;
flag = false;
// ArraysUtil.arrayPaint(arr);
// System.out.println();
}
}
}
2.3、插入排序
算法介绍: 算法不稳定,效率较高,为N*lnN,它的主要思想是将数组分解为两个部分,左部分为已排序好的子数组,右部分为未排序的子数组,每次取未排序子数组的第一个元素插入到已排序的子数组中,直到未排序的子数组的长度为0;
算法分析: 以我个人的理解,插入排序无非就是在选择排序上做了一点点改变而已,插入的过程实际上也是选择的过程,最开始,有序数组的长度为1,取其后一位也就是无序数组的第一个元素,将其插入到有序的数组中,而为了不改变其他元素的结构,实现插入的过程实际就是让插入位置后的索引到插入数的索引的前一位集体后移,然后利用外循环就能完美实现无序子数组中所有元素的插入了。
动画分析:(此图引用自bluesliuf的https://blog.youkuaiyun.com/bluesliuf/article/details/89043746)

代码实现:
// 插入排序
public static void insertSort(int arr[]) {
for (int i = 1; i < arr.length; i++) {
int insertVal = arr[i];
int insertIndex = i - 1;
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
// 让前一个元素覆盖后一个元素
arr[insertIndex + 1] = arr[insertIndex];
// 指针后移
insertIndex--;
}
//如果insertIndex + 1 = i,则说明insertIndex一次都没有变化过,也就不需要交换了
if (insertIndex + 1 != i) {
// 因为当insertIndex出循环后的索引指向需要交换的元素前一个位置,所以需要进行一次后移
arr[insertIndex + 1] = insertVal;
}
// ArraysUtil.arrayPaint(arr);
}
}
2.4、希尔排序
算法介绍:对于插入排序来说,如果后面的数较小,这样往前插入的次数就会越多,而通过希尔排序的概念,我们可以极大的缩减这一部分的运行时间。可以说,希尔排序就是对插入排序的进一步扩展、优化的结果。
算法分析:在希尔排序中,引入了增量这一概念,通常情况下,初始的增量值为数组长度的一半,每次循环增量值取自身的一半,利用增量,我们可以实现对原数组的划分,然后利用插入、冒泡或选择对同一组的元素进行排序(为了保证效率,尽量使用插入),如此循环下去,直到增量值为0,排序结束。
动画分析:
代码实现:
/**希尔排序:插入法/
public static void fullEditionShellSort(int arr[]) {
//控制增量
for(int gap=arr.length/2;gap>0;gap/=2) {
// 9 8 7 6 5 4 3 2 1
for(int i=gap;i<arr.length;i++) {
int index = i;
int temp = arr[i];
//这里利用了插入排序提高效率,减少了不必要的交换
while(index - gap >=0 && temp < arr[index-gap]) {
arr[index] = arr[index-gap];
index-=gap;
}
arr[index] = temp;
}
}
}
/**希尔排序:移动法(冒泡法)/
public static void shellSort(int arr[]) {
//控制增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
//控制当前组的范围
for (int i = gap; i < arr.length; i++) {
//对当前组进行排序
for (int j = i - gap; j >= 0; j -= gap) {
if (arr[j] > arr[j + gap]) {
arr[j] = arr[j] ^ arr[j + gap];
arr[j + gap] = arr[j] ^ arr[j + gap];
arr[j] = arr[j] ^ arr[j + gap];
}
}
}
}
2.5、快速排序
算法介绍:
算法分析:
动画分析:
2.6、* 归并排序(分治)
算法介绍:
算法分析:
动画分析:
2.7、堆排序(桶排序)
算法介绍:
算法分析:
动画分析: