算法总结(一)

78 阅读6分钟

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;
}
  1. 一个数组,只有一个数出现奇数次,其他数都是偶数次,时间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;
}
  1. 一个数组,有俩个数出现奇数次,其他数都是偶数次,时间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)

img

  • 快排 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

堆排序,

  1. 变成大根堆,0-0变成大根堆,0-1。。。。0 - N-1大根堆,根据下标对应
  2. 把第一个和最后一个交换,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

  1. 最先遍历前k + 1个数,将小根堆的头弹出,这个就是0位置的数,因为k后边位置的数是不可能出现在0位置的
  2. 把k+1位置的数放到小根堆,然后将头放大1位置
  3. 重复操作。。。。。当数组快结束的时候,将小根堆依次弹出,就有序了

时间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. 桶排序

不基于比较的排序

  1. 计数排序

词频统计

  1. 基数排序(排序对象有进制)

  2. 看最大的数有几位

  3. 0-9,一共十个桶,按照个位数是多少进入桶

  4. 然后从左往右,依次倒出数字,先进先出FIFO

  5. 十位数的值是多少进入桶,然后出来,

  6. 百位。。。。。重复

为什么可以利用辅助数组和count数组可以实现FIFO

  1. 首先,我们计算出了原数组的某一个bit位的值,假设是十位的,并且我们把这个位置的词频统计出来了,如count[2] = 3;就代表,十位上是2的数有三个
  2. 然后,我们求出了前缀和数组,假如count[2] = 7;那么就代表十位上的数 <= 2的数有7个
  3. 然后呢!我们倒序遍历了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. 排序算法的总结

  1. 稳定性

多个相等的数排完序后,他们的相对顺序维持,这种排序就是稳定

  1. 总结
稳定性时间复杂度空间
选择NO(N^2)O(1)
冒泡YO(N^2)O(1)
插入YO(N^2)O(1)
归并YO(N*logN)O(N)
快排NO(N*logN)O(logN)
堆排NO(N*logN)O(1)
桶排序YO(N*logN)O(N)

本文由mdnice多平台发布