1、插入&选择&冒泡&归并(基础排序)
1.1 插入排序
场景:打扑克。
需求:把扑克牌整理成有序的,方便出牌。
思路:把牌分成两部分:一部分是你手里的牌(已经排好序),一部分是要拿的牌(无序)。把一个无序的数列一个个插入到有序数列中。
等价于:一个有序的数组,我们往里面添加一个新的数据后,如何继续保持数据有序呢?我们只要遍历数组,找到数据应该插入的位置将其插入即可。
如上图:我们要把右边的5放入左边排好序的数组中,只需要那5跟左边数组的值一一进行比较即可。对于比5 大的值,往数组后移动,当对比到2时发现5比2大,存入到2后面即可。
代码实现void insertSort(int[] target){ int length = target.length; for (int i = 1; i < length; i++) { //i从1开始,表示第一个不需要排序 int data = target[i]; int j = i - 1; for (; j >= 0; j--) { if (target[j] > data) { // 数据往后移动 target[j + 1] = target[j]; }else { //因为前面已经是排好序的 那么找到一个比他小的就不用找了,因为前面的肯定更小 break; } } //** 需要着重理解一下 为什么是 j+1** target[j + 1] = data; System.out.print("第" +i +"次的排序结果为:"); for(j = 0 ; j < length;j++){ System.out.print(target[j]+" "); } } }
1.2 选择排序
选择排序的思路和插入排序非常相似,也分已排序和未排序区间。但选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。但是不像插入排序会移动数组 选择排序会每次进行交换。
如对数组:4 5 6 3 2 1进行排序
步骤拆分:
第一次:4 5 6 3 2 1 => 1和4进行交换 => 1 5 6 3 2 4
第二次:1 5 6 3 2 4 =>5和2进行交换 => 1 2 6 3 5 4
…
依次类推,最终返回:1 2 3 4 5 6
代码实现:public void selectSort(int[] nums){ int n = a.length; for (int i = 0; i < n; i++) { //最小数据的小标 int minLoc = i; for (int j = i + 1; j < n; j++) { //一直对比选择 if (a[j] < a[minLoc]) { minLoc = j; } } //最小的数据,放到已排序的数组的后面 int tmep = a[i]; a[i] = a[minLoc]; a[minLoc] = tmep; } }
1.3 冒泡排序
冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作。
比如:对 4 5 6 3 2 1进行冒泡排序
第一次冒泡的结果:4 5 6 3 2 1->4 5 3 6 2 1 - > 4 5 3 2 6 1 -> 4 5 3 2 1 6,哪个元素的位置确定了,6
第二次冒泡的结果:4 5 3 2 1 6->4 3 5 2 1 6 -> 4 3 2 5 1 6 -> 4 3 2 1 5 6
第三次冒泡的结果:4 3 2 1 5 6->3 4 2 1 5 6 -> 3 2 4 1 5 6 -> 3 2 1 4 5 6
第四次冒泡的结果:3 2 1 4 5 6->2 3 1 4 5 6 -> 2 1 3 4 5 6
第五次冒泡的结果:2 1 3 4 5 6->1 2 3 4 5 6
代码实现:public static void bubbleSord(int[] arr) { //控制共比较多少轮 for (int i = 0; i < arr.length - 1; i++) { //i < arr.length -1 ? 因为是两个数的比较次数,比如 数组就两个数:1,2 ,只需要比较一次即可 //比较次数 for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; //另外一种交换方法: //a:2,b:3 //a = a+b //b = a- b //a = a- b } } } }
1.4 归并排序
其核心思想是递归和分治的思想。其主要流程如下:
代码实现如下:private static void merge(int[] data, int left, int right) { if (left < right) { int mid = (left + right) / 2; //递归分组 merge(data, left, mid); merge(data, mid + 1, right); // 分完了 接下来就要进行合并,也就是我们递归里面归的过程 mergeData(data, left, mid, right); } } private static void mergeData(int[] data, int left, int mid, int right) { //临时数组,保存对比结果,空间换时间 int[] temp = new int[data.length]; //表示的是左边的第一个数的位置 int point1 = left; //表示的是右边的第一个数的位置 int point2 = mid + 1; //表示的是我们当前已经到了哪个位置了 int loc = left; while (point1 <= mid && point2 <= right) { if (data[point1] < data[point2]) { temp[loc] = data[point1]; point1 ++ ; loc ++ ; }else { temp[loc] = data[point2]; point2 ++; loc ++ ; } } //用来完成剩下的数据组装 while (point1 <= mid) { temp[loc ++] = data[point1 ++]; } while (point2 <= right) { temp[loc ++] = data[point2 ++]; } //排序好的数组放到老数组里面 for (int i = left; i <= right; i++) { data[i] = temp[i]; } }
1.5 快速排序
如 45 28 80 90 50 16 100 10
基准数:一般就是取要排序序列的第一个。
第一次排序基准数:45
从后面往前找到比基准数小的数进行对换:10 28 80 90 50 16 100 45
从前面往后面找比基准数大的进行对换:10 28 45 90 50 16 100 80
…
10 28 16 90 50 45 100 80
10 28 16 45 50 90 100 80
以基准数分为3部分,左边的比之小,右边比之大:{10 28 16} 45 {50 90 100 80}
到此第一次以45位基准数的排序完成。依次类推快排和归并的对比:
- 归并排序的处理过程是由下到上的,先处理子问题,然后再合并。
- 快排其实就是从上到下,先分区,在处理子问题,不用合并。
图解
public static void qSort(int data[], int left, int right) { int base = data[left]; // 就是我们的基准数,取序列的第一个,不能用data[0] int ll = left; // 表示的是从左边找的位置 int rr = right; // 表示从右边开始找的位置 while (ll < rr) { // 从后面往前找比基准数小的数 while (ll < rr && data[rr] >= base) { rr--; } if (ll < rr) { // 表示是找到有比之大的 int temp = data[rr]; data[rr] = data[ll]; data[ll] = temp; ll++; } while (ll < rr && data[ll] <= base) { ll++; } if (ll < rr) { int temp = data[rr]; data[rr] = data[ll]; data[ll] = temp; rr--; } } // 肯定是递归 分成了三部分,左右继续快排,注意要加条件不然递归就栈溢出了 if (left < ll) qSort(data, left, ll - 1); if (ll < right) qSort(data, ll + 1, right); }
2、总结
衡量一个算法的指标:
- 时间效率:决定了算法运行多久(时间复杂度)
- 空间复杂度
- 比较次数&交换次数:排序肯定会牵涉到两个操作,一个比较是肯定的。也存在交换。
- 稳定性:相同的两个数排完序后,相对位置不变。
以上排序方法对比:
排序名称 时间复杂度 是否稳定 额外空间开销 插入排序 O(n^2) 稳定 O(1) 冒泡排序 O(n^2) 稳定 O(1) 选择排序 O(n^2) 不稳定 O(1) 归并排序 O(nlogn) 稳定 O(n) 插入排序 O(nlogn) 不稳定 O(1)
3、如何选择
- 分析场景:稳定还是不稳定
- 数据量:数据量小的时候选什么?比如就50个数,优先选插入(5000*5000=25000000)
- 分析空间
综上所述,没有一个固定的排序算法,都是要根据情况分析的。但是如果你不会分析的情况下 选择归并或者快排。