时间复杂度和空间复杂度分析
时间复杂度
时间复杂度衡量的是算法执行所需时间随着输入规模(n)变化的增长速度。通常我们通过分析算法的执行步骤来确定时间复杂度。
常见时间复杂度:
- 常数时间
O(1):不随输入规模的增大而变化。 - 线性时间
O(n):与输入规模成正比。 - 对数时间
O(log n):例如二分查找。 - 平方时间
O(n^2):例如冒泡排序。 - 立方时间
O(n^3):例如一些动态规划算法。 - 指数时间
O(2^n):例如某些递归算法,可能会导致暴力求解。
分析步骤:
- 基本操作:确定在算法中每一步基本操作的执行次数。
- 循环 :分析循环结构(
for,while等)。如果一个循环执行n次,那么它的时间复杂度是O(n)。如果有嵌套循环,比如一个for循环里面还有一个for循环,每个循环执行n次,那么时间复杂度就是O(n^2)。 - 递归 :递归算法的时间复杂度可以通过递推公式来计算,常用的计算方法有递推树和主定理。例如,快速排序的时间复杂度通常是
O(n log n),而归并排序也是O(n log n)。
空间复杂度
空间复杂度衡量的是算法在执行过程中所需的额外空间(除去输入数据占用的空间)。它通常与算法使用的辅助空间(例如数组、哈希表、栈等)有关。
常见空间复杂度:
- 常数空间
O(1):算法只需要使用常量大小的空间 - 线性空间
O(n):算法使用的空间与输入数据的大小成正比 - 对数空间
O(log n):比如在递归算法中,递归深度为log n时
分析步骤:
- 输入空间:输入的大小本身会占用空间,因此有时会被忽略,特别是在考虑空间复杂度时。
- 变量和数据结构 :查看算法中使用的所有额外数据结构。比如一个数组或链表大小为
n,那么它占用的空间是O(n)。 - 递归 :递归调用通常会增加函数调用栈的空间,因此递归的空间复杂度有时会比非递归的算法更高。
- 比如递归树的深度是
n时,空间复杂度可能是O(n)
- 比如递归树的深度是
实例分析
示例 1:线性查找
java
public class LinearSearch {
public static int linearSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 9};
int target = 5;
System.out.println(linearSearch(arr, target)); // 输出:2
}
}
时间复杂度:
- 时间复杂度分析 :
线性查找需要遍历整个数组,最坏情况下要遍历n次,因此时间复杂度是O(n)。
空间复杂度:
- 空间复杂度分析 :
只使用了常数空间i和target,因此空间复杂度是O(1)。
示例 2:二分查找
java
public class BinarySearch {
public static int binarySearch(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 9};
int target = 5;
System.out.println(binarySearch(arr, target)); // 输出:2
}
}
时间复杂度:
- 时间复杂度分析 :
二分查找将问题规模每次减半,因此时间复杂度是O(log n)。
空间复杂度:
- 空间复杂度分析 :
只使用了常数空间left,right,mid,因此空间复杂度是O(1)。
示例 3:冒泡排序
java
public class BubbleSort {
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换位置
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
public static void main(String[] args) {
int[] arr = {5, 2, 9, 1, 5, 6};
bubbleSort(arr);
for (int num : arr) {
System.out.print(num + " "); // 输出:1 2 5 5 6 9
}
}
}
时间复杂度:
- 时间复杂度分析 :
外层循环执行n次,内层循环执行n-1,n-2, ..., 1 次,因此总时间复杂度是O(n^2)。
空间复杂度:
- 空间复杂度分析 :
只使用了常数空间temp来交换数组元素,因此空间复杂度是O(1)。
示例 4:快速排序
java
public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
public static void main(String[] args) {
int[] arr = {10, 7, 8, 9, 1, 5};
quickSort(arr, 0, arr.length - 1);
for (int num : arr) {
System.out.print(num + " "); // 输出:1 5 7 8 9 10
}
}
}
时间复杂度:
- 时间复杂度分析 :
快速排序每次将数组分成两部分,平均情况下时间复杂度是O(n log n)。最坏情况下(数组已经有序时)是O(n^2)。
空间复杂度:
- 空间复杂度分析 :
快速排序需要递归调用,因此空间复杂度与递归深度成正比,最坏情况下是O(n),平均情况下是O(log n)。
示例 5:合并排序
java
public class MergeSort {
public static void mergeSort(int[] arr) {
if (arr.length < 2) return;
int mid = arr.length / 2;
int[] left = new int[mid];
int[] right = new int[arr.length - mid];
System.arraycopy(arr, 0, left, 0, mid);
System.arraycopy(arr, mid, right, 0, arr.length - mid);
mergeSort(left);
mergeSort(right);
merge(arr, left, right);
}
private static void merge(int[] arr, int[] left, int[] right) {
int i = 0, j = 0, k = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
arr[k++] = left[i++];
} else {
arr[k++] = right[j++];
}
}
while (i < left.length) arr[k++] = left[i++];
while (j < right.length) arr[k++] = right[j++];
}
public static void main(String[] args) {
int[] arr = {12, 11, 13, 5, 6, 7};
mergeSort(arr);
for (int num : arr) {
System.out.print(num + " "); // 输出:5 6 7 11 12 13
}
}
}
时间复杂度:
- 时间复杂度分析 :
合并排序每次将数组分成两半,并进行合并,时间复杂度是O(n log n)。
空间复杂度:
- 空间复杂度分析 :
合并排序需要额外的数组存储left和right,因此空间复杂度是O(n)。
示例 6:矩阵乘法
java
public class MatrixMultiplication {
public static int[][] matrixMultiply(int[][] A, int[][] B) {
int n = A.length;
int m = B[0].length;
int p = A[0].length;
int[][] result = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < p; k++) {
result[i][j] += A[i][k] * B[k][j];
}
}
}
return result;
}
public static void main(String[] args) {
int[][] A = {{1, 2}, {3, 4}};
int[][] B = {{5, 6}, {7, 8}};
int[][] result = matrixMultiply(A, B);
for (int[] row : result) {
for (int val : row) {
System.out.print(val + " "); // 输出:19 22 43 50
}
System.out.println();
}
}
}
时间复杂度:
- 时间复杂度分析 :
矩阵乘法需要三重循环,时间复杂度是O(n * m * p),如果矩阵是n x n的话,时间复杂度是O(n^3)。
空间复杂度:
- 空间复杂度分析 :
需要一个n x m的结果矩阵,因此空间复杂度是O(n * m)。
总结:
每个算法的时间复杂度和空间复杂度都依赖于其结构和操作方式,Java 示例中的复杂度分析与之前的解释是相同的。你可以根据这些示例来分析不同算法的性能特点。