1. 排序
😁
时间复杂度按照最差的估计
1.1. 选择排序 O(N^2)
public static void selectionSort(int[] arrs ) {
int n = arrs.length;
int minIndex;
for (int i = 0; i < n - 1; i++ ) {
minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arrs[minIndex] > arrs[j]) {
swap(minIndex, j, arrs);
}
}
}
}
public void swap(int a, int b, int[ arrs) {{
int temp = arrs[a];
arrs[a] = arrs[b];
arrs[b] = temp;
}
1.2. 冒泡排序 O(N^2)
public static void bubbleSort(int[] arrs) {
int n = arrs.length;
for(int i = 0; i < n - 1; i++) {
for(int j = 0; j < n - i - 1; j++) {
if(arrs[j] > arrs[j + 1]) {
swap(j, j + 1, arrs);
}
}
}
}
public void swap(int a, int b, int[] arrs) {
int temp = arrs[a];
arrs[a] = arrs[b];
arrs[b] = temp;
}
异或
N ^ N = 0;
0 ^ N = N;
// 所以, swap就可以下边这样, 但是这两个数的内存是两个东西, 不然就会出现问题
public static void swap(int a, int b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
- 一个数组,只有一个数出现奇数次,其他数都是偶数次,时间O(N),空间O(1)
//只有一个数出现奇数次异或后就是它
public staitc int m(int[] arr) {
int result = 0;
for(int i = 0; i < arr.length; i++) {
result ^= arr[i];
}
return result;
}
- 一个数组,有俩个数出现奇数次,其他数都是偶数次,时间O(N),空间O(1)
类似于上道题目,但最后的reslut = a ^ b; a,b是答案,并且a和b肯定不相等,如果a=b,那就都是偶数次了
所以 result != 0; 找到result最右侧的1,因为这个位置是1,所以a和b总有一个数这个位置是1, 另一个是0;
将这个位置所有位置是1的所有数异或,由于其他数都是偶数次,所以这个位置是1的数就是当前的异或结果,
public int[] m(int[] arr) {
int result = 0;
int eor = 0;
for(int i = 0; i < arr.length; i++) {
result ^= arr[i];
}
int rightOne = result & (~result + 1));//最右侧的1
for(int i = 0; i < arr.length; i++) {
if((rightOne & arr[i]) == 1) {
eor ^= arr[i];
}
}
//eor是其中一个答案,所以另一个就是
int a = eor;
int b = result ^ a;
return int[]{a, b};
}
没有状态了
1.3. 插入排序 O(N^2)
0-0有序,0-1有序,0-2有序,... 0-n有序
public void insertSort(int[] arr) {
int n = arr.length;
for(int i = 0; i < n - 1; i++) {
for(int j = i + 1; j > 0 && arr[j] < arr[j - 1]; j--) {
swap(j, j - 1, arr);
}
}
}
public void swap(int i, int j, int[] arr) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
1.4. 二分
- 有序数组找目标数
leetcode
- 大于等于某个数最左侧的位置
- 小于等于某个数最右侧的位置
- 局部最小值 arr无序,相邻两数不相等,找局部最小
public void localMinimum(int[] arr) {
int n = arr.lenght;
int r = n - 1;
int l = 1;
int mid;
while( r <= l) {
mid = l + ((r - l) >> 1);
if(arr[mid] > arr[mid - 1]) {
r = mid - 1;
} else {
l = mid + 1;
}
}
System.out.println(r);
}
1.5. master公式
递归的复杂度
public int process(int l , int r, int[] arr) {
if(l == r) {
return arr[l];
}
int mid = l + (l + (r - l) >> 1);
int leftMax = process(l, mid, arr);
int rightMax = process(mid + 1, r, arr);
return Math.max(leftMax, rightMax);
}
- master公式(子问题等规模)
T(N) = a * T(N/b) + O(N ^ d);
b是等量的子问题,上例是N / 2;
a是次数, 上例是2次
O(N ^ d) 是其他的语句复杂度 上例是O(1);
- logba < d O(N ^ d)
- logba > d O(N ^ logba )
- logba = d O( N ^ d * log N);
1.6. 归并排序
左侧排好序,右侧排好序,整体排序
public void main(int[] arr) {
if(arr = null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public void mergeSort(int[] arr, int l, int r) {
if(l == r) {
return;
}
int mid = l + (r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, r, m);
}
public void merge(int[] arr, int l, int r, int mid) {
int p = l;
int q = mid + 1;
int[] help = int[r - l + 1];
int index = 0;
while(p <= mid && q <= r) {
help[index++] = arr[p] <= arr[q] ? arr[p++] : arr[q++];
}
while(p <= mid) {
help[index++] = arr[p++];
}
while(q <= r) {
help[index++] = arr[q++];
}
for(int i = 0; i < index; i++) {
arr[l + i] = help[i];
}
}
master公式计算复杂度
a = 2, b = 2, d = 1
所以归并排序整体复杂度是0(N * logN)
空间是O(N);
- 小和问题
左边比他小的数的和
arr = {1, 3, 4, 2, 5};
1的左边没有比他小的数
3的左边比他小的数是1
4的左边比他小的数是1, 3 sum 是4
2的是1,
5的是1+ 3+ 2+ 4 = 10;
整体是10 + 4 + 1+ 1 = 16
转换问题
转换成右边比当前数大的有几个,就产生几个这个数的小和
1可以产生4个,3可以产生2个,4是1个,2是1个,5没有
整体是1 * 4 + 3 * 2 + 4 * 1 + 2 * 1 = 16;
public int smallSum(int[] arr) {
return process(arr, 0, arr.length - 1);
}
public int process(int[] arr, int l, int r) {
if(l == r) {
return 0;
}
int mid = l + ((r - l) >> 1);
int leftSum = process(arr, l, mid);
int rightSum = process(arr, mid + 1, r);
return leftSum + rightSum + merge(arr, l, mid, r);
}
public int merge(int[] arr, int l, int m, int r) {
int sum = 0;
int p = l;
int q = m + 1;
int[] help = int[r - l + 1];
int index = 0;
while(p <= m && 1 <= r) {
sum += arr[p] < arr[q] ? arr[p] * (r - q + 1) : 0;
help[index++] = arr[p] < arr[q] ? arr[p++] : arr[q++];
}
while(p <= m) {
help[index++] = arr[p++];
}
while(q <= r) {
help[index++] = arr[q++];
}
for(index = 0; index < help.length; index++) {
arr[l + index] = help[index];
}
return sum;
}
- 逆序对
左边的数比右边的大就是一个逆序对
arr = {3, 2, 4, 5, 0}
(3, 2); (3, 0); (2, 0); (4, 0); (5, 0);
这个就是求右边有多少个数比它小,和上边的差不多哦
1.7. 快排
- 荷兰国旗 1
给定一个数num,把小于等于num的数放在数组的左边,大于num的数放在数组的右边
要求时间O(N),空间O(1)
给一个下标 l,左边是小于等于区域
arr[i] < num 时i++
arr[i] > num 时l++, i++,
public void process(int[] arr, int target) {
int l = 0;
for(int i = 0; i < arr.length; i++) {
if(arr[i] > target) {
swap(l++, i, arr);//i-- 是因为交换过来之后还要在比较, 交换过来的数不知道大小
}
}
}
public void swap(int l, int r, int[] arr) {
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
}
- 荷兰国旗 2
给定一个数num,把小于num的数放在数组的左边,大于num的数放在数组的右边,等于num的放在数组的中间
要求时间O(N),空间O(1)
给定两个下标l, r,
l左边全是小于num的数
r右边全是大于num的数
如果arr[i] < num 交换l+1和i位置
如果arr[i] == num 不变化,i++
如果arr[i] > num 交换r和i位置
public void process(int[] arr, int target) {
int l = 0;
int r = arr.length - 1;
for(int i = 0; i < r; i++) {
if(arr[i] == target) {
} else if(arr[i] > target) {
swap(i--, r--, arr);//交换后的数大小不知道,所以需要i--,再比较一次
} else {
swap(l++, i, arr);
}
}
}
public void swap(int l, int r, int[] arr) {
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
}
- 快排 1.0 (拿最后一个数作为num)
递归执行此操作,时间复杂度是O(N^2),
如果已经有序的话,最坏是O(N^2)

- 快排 2.0
划分值随机
额外空间O(logN)
public void quickSort(int[] arr) {
process(arr, 0, arr.length - 1);
}
public void process(int[] arr, int l, int r) {
if(l >= r) {
return;
}
int index = (int)(Math.random(r + 1 - l))+ l;
swap(index, r, arr);//交换随机数
int[] p = partition(arr, l, r);
process(arr, l, p[0] - 1);//小于区
process(arr, p[1] + 1, r);//大于区
}
public int[] partition(int[] arr, int l, int r) {
int less = l;
int more = r;
//int target = arr[r];
//int i = l;
while(l < more) {
if(arr[l] == arr[r]) {
l++;
} else if(arr[l] > arr[r]) {
swap(l, --more, arr);
} else {
swap(less++, l++, arr);
}
}
swap(more, r, arr);
return new int[]{less, more};
}
public void swap(int i, int j, int[] arr) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
1.8. 堆排序
空间复杂度O(1);
把数组看成一个完全二叉树(从左到右,依次递满)
i位置的左孩子, 2 * i + 1
右孩子, 2 * i + 2;
父 i - 1 / 2;
大根堆,每一颗树的头节点是最大值 heapInsert
堆排序,
- 变成大根堆,0-0变成大根堆,0-1。。。。0 - N-1大根堆,根据下标对应
- 把第一个和最后一个交换,heapSize--,0位置做heapify.......重复
public void heapSort(int[] arr) {
int n = arr.length;
int heapSize = n;
/**
for(int i = 0; i < n; i++) { O(N)
heapInsert(arr, i); O(logN)
}
*/
//从最后一个位置执行heapify也是可以的,这个操作是 O(N)
for(int i = n - 1; i > 0; i--) {
heapify(arr, i, heapSize);
}
swap(0, --heapSize, arr);
while(heapSize > 0) {//O(N)
heapify(arr, 0, heapSize); //O(logN)
swap(0, --heapSize, arr);
}
}
//某个数向上移动
public void heapInsert(int[] arr, int index) {
//不需要判断index == 0;因为这时候while必然不会进入
while(arr[index] > arr[(index - 1) / 2]) {
swap(index, (index - 1) / 2, arr);
index = (index - 1) / 2;
}
}
//某个数在index位置,能否向下移动
public void heapify(int[] arr, int index, int heapSize) {
int left = index * 2 + 1;//左孩子的下标
while(left < heapSize) {//有孩子
//只有存在右孩子&&右孩子的值比左孩子大才会将右孩子的下标赋给largest
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ?
left + 1 : left;
//将两个孩子中的最大值和当前的值比较,只有小于时才需要交换
largest = arr[largest] > arr[index] ? largest : index;
//它的孩子没有比他大,没必要再比较了,直接结束
if(largest == index) {
break;
}
//需要交换
swap(index, largest, arr);
//更新index
index = largest;
//更新左孩子
left = index * 2 + 1;
}
}
public void swap(int i, int j, int[] arr) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
- 堆的问题
一个几乎有序的数组(几乎有序是指,如果把数组排好序,每个元素的移动距离不超过k,并且k相对数组大小来说很小)选择一个合适的排序方式
准备一个小根堆,大小为k
- 最先遍历前k + 1个数,将小根堆的头弹出,这个就是0位置的数,因为k后边位置的数是不可能出现在0位置的
- 把k+1位置的数放到小根堆,然后将头放大1位置
- 重复操作。。。。。当数组快结束的时候,将小根堆依次弹出,就有序了
时间O(N*logK),k很小就可以认为是O(N)的
小根堆,java有**PriorityQueue**这个API
API 是不可以调整创造好的堆的
自己手写的堆可以,
看需求是手写还是系统
public void sortArrayDistanceLessK(int[] arr, int k) {
PriorityQueue<Integer> heap = new PriorityQueue<>();
int index = 0;
for(; index < Math.min(arr.length, k); index++) {
heap.add(arr[index]);
}
int i = 0;
for(; index < arr.length; index++) {
heap.add(arr[index]);
arr[i++] = heap.poll();
}
while(!heap.isEmpty()) {
arr[i++] = heap.poll();
}
}
1.9. 比较器
public class MyComparator implements Comparator<MyClass> {
@Override
public int compare(o1, o2) {
return o1 - o2;
}
}
1.10. 桶排序
不基于比较的排序
- 计数排序
词频统计
-
基数排序(排序对象有进制)
-
看最大的数有几位
-
0-9,一共十个桶,按照个位数是多少进入桶
-
然后从左往右,依次倒出数字,先进先出FIFO
-
十位数的值是多少进入桶,然后出来,
-
百位。。。。。重复
为什么可以利用辅助数组和count数组可以实现FIFO
- 首先,我们计算出了原数组的某一个bit位的值,假设是十位的,并且我们把这个位置的词频统计出来了,如count[2] = 3;就代表,十位上是2的数有三个
- 然后,我们求出了前缀和数组,假如count[2] = 7;那么就代表十位上的数 <= 2的数有7个
- 然后呢!我们倒序遍历了arr数组,如果arr[n - 1] 的十位上是2,那就说明这个数进入十位是2这个桶的时候是最后进入的,那么他应该最后出来(FIFO),所以他在这一轮完成后,在数组中的位置是在arr[6],因为count[2]前面一共有7个数,那么它就是在第七个数的位置。
public void radixSort(int[] arr) {
radixSort(arr, maxBits(arr));
}
public void radixSort(int [] arr, int digits) {
int n = arr.lenght;
int[] help = new int[n];//辅助数组
for(int d = 1; d <= digits; i++) {//最大值有多少位就重复几次操作
int[] count = new int[10];//计数,和辅助数组可以实现FIFO
for(int i = 0; i < n; i++) {//对应的bit位的数上的词频
count[bitValue(arr[i], d)]++;
}
for(int i = 1; i < 10; i++) {//求出count的前缀和数组
count[i] += count[i - 1];
}
for(int i = n - 1; i >= 0; i--) {
int j = bitValue(arr[i], d)
int index = count[j] - 1;
help[index] = arr[i];
count[j]--;
}
for(int i = 0 ; i < n; i++ ) {
arr[i] = help[i];
}
}
}
public int bitValue(int num, int d) {
return (num / (int)Math.pow(10, d - 1)) % 10;
}
public int maxBits(int[] arr) {
int max = Integer.MIN_VALUE;
for(int i = 0; i < arr.lenght; i++) {
max = Math.max(max, arr[i]);
}
int res = 0;
while(max != 0) {
res++;
max /= 10;
}
return res;
}
1.11. 排序算法的总结
- 稳定性
多个相等的数排完序后,他们的相对顺序维持,这种排序就是稳定
- 总结
| 稳定性 | 时间复杂度 | 空间 | |
|---|---|---|---|
| 选择 | N | O(N^2) | O(1) |
| 冒泡 | Y | O(N^2) | O(1) |
| 插入 | Y | O(N^2) | O(1) |
| 归并 | Y | O(N*logN) | O(N) |
| 快排 | N | O(N*logN) | O(logN) |
| 堆排 | N | O(N*logN) | O(1) |
| 桶排序 | Y | O(N*logN) | O(N) |
本文由mdnice多平台发布