算法笔记——左神初级(1)冒泡、选择、插入、归并排序

时间复杂度

常数时间的操作:一个操作如果和数据量没有关系,每次都是 固定时间内完成的操作,叫做常数操作

时间复杂度为一个算法流程中,常数操作数量的指标。常用O (读作big O)来表示。具体来说,在常数操作数量的表达式中, 只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分 如果记为f(N),那么时间复杂度为O(f(N))。

评价一个算法流程的好坏,先看时间复杂度的指标,然后再分 析不同数据样本下的实际运行时间,也就是常数项时间。

额外空间复杂度

算法运行中需要申请几个变量,然后算法即可完成,额外空间复杂度为O(1)。
算法运行中需要额外申请的空间,跟原来数组长度一样或者为原来数组长度一半那空间复杂度都为O(N)。

冒泡排序

其实就是从首位开始两两比较,把大的数往后移,移到最后一位之后,再在前N-1个数上重复此操作。

import java.util.Arrays;
public class Code_00_BubbleSort {
	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;
	}

时间复杂度可以观察for循环,等差数列,所以可以得知时间复杂度为O(N^2)。

选择排序

import java.util.Arrays;

public class Code_02_SelectionSort {

	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++) {
				minIndex = arr[j] < arr[minIndex] ? j : minIndex;
			}
			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;
	}

核心在于寻找最小数,0至N-1上最小数与0位置上数交换,1至N-1上最小的数与1位置上数进行交换,……以此类推。 核心与冒泡排序类似,所以时间复杂度也为O(N^2)。

插入排序

import java.util.Arrays;

public class Code_01_InsertionSort {

	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) {
		arr[i] = arr[i] ^ arr[j];
		arr[j] = arr[i] ^ arr[j];
		arr[i] = arr[i] ^ arr[j];
	}

核心思想类似于扑克牌排列,前1个,前2个,……前N个内部排列。

这里涉及一个重要概念 数据状况:最好情况、最差情况、平均情况

对于冒泡排序和选择排序而言,都是执行严格的O(N^2)复杂度,和数据状况无关。
如果排好序,复杂度仅为O(N)。

但在情况不确定情况下,一律按最差情况算复杂度,所以插入排序O(N^2)

递归算法

递归算法实际上是系统帮你压栈

剖析递归行为和递归行为时间复杂度的估算

算法笔记——左神初级(1)冒泡、选择、插入、归并排序_第1张图片

其中,a为子过程发生的次数、N/b为子过程的样本量(b为类数)、O(N^d)是指除子过程之外剩下过程的时间复杂度


归并排序

核心思想:左侧部分排好顺序,右侧部分排好顺序,再设立一个辅助数组,再整体外排。

import java.util.Arrays;

public class Code_05_MergeSort {

	public static void mergeSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		mergeSort(arr, 0, arr.length - 1);
	}

	public static void mergeSort(int[] arr, int l, int r) {
		if (l == r) {
			return;
		}
		int mid = l + ((r - l) >> 1);  //l和r中点的位置(l+r)/2  这样写是为了防止l+r溢出
		mergeSort(arr, l, mid);//T(N/2)
		mergeSort(arr, mid + 1, r);//T(N/2)
		merge(arr, l, mid, r);//O(N)
		
	}

	public static void merge(int[] arr, int l, int m, int r) {
		int[] help = new int[r - l + 1];
		int i = 0;
		int p1 = l;
		int p2 = m + 1;
		//有且只有一个会越界,代表其中一个数组找完
		while (p1 <= m && p2 <= r) {
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		while (p1 <= m) {
			help[i++] = arr[p1++];
		}
		while (p2 <= r) {
			help[i++] = arr[p2++];
		}
		//此时help数组是排完的,回填到arr数组中即可
		for (i = 0; i < help.length; i++) {
			arr[l + i] = help[i];
		}
	}

mid = L + ((R - L) >> 1); L和R中点的位置(L+R)/2 这样写是为了防止L+R溢出,且位运算比算术运算快
所以由上可知,时间复杂度为 O(N*logN), 额外空间复杂度O(N),因为最多只需要生成一个长度为N的辅助空间用来存help。

归并排序比冒泡、插入排序比较快的原因,是因为小组合成大组的过程中,小组内本身排好的顺序被利用了起来。


小和问题

算法笔记——左神初级(1)冒泡、选择、插入、归并排序_第2张图片
核心用merge函数来合并并提取小和。

public class Code_12_SmallSum {

	public static int smallSum(int[] arr) {
		if (arr == null || arr.length < 2) {
			return 0;
		}
		return mergeSort(arr, 0, arr.length - 1);
	}

	public static int mergeSort(int[] arr, int l, int r) {
		if (l == r) {
			return 0;
		}
		int mid = l + ((r - l) >> 1);
		return mergeSort(arr, l, mid) + mergeSort(arr, mid + 1, r) + merge(arr, l, mid, r);
		//关键思路:左侧小和+右侧小和+左右两侧合在一起的小和
	}

	public static int merge(int[] arr, int l, int m, int r) {
		int[] help = new int[r - l + 1];
		int i = 0;
		int p1 = l;
		int p2 = m + 1;
		int res = 0;
		while (p1 <= m && p2 <= r) {
			res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
			//求小和的操作,如果p1比p2小,榨出的小和就是从p2到r的个数乘当前这个小的数p1
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		while (p1 <= m) {
			help[i++] = arr[p1++];
		}
		while (p2 <= r) {
			help[i++] = arr[p2++];
		}
		for (i = 0; i < help.length; i++) {
			arr[l + i] = help[i];
		}
		return res;
	}

由上可以看出,小和问题思路就是在归并排序的基础上,加入了求小和的步骤,把左边较小的数,乘上右边比其大的个数。


对数器

算法笔记——左神初级(1)冒泡、选择、插入、归并排序_第3张图片
下方的代码,是随机样本产生器的一种例子:

// test 
public static void rightMathod(int [] arr){
	Arrays.sort(arr);
}
//test
public static int[] generateRandomArray(int size,int value){
	//math.random()->double[0,1)
	//(int)((size+1)*Math.random())->[0,size]整数
	//size = 6,size+1 = 7;
	//Math.random()->[0,1)*7->[0,7)double
	//double-> int [0,6] ->int
	
	//生成长度随机的数组
	int[] arr = new int[(int)((size+1)*Math.random());
	for(int i = 0;i<arr.length;i++){
		arr[i] = (int)((value+1)*Math.random())-(int)(value*Math.random());
	}
	return arr;
}
// for test
	public static int[] copyArray(int[] arr) {
		if (arr == null) {
			return null;
		}
		int[] res = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			res[i] = arr[i];
		}
		return res;
	}

	// for test
	public static boolean isEqual(int[] arr1, int[] arr2) {
		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
			return false;
		}
		if (arr1 == null && arr2 == null) {
			return true;
		}
		if (arr1.length != arr2.length) {
			return false;
		}
		for (int i = 0; i < arr1.length; i++) {
			if (arr1[i] != arr2[i]) {
				return false;
			}
		}
		return true;
	}

	// for test
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	// for test
	public static void main(String[] args) {
		int testTime = 500000;
		int maxSize = 100;
		int maxValue = 100;
		boolean succeed = true;
		for (int i = 0; i < testTime; i++) {
			int[] arr1 = generateRandomArray(maxSize, maxValue);
			int[] arr2 = copyArray(arr1);
			insertionSort(arr1);
			comparator(arr2);
			if (!isEqual(arr1, arr2)) {
				succeed = false;
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Fucking fucked!");

		int[] arr = generateRandomArray(maxSize, maxValue);
		printArray(arr);
		insertionSort(arr);
		printArray(arr);
	}

}

所以笔试前需要准备一些对数器模板,包括数组、二叉树等。

你可能感兴趣的:(Java)