常数时间的操作:一个操作如果和数据量没有关系,每次都是 固定时间内完成的操作,叫做常数操作。
时间复杂度为一个算法流程中,常数操作数量的指标。常用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)。
递归算法实际上是系统帮你压栈
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。
归并排序比冒泡、插入排序比较快的原因,是因为小组合成大组的过程中,小组内本身排好的顺序被利用了起来。
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;
}
由上可以看出,小和问题思路就是在归并排序的基础上,加入了求小和的步骤,把左边较小的数,乘上右边比其大的个数。
// 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);
}
}
所以笔试前需要准备一些对数器模板,包括数组、二叉树等。