大家好,我是被白菜拱的猪。
一个热爱学习废寝忘食头悬梁锥刺股,痴迷于girl的潇洒从容淡然coding handsome boy。
一、写在前言
大学已经两年了,至今对各种排序仍不熟悉,光听说什么希尔排序,归并排序,他们到底是个啥却丝毫不知,有时候恨不得挖个洞钻进去。之前不知道也就算了,别让自己永远不知道。
有人在学排序的时候会出现一个问题,学了之后不久就忘掉了,知道有这么一个东西,但最终怎么写却不知道,我认为有两种原因,一没有深刻的了解各个排序算法的流程,也就是计算机是如何将数据排序的,二是没有把握好细节,知道思路了但不知道怎么敲,往往是有些细节没有把握好。
我们要做的是记住的是计算机是如何排的,以及他们之间的差别,人的大脑在记东西时,图片往往比文字更容易记住,所以我这里推荐一个可视化网站可以帮助我们记忆:
好了闲话不多说,让我们开启排序的旅程。
二、排序
(一)简单介绍
在具体介绍排序之前,我们先要了解了解排序有多少种,先在宏观上把握好。
在接下来我们要讲八种排序算法,冒泡排序,直接插入排序,简单选择排序属于简单算法,希尔排序,堆排序,归并排序,快速排序属于改进算法,然后在加一个基数排序(桶排序)。
除了了解什么排序之外,还要了解排序算法的时间复杂度,从而晓得他们哪个牛逼不牛逼。
下面简单介绍几种常见的时间复杂度(我现在对此还此是云里雾里,所以更要好好学啊)
- O(logn)
int i = 1
while(i<n) {
i = i *2;
}
- O(n)单层的for循环
- O(nlogn)就是将O(logn)执行n遍,就是线性对数阶
for(int m = 1; m < n ;m++) {
i = 1;
while(i < n) {
i = i * 2;
}
}
常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)< Ο(n^k) <Ο(2^n) ,随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
这里对算法的时间复杂度是什么,还有什么事前分析和事后统计就不在过多的介绍。那我们直接开始吧。
(二)冒泡排序
1、思路图解
假如图解没看懂的话建议大家一定要去我推荐的网站去操作操作,一定要了解他的原理,因为后面还要讲七种排序算法,搞不好会弄混,一定要想一想他们之间的区别,以及为什么会叫这个名字,冒泡,就像小泡泡一样咕咕咕的升到水面。
另外,我们发现假如直接是 1 2 3 4 5时,已经是排好顺序的,但是它还是要进行比较,这种比较就是多余的,所以我们可以将代码进行优化。
冒泡排序的时间复杂度是O(n^2)
2、代码实现
package com.codingboy.sort;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Random;
/**
* @author: ljl
* @date: 2020/8/11 21:40
* @description: 冒泡排序
*/
public class BubbleSort {
public static void main(String[] args) {
// int[] arr = {5, 10 ,-1 , 3 ,2 };
// bubbleSort(arr);
// System.out.println(Arrays.toString(arr));
//下面对八万条数据进行测试
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random() * 80000); //Math.random [0,1)
}
long before = System.currentTimeMillis();
bubbleSort(arr);
long after = System.currentTimeMillis();
System.out.println("用时(s):" + (after - before)/1000);
}
//单独封装成一个方法,并且使用flag来优化,以防已排好进行重复判断
public static void bubbleSort(int[] arr) {
int temp = 0;
boolean flag = false;//表示没有进行交换
//先把握好大体的方向,要进行arr.length - 1 趟排序,才能把各个数字放在准确的位置
for (int i = 0; i < arr.length -1; i++) {
//每一趟排序,第一趟要进行4次交换,第二趟要进行3次交换
for(int j = 0;j < arr.length - 1 -i;j++) {
//交换
if(arr[j] > arr[j+1]) {
//假如进项了交换,就将flag = true
flag = true;
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
//一趟下来没有交换位置,说明就已经排好了顺序
if(!flag) {
break;
}else {
flag = false;
}
}
}
}
这里将冒泡单独封装成了一个方法,而且对此进行了优化,除此之外又对八万条随机数据进行了排序排序,用时约十秒钟。
(三)选择排序
1、思路图解
选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从arr[0]~ arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1]~ arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]~ arr[n-1]中选取最小值,与arr[2]交换,…,第i次从arr[i-1]~ arr[n-1]中选取最小值,与arr[i-1]交换,…, 第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
2、代码实现
我们发现要使用两个for循环,外面的一层是代表进行几轮排序,每一轮排序就确定好一个位置,五个数字,就进行4轮排序,每一轮排序就要去判断谁最小,第一轮,假如第一个数字最小,那么就要将当前最小的数与后面的数一一判断,从而得到最小的数,也就是要进行四次判断,同理第二轮要进行三次判断。talk is shit,show me the code。
package com.codingboy.sort;
import java.util.Arrays;
/**
* @author: ljl
* @date: 2020/8/12 9:33
* @description: 选择排序
*/
public class SelectSort {
public static void main(String[] args) {
int[] arr1 = {109, 1, 10, 5};
System.out.println(Arrays.toString(arr1));
selectSort(arr1);
System.out.println(Arrays.toString(arr1));
//下面对八万条数据进行测试
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random() * 80000); //Math.random [0,1)
}
long before = System.currentTimeMillis();
selectSort(arr);
long after = System.currentTimeMillis();
System.out.println("用时(s):" + (after - before)/1000.0);
}
public static void selectSort(int[] arr) {
//用来记录最小值
int min = 0;
//用来记录最小值的索引,方便日后交换位置
int index = 0;
for (int i = 0; i < arr.length - 1; i++) {
min = arr[i];
index = i;
//下面进行判断,选择出最小值与开始位置交换
for (int j = i + 1;j < arr.length;j++) {
if (min > arr[j]) {
min = arr[j];
index = j;
}
}
//比较结束,交换位置,假如最小值还是原先的那个值即index没有变,就不用交换
if(index != i){
arr[index] = arr[i];
arr[i] = min;
}
}
}
}
同样我们对选择排序也进行了八万条数据测试,发现选择排序快于冒泡排序,这是因为选择排序每次循环只进行了一次交换,而冒泡则交换多次。而选择排序的时间复杂度与冒泡排序相同都是O(n^2)。
(四)插入排序
1、思路讲解
插入排序是将待排序的n个元素分为两个部分,有序表和无序表,有序表一开始只有一个元素,无序表则有n-1个元素,我们接下来要做的就是将无序表中的元素一一插进有序表。
插入的关键在于如何找到合适的位置,在讲排序时,我们都是按照升序讲解,首先将要插入的元素与前面的元素进行比较,假如小于前面的元素,则将前面的元素后移,知道前面的元素小于要插入的元素。说的有些抽象直接上代码。
2、代码实现
package com.codingboy.sort;
import java.util.Arrays;
/**
* @author: ljl
* @date: 2020/8/12 10:55
* @description: 插入排序
*/
public class InsertSort {
public static void main(String[] args) {
int[] arr1 = {101, 34, 119 , 1 ,1};
System.out.println(Arrays.toString(arr1));
insertSort(arr1);
System.out.println(Arrays.toString(arr1));
//下面对八万条数据进行测试
int[] arr = new int[80000];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random() * 80000); //Math.random [0,1)
}
long before = System.currentTimeMillis();
insertSort(arr);
long after = System.currentTimeMillis();
System.out.println("八万条数据测试用时(s):" + (after - before)/1000.0);
}
public static void insertSort(int[] arr) {
int insertValue = 0; //要插入的元素
int insertIndex = 0; //要插入的索引值
for (int i = 1; i < arr.length; i++) { //这里不需要-1,有序表默认只有第一个元素,所以我们需要将后面的元素一一插入
insertValue = arr[i]; //默认插入的元素为当前元素
insertIndex = i - 1; //默认要插入的地方为当前元素前面的位置
//假如前面的比他大,则后移
while (insertIndex >= 0 && arr[insertIndex] > insertValue){//防止越界
//后移
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
//最后插入指定的位置,即insertIndex + 1,因为前面while中最后insertIndex--了。减过的要给他加回去
arr[insertIndex + 1] = insertValue;
}
}
}
最后八万条数据测试,卧槽好快啊
时间复杂度同样是O(n^2)
三、结束语
这里有一个问题,为什么同样的时间复杂度,执行的时间插入排序要快那么多?
代码上看,冒泡排序的数据交换比插入排序的数据移动要复杂,冒泡排序需要三次赋值操作,而插入排序只需要一次。这也是插入排序受欢迎的原因之一。
这里就先讲讲我们对此不陌生的三个排序,后面讲希尔,归并等不熟悉的排序算法。
我依稀的记得大一上c++考试的时候考了一道选择排序,当时一脸懵逼,书上原封不动的代码,但是我当时看了冒泡,觉得选择不会考,结果他偏偏就考了,最后瞎写的,现在想想当初是真的菜啊,但是我们每一个人不都是从菜一步步过来的嘛。