目录
一、概念
算法中的基本操作语句的重复执行次数是问题规模 n 的某个函数,用 T(n) 表示(T(n) 不同,时间复杂度可能相同
时间复杂度是算法运行时的时间消耗,通常用大 O 表示(忽略常数项,忽略低次项、忽略系数)
二、常见的时间复杂度
- O(1):常数时间复杂度,表示算法的执行时间是固定的,与输入规模无关。例如查找哈希表中的元素、数组访问等。
- O(log n):对数时间复杂度,通常用于二分法查找等分治算法。例如查找排序数组中的元素。
- O(n):线性时间复杂度,表示算法的执行时间与输入规模成线性比例增长。例如遍历数组、链表等。
- O(n log n):快速排序和归并排序的时间复杂度,此类算法的执行时间与输入规模呈线性对数比例增长。
- O(n^2):平方时间复杂度,表示算法的执行时间与输入规模的平方成比例增长。例如使用嵌套循环对数组进行排序。
- O(2^n):执行时间呈指数级增长,通常在问题规模较大时会变得非常耗时
三、示例
O(1)
public void demo1(int n) {
int i = 1;
int j = 2;
int k = i * j;
}
public void demo2() {
for (int i = 0; i < 100 ; i++) {
int i = 1;
int j = 2;
int k = i * j;
}
}
O(log n)
//数组需有序
public static int binarySearch(int[] array, int target) {
int low = 0;
int high = array.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (array[mid] == target) {
return mid;
} else if (array[mid] < target) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1; // 目标元素不存在
}
O(n)
public void demo(int n){
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
for (int i = 0; i < 2 * n; i++) {
sum += i;
}
for (int i = 0; i < 10; i++) {
sum += i;
}
}
//复杂度 O(m + n)
public void demo(int m, int n){
int sum = 0;
for (int i = 0; i < m; i++) {
sum += i;
}
for (int i = 0; i < n; i++) {
sum += i;
}
for (int i = 0; i < 10; i++) {
sum += i;
}
}
O(n log n)
/**
* 归并排序的关键操作是合并两个有序数组,这部分的时间复杂度为O(n)。
* 而每次递归调用mergeSort时,数组长度减半,因此总共需要log n次递归调用。
* 所以,归并排序的时间复杂度为O(nlogn)。
*/
public class MergeSort {
public static void mergeSort(int[] array) {
if (array.length <= 1) {
return;
}
int mid = array.length / 2;
int[] left = new int[mid];
int[] right = new int[array.length - mid];
// 将原始数组拆分为左右两个子数组
System.arraycopy(array, 0, left, 0, left.length);
System.arraycopy(array, mid, right, 0, right.length);
// 递归地对左右子数组进行排序
mergeSort(left);
mergeSort(right);
// 合并左右子数组
merge(left, right, array);
}
private static void merge(int[] left, int[] right, int[] merged) {
int i = 0; // 左子数组索引
int j = 0; // 右子数组索引
int k = 0; // 合并后数组索引
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
merged[k++] = left[i++];
} else {
merged[k++] = right[j++];
}
}
while (i < left.length) {
merged[k++] = left[i++];
}
while (j < right.length) {
merged[k++] = right[j++];
}
}
}
O(n^2)
/**
* 在最坏情况下,当数组是逆序排列时,插入排序需要比较和移动元素的次数达到最大值。
* 对于长度为n的数组,第i个元素需要与前i-1个元素进行比较和移动操作,因此总共需要进行1+2+3+...+(n-1)次操作,
* 这是一个等差数列,求和后结果为(n-1) * n / 2。 所以,插入排序的时间复杂度为O(n^2)
*/
public class InsertionSort {
public static void insertionSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int current = array[i];
int j = i - 1;
while (j >= 0 && array[j] > current) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = current;
}
}
}
O(2^n)
// 每次递归调用都会产生两个子问题,因此递归树会呈指数级别增长
public class Fibonacci {
public static void main(String[] args) {
int n = 10; // 求斐波那契数列的前n项
for (int i = 0; i < n; i++) {
System.out.print(fibonacci(i) + " ");
}
}
/**
* 递归实现斐波那契数列
* @param n 斐波那契数列的第n项
* @return 斐波那契数列的第n项的值
*/
public static int fibonacci(int n) {
if (n == 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
}
四、对比
O(1)<O(log n)<O(n)<O(n log n)<O(n^2)<O(2^n)