冒泡排序
冒泡排序的核心思想就是:通过前后元素两两交换的方式,将待排序列的最大值放在序列的最后。
Java基本模板:
import java.util.Arrays;
import java.util.Random;
public class BubbleSort {
public static void main(String[] args) {
//普通写法
Random random = new Random();
int[] arr = new int[10];
for (int i = 0; i < 10 ; i++) {
//随机生成数组内的元素
arr[i] = random.nextInt(20);
}
//为什么i < arr.length - 1 因为假设我们有10个数
//如果我们将九个大数按顺序放在后面后,自然不需要做第十次循环
for (int i = 0; i < arr.length - 1 ; i++) {
//为什么j < arr.length - i - 1
//首先 j 要和 j+1进行比较,,所以至少小于arr.length - 1
//其次因为每一次循环都新增了一个确定的大数放在最后面,
// 第一次 i = 0, j 走到arr.length - 1 - 0
// 第二次 i = 1, j 自然只需要到 arr.length - 1 - i(1)即可
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;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
算法优化:因为我们可能在排序中途已经将数组排好序了,因此我们应该题前终止后续的排序。我们可以设置一个flag标记,如果在一次循环中一次都没有进行交换,我们直接结束循环。
public class BubbleSort {
public static void main(String[] args) {
//优化算法
Random random = new Random();
int[] arr = new int[10];
for (int i = 0; i < 10 ; i++) {
//随机生成数组内的元素
arr[i] = random.nextInt(20);
}
//设立标记
boolean flag ;
for (int i = 0 ; i < arr.length - 1 ; i++ ){
flag = true;
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;
flag = false;
}
}
if (flag){
//如果flag为true说明一次代码都没执行,直接结束循环
break;
}
}
System.out.println(Arrays.toString(arr));
}
}
优化前后效率测试:
该算法的优化主要取决于排序途中是否已经排好序了,因此具体的优化效率应特殊而定,当排序大致有序的情况下,算法优化性能较高。
完全随机的排序100000个数字的测试结果:
大致有序的100000个数字的排序结果
冒泡排序的算法时间复杂度为O(n^2),额外得空间复杂度O(1),是稳定的排序算法。
选择排序
选择排序的核心思想:每次通过随机位置的两两交换将待排序列的最小值放在第一位。
public class SelectionSort {
public static void main(String[] args) {
//ArrayUtil是我自己写的工具类,专用于生成我们需要的数组
int[] arr = ArrayUtil.getRandomArr(15, 20);
/**
* 从小到大排,每次选取最小的数在待排序列的最前面
*/
for (int i = 0; i < arr.length - 1 ; i++) {
for (int j = i + 1; j < arr.length ; j++) {
if (arr[i] > arr[j]){
ArrayUtil.swap(arr,i,j);
}
}
}
System.out.println(Arrays.toString(arr));
}
}
选择排序的时间复杂度为O(n^2),额外得空间复杂度O(1),不是稳定的排序算法。
插入排序
插入排序的核心思想:往排序好的子序列中,每次将一个数按照从后往前的顺序,依次寻找当前元素应该插入的位置,最后将该元素插入进去。
前面两种排序算法因为过于简单没有详细说明,从插入排序开始会对算法的要点进行讲解。
- 我们每次维护数组前 i 个元素是有序的,因为咱们想想,我们要寻找插入元素的顺序,如果是往一个无序的序列中插入是实现不了的。
- 我们怎么维护前 i 个元素是有序的?首先 i = 1 时,前一个元素无论是任何数字都是有序的,因为只有一个数嘛,从第二个元素开始,我们是不是就相当于在往一个有序数列中插入元素了?只要每次插入的位置是正确的那我们是不是就维护了前 i 个元素是有序的。
- 怎么寻找元素插入的顺序,假设现在有待排序的序列 1 3 5 7,我们使用变量 cur 保存当前需要插入的元素,使用元素 j 保存元素需要插入的角标,j 默认从 i + 1 开始,每次我们比较 arr[ j - 1 ] 与 cur 的大小关系,如果cur > arr[ j - 1 ]说明我们还没找到元素插入的位置,让 j - 1的元素后移,然后j - -。
在学习代码的同学我希望能带着以下几个问题思考代码,这样你才能真正理解插入排序。(后面给出了答案,但是你自己要先思考噢)
* 1.为什么 i 从 1 开始 ?
* 2.为什么要用 cur 保存 arr[i] ?
* 3.为什么while循环 j > 0 ?
* 4.为什么用arr[j - 1] 和 cur比较,而不是arr[j - 1] 和 arr[j]比较
import java.util.Arrays;
public class InsertionSort {
public static void main(String[] args) {
int[] arr = ArrayUtil.getRandomArr(15, 20);
/**
* 插入排序
* 1.为什么 i 从 1 开始 ?
* 2.为什么要用 cur 保存 arr[i] ?
* 3.为什么while循环 j > 0 ?
* 4.为什么用arr[j - 1] 和 cur比较,而不是arr[j - 1] 和 arr[j]比较
*/
for (int i = 1 ; i < arr.length ; i++){
int cur = arr[i];
int j = i;
while (j > 0 && arr[j - 1] > cur){
arr[j] = arr[j - 1];
j--;
}
arr[j] = cur;
}
System.out.println(Arrays.toString(arr));
}
}
问题解答:
1.为什么 i 从 1 开始 ?
因为一开始角标为 0 的元素自己就是一个待排序列,所以我们直接从角标 1 开始插入元素。
2.为什么要用 cur 保存 arr[i]
因为我们每次通过将元素后移和 j - -的方式维护 j 所在的角标是可以进行插入的,所以我们一开始的角标 j 所对应的元素将可能被覆盖,所以用 cur 保存 arr[i] 。
3.为什么while循环 j > 0 ?
因为 j 是我们希望插入元素的角标,我们每次和 j - 1 的元素进行比较,希望找到正确的插入位置,所以我们至少要和一个元素进行比较,并且角标 j - 1 不会越界,所以 j > 0。
4.为什么用arr[j - 1] 和 cur比较,而不是arr[j - 1] 和 arr[j]比较?
这个问题其实和第二个类似,因为 j 角标的元素实际上是被我们覆盖了的,而我们真正想插入的元素已经用 cur 保存了,所以是用arr[j - 1] 和 cur比较。
插入排序的算法时间复杂度为O(n^2),尽管插入排序也是N方的排序算法,但是插入排序在实际效率上往往是比前两者要好的,因为实际上插入排序每一次并没有做完整个循环,而是当我们找到我们想插入的位置时就停止了本次循环,所以效率相较前两者较高,额外得空间复杂度O(1),同样的插入排序也是稳定的排序算法。
希尔排序
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。
因为插入排序在小数据和基本有序的数据情况下效率较高,所以我们希望在排序中能构建出小数据排序或者基本有序的数据排序因此有了希尔排序的思想。
1. 如何实现小数据排序? :对数据进行分组
2. 如何实现基本基本有序?:我们对每个分组排序后,数据的趋势基本是大的在后面,小的在前面
实现了基本有序的情况
同时为了保证数据的彻底有序,我们必须不断地缩小分组的长度直到分组长度为1
public static void main(String[] args) {
int[] arr = new int[] {7,3,5,1,2,4,9,8};
int len = 5;
while(len > 0){
//不断缩小增量的循环
//对每一组进行插入排序
for(int j = len ; j < arr.length ; j++){//①号
//每一组的下一个待插入元素
int index = j;
int insert = arr[j];
while (index - len >= 0 && arr[index - len] >insert){
arr[index] = arr[index - len];
index -= len;
}
arr[index] = insert;
}
len /= 2;
}
System.out.println(Arrays.toString(arr));
}
注意代码实现过程中的 for(int j = len ; j < arr.length ; j++)
刚开始看到这块的时候同学们可能会有点疑惑,为什么是 j++,而不是 j + len 一组一组进行插入,其实这只是
一种实现方式,如果每次一组一组完成,需要写两个for循环,但是每次都先插入一组的单个数,那么就只需要
一个for循环。
希尔排序的时间复杂度其实是取决于增量序列的,所以在这里就不具体指出数值,大概为 n 的1.3 、1.5次幂左右。空间复杂度为 O(1),是不稳定的排序算法。
归并排序
做归并排序之前,首先希望大家先去掌握如何将两个有序数组(链表)合并为一个有序数组
归并排序的核心思想就是合并
代码如下
package cent3;
import java.util.Arrays;
import utils.ArrayUtil;
public class 归并排序 {
public static void main(String[] args) {
int[] arr = ArrayUtil.randomArr(10);
mergeSort(arr,0,arr.length - 1);
System.out.println(Arrays.toString(arr));
}
/**
*
* @param arr 待排数组
* @param left 起点
* @param right 终点
*/
private static void mergeSort(int[] arr , int left , int right) {
if(left >= right) {
return;
}
int mid = (left + right) >> 1;
mergeSort(arr,left,mid);
mergeSort(arr,mid+1,right);
merge(arr,left,mid+1,right);
}
private static void merge(int[] arr, int l1 , int l2,int r) {
int[] temp = new int[r - l1 + 1];
for(int i = l1, j = 0 ; i <= r ; i++,j++) {
temp[j] = arr[i];
}
int s1 = 0;
int e1 = l2 - l1;
int s2 = l2 - l1;
int e2 = r - l1 + 1;
int index = l1;
while(s1 < e1 || s2 < e2) {
if(s1 < e1 && s2 < e2) {
if(temp[s1] <= temp[s2]) {
arr[index++] = temp[s1++];
}else {
arr[index++] = temp[s2++];
}
}else if(s1 < e1) {
arr[index++] = temp[s1++];
}else {
break;
}
}
}
}
堆排序
public class HeapSort {
public static void main(String[] args) {
int[] arr = ArrayUtil.randomArr(5);
System.out.println(Arrays.toString(arr));
//heaping(arr);
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
private static void heapSort(int[] arr) {
heaping(arr);
for(int i = arr.length - 1 ; i >= 0 ; i--) {
ArrayUtil.swap(arr, 0 , i);
sink(arr,0,i);
}
}
/**
* 堆排序 首先需要将
* 1.数堆化
* 2.取走堆顶元素
* 3.下沉
*/
private static void heaping(int[] arr) {
//从最后一个非叶子节点开始
// (n - 1 - 1 )/ 2
int index = arr.length / 2 - 1;
for(int i = index ; i >= 0 ; i--) {
sink(arr,i,arr.length);
}
}
private static void sink(int[] arr, int i,int len) {
int left = 2 * i + 1;
if(left >= len) {
return;
}
int maxIndex = left;
if(left + 1 < len) {
maxIndex = arr[left] >= arr[left+1] ? left : left + 1;
}
if(arr[i] < arr[maxIndex]) {
ArrayUtil.swap(arr, maxIndex, i);
sink(arr,maxIndex,len);
}
}
}
快速排序
快速排序有三种形式
单路快排
public class 单路快排 {
public static void main(String[] args) {
int[] arr = ArrayUtil.randomArr(10);
System.out.println(Arrays.toString(arr));
quickSort(arr,0,9);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr ,int start ,int end) {
if(start > end) {
return;
}
int power = arr[start];
int l = start + 1;
int r = end;
while(l <= r) {
if(arr[l] <= power) {
l++;
}else {
swap(arr,l,r);
r--;
}
}
swap(arr,start,r);
quickSort(arr, start, r - 1);
quickSort(arr, l, end);
}
public static void swap(int[] arr,int i , int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
双路快排
public class 双路快排 {
public static void main(String[] args) {
int[] arr = ArrayUtil.randomArr(11);
System.out.println(Arrays.toString(arr));
quickSort(arr, 0, 10);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr , int start , int end) {
if(start >= end) {
return;
}
int midNum = arr[start];
int low = start + 1;//0
int high = end ;//9
//[5, 4, 2, 8, 1, 8, 1, 8, 1, 4]
//low 0 high 9
while(low <= high) {
while(high >= low && arr[high] >= midNum) {
high--;
}
while(high >= low && arr[low] < midNum ) {
low++;
}
if(low <= high) {
swap(arr,low,high);
}else {
swap(arr,start,high);
quickSort(arr, start, high-1);
quickSort(arr, high+1, end);
}
}
}
public static void swap(int[] arr , int i , int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
三路快排
package cent3;
import java.util.Arrays;
import utils.ArrayUtil;
public class 三路快排 {
public static void main(String[] args) {
int[] arr = ArrayUtil.randomArr(10);
System.out.println(Arrays.toString(arr));
quickSort(arr,0,9);
System.out.println(Arrays.toString(arr));
}
private static void quickSort(int[] arr ,int start ,int end) {
if(start > end ) {
return;
}
//[14, 8, 5, 4, 5, 13, 9, 15, 17, 16]
int power = arr[start];//14
int scan = start + 1;
int equal = scan;
int big = end;
while(scan <= big) {
if(arr[scan] == power) {
scan ++;
}else if(arr[scan] < power) {
swap(arr,equal,scan);
scan ++;
equal++;
}else {
swap(arr,scan,big);
big--;
}
}
swap(arr,start, equal - 1);
quickSort(arr, start, equal-2);
quickSort(arr, scan, end);
}
public static void swap(int[] arr,int i , int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
计数排序
后续内容博主想起来了就更新哈哈哈~~~~