1 选择排序
给N个数,第一个小的放在第一个位置,第二个小的放在第二个位置就是选择排序了
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if(arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr, i, minIndex);
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
2冒泡排序
给N个数,从左到右,谁大谁往后,那么一次遍历后,最大值一定在最后一个位置,以此类推,排序即可
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int end = arr.length - 1; end > 0; end--) {
for (int i = 0; i < end; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
3插入排序
给N个数 从0-0,0-1,0-2,0-3。。范围依次去处理变为升序即可
假如 43125
0-0范围 4不变
0-1范围 变为34
0-2范围 1看到4大变为314,1看到3大变为 134, 以此类推
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
4归并排序
归并:顾名思义,有点分治的意思,比如你日常开发,数据大,你需要多线程处理,然后一块给你返回汇总,归并排序就是以二分为基础,先搞左边的,再搞右边的,最后搞一块就行
比如 3421 拆成 34 和21
左边还是34 右边变为12
然后左右都有一个指针先指向3和1 右边小则输出1移动右指针,还小输出2移动右指针为null,左指针从左到右依次输出34,最终排序为1234
// 递归方法实现
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
// arr[L...R]范围上,请让这个范围上的数,有序!
public static void process(int[] arr, int L, int R) {
//L和R相等已经二分到了极致,不需要任何操作了
if (L == R) {
return;
}
// int mid = (L + R) / 2 二分找到中点
int mid = L + ((R - L) >> 1);
//找到左边的按照顺序去排序
process(arr, L, mid);
//找到右边的按照顺序去排序
process(arr, mid + 1, R);
//合并左右
merge(arr, L, mid, R);
}
//arr 数组,L数组的左边界,M数组的中间+1的数,R右边界
public static void merge(int[] arr, int L, int M, int R) {
//存储答案
int[] help = new int[R - L + 1];
//计数
int i = 0;
//左边开始位置指针
int left = L;
//右边开始位置指针
int right= M + 1;
//左右指针 谁小谁赋值
while (left <= M && right <= R) {
help[i++] = arr[left] <= arr[right] ? arr[left++] : arr[right++];
}
// 要么left越界,要么right越界
// 不可能出现:共同越界 将没有越界的赋值
while (left <= M) {
help[i++] = arr[left++];
}
while (right <= R) {
help[i++] = arr[right++];
}
//放回arr
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
}
5快排(荷兰国旗)
先了解
给N个数排序,先找到最后一个数X,右区间把最后一个数扩进去,小于等于X放左边,大于X放右边,等于放中间遍历满足条件
1.当前值<X,当前值和<=区的下一个数交换,<=区间右扩,当前值右移
2.当前值>X,当前值不动,>=区左边的数和当前值交换,>=区左扩
3等于当前值 右移
代码中 swap(arr, L + (int) (Math.random() * (R - L + 1)), R);这个就是随机打基准值,不是取最右边的数,是随机取放在最右边,这样复杂度会从0N2变为 NlogN
public static int[] partition(int[] arr, int L, int R) {
int lessR = L - 1;
int moreL = R;
int index = L;
while (index < moreL) {
if (arr[index] < arr[R]) {
swap(arr, ++lessR, index++);
} else if (arr[index] > arr[R]) {
swap(arr, --moreL, index);
} else {
index++;
}
}
//把最后边的值归位
swap(arr, moreL, R);
//返回相等的区间
return new int[] { lessR + 1, moreL };
}
public static void process(int[] arr, int L, int R) {
if (L >= R) {
return;
}
swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
int[] equalE = partition(arr, L, R);
//从相等区间的左边和右边继续排序去
process(arr, L, equalE[0] - 1);
process(arr, equalE[1] + 1, R);
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
6 桶排序
桶排序思想下的排序:计数排序 & 基数排序
1)桶排序思想下的排序都是不基于比较的排序
2)时间复杂度为O(N),额外空间负载度O(M)
3)应用范围有限,需要样本的数据状况满足桶的划分
4)一般来讲,计数排序要求,样本是整数,且范围比较窄
5)一般来讲,基数排序要求,样本是10进制的正整数
算法思路1:
假如有 101 201 105 055 这几个数 十进制的数默认准备 10个桶子,
先装个位数的
---------------------------------------------------------------
0号桶
1号桶 101 201
2号桶
5号桶 105 055 弹出
101 201 105 055
------------------------------------------------------
十位数进
0号桶 101 201 105
1号桶
2号桶
5号桶 055 弹出
101 201 105 055
------------------------------------------------------------------------
百位数进
0号桶 055
1号桶 101 105
2号桶 201
弹出
055 101 105 201 排好序了
算法思路2:
假如有 101 201 105 055 这几个数 十进制的数默认准备 int[10]
先装个位数的(从左到右)
---------------------------------------------------------------
int[1]=2 int[5]=2
然后准备一个int[10]' 计算前缀和
Int[0]=0 Int[1]=2 Int[2]=2...int[5]=7 这个的含义就是,
当我们从右往左遍历的时候
当你的个位数的值是X的时候
那么你在arr的位置[用help[]代替]应该安排在help[int[X]-1]这个位置然后把int[X]--
也就是说int前缀和代表你这个值是在help的从0到X的位置,因为你从右往左遍历所以放在最后一个位置。
------------------------------------------------------
十位数进 百位数进 雷同 我们就是用int[]替代了桶子,因为桶子可能会建立多个队列
public static void radixSort(int[] arr, int L, int R, int digit) {
final int radix = 10;
// 有多少个数准备多少个辅助空间
int[] help = new int[R - L + 1];
//遍历N位数 个位/十位/百位
for (int i = 1; i< digit; i++) {
int[] count=new int[radix];
for (int j = L; j<= R; j++) {
count[getDigit(arr[j], i)]++;
}
//获取前缀和
for (int z = 1; z < count.length; z++) {
count[z]=count[z-1]+count[z];
}
//从右往左遍历
for (int x = R; x >= L; x--) {
int digit1 = getDigit(arr[x], i);
help[count[digit1]-1]=arr[x];
count[digit1]--;
}
//给之前的arr[]赋值
for (int a = L, b = 0; a <= R; a++, b++) {
arr[a] = help[b];
}
}
}
public static int getDigit(int x, int d) {
return ((x / ((int) Math.pow(10, d - 1))) % 10);
}
public static void radixSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
radixSort(arr, 0, arr.length - 1, maxbits(arr));
}
public static int maxbits(int[] arr) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int res = 0;
while (max != 0) {
res++;
max /= 10;
}
return res;
}
总结
1)不基于比较的排序,对样本数据有严格要求,不易改写
2)基于比较的排序,只要规定好两个样本怎么比大小就可以直接复用
3)基于比较的排序,时间复杂度的极限是O(N*logN)
4)时间复杂度O(N*logN)、额外空间复杂度低于O(N)、且稳定的基于比较的排序是不存在的。
5)为了绝对的速度选快排、为了省空间选堆排、为了稳定性选归并
6)归并排序的额外空间复杂度可以变成O(1),“归并排序 内部缓存法”,但是将变得不再稳定。
7)“原地归并排序" ,会让时间复杂度变成O(N^2)
8)快速排序稳定性改进,“01 stable sort”,但是会对样本数据要求更多。