常见时间复杂度
O(1) - 常数时间复杂度
定义:算法的运行时间与输入规模无关,始终是一个常数。
示例:访问数组的某个元素。
int getElement(int arr[], int index) {
return arr[index];
}
解释:无论数组有多大,访问数组中的任何一个元素所需的时间都是固定的。这种操作的时间复杂度是 O(1)。
具体分析:
- 输入规模:数组
arr
和索引index
。 - 操作数:只需一次查找和返回操作,无论数组长度。
- 应用场景:获取数组中的某个元素、执行简单的算术运算(如加法、乘法)、赋值操作。
O(log n) - 对数时间复杂度
定义:算法的运行时间与输入规模的对数成正比,常见于“每次将问题规模减半”的算法。
示例:二分查找。
int binarySearch(int arr[], int size, int target) {
int left = 0, right = size - 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;
}
解释:在有序数组中查找一个元素,每次将搜索范围减半,时间复杂度是 O(log n)。
具体分析:
- 输入规模:数组
arr
的长度size
和目标值target
。 - 操作数:每次比较后,将搜索范围缩小一半,直到找到目标或范围为空。
- 应用场景:二分查找、平衡二叉搜索树的查找操作。
O(n) - 线性时间复杂度
定义:算法的运行时间与输入规模成正比,通常是简单的遍历操作。
示例:找到数组中的最大值。
int findMax(int arr[], int size) {
int max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
解释:需要遍历整个数组,随着数组长度的增加,运行时间也线性增加,因此时间复杂度是 O(n)。
具体分析:
- 输入规模:数组
arr
的长度size
。 - 操作数:遍历数组中的每个元素,共
n
次比较操作。 - 应用场景:遍历数组、线性搜索、统计数组中的元素个数。
O(n log n) - 线性对数时间复杂度
定义:算法的运行时间与输入规模的乘积成正比,其中一个因子是对数。
示例:归并排序。
void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int L[n1], R[n2];
for (int i = 0; i < n1; i++)
L[i] = arr[left + i];
for (int j = 0; j < n2; j++)
R[j] = arr[mid + 1 + j];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
解释:归并排序将数组分成两半分别排序并合并,每个层级的合并操作是 O(n),而分层级数是 O(log n),总时间复杂度是 O(n log n)。
具体分析:
- 输入规模:数组
arr
的长度n
。 - 操作数:分治递归,每层级进行 n 次合并操作,共 log n 层级。
- 应用场景:高效排序算法,如归并排序、快速排序(平均情况)。
O(n^2) - 平方时间复杂度
定义:算法的运行时间与输入规模的平方成正比,通常是嵌套循环。
示例:冒泡排序。
void bubbleSort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
解释:每次都需要比较和交换未排序部分的所有元素,内外两个循环都遍历数组,运行时间随着输入规模的平方增长,因此时间复杂度是 O(n^2)。
具体分析:
- 输入规模:数组
arr
的长度n
。 - 操作数:内外两个循环,每次进行比较和交换,共 n*(n-1)/2 次操作。
- 应用场景:简单排序算法,如冒泡排序、选择排序、插入排序。
O(2^n) - 指数时间复杂度
定义:算法的运行时间与输入规模的指数成正比,通常是递归算法。
示例:斐波那契数列的递归计算。
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
解释:每次计算 fibonacci(n) 都需要计算 fibonacci(n-1) 和 fibonacci(n-2),随着 n 的增大,计算次数呈指数增长,因此时间复杂度是 O(2^n)。
具体分析:
- 输入规模:整数
n
。 - 操作数:每次递归调用都会分裂成两个递归调用,形成一棵二叉树,共 2^n 次调用。
- 应用场景:递归算法,如斐波那契数列计算、汉诺塔问题。
O(n!) - 阶乘时间复杂度
定义:算法的运行时间与输入规模的阶乘成正比,通常是排列组合问题。
示例:排列组合生成。
void permute(int arr[], int l, int r) {
if (l == r) {
// 输出排列
} else {
for (int i = l; i <= r; i++) {
swap(&arr[l], &arr[i]);
permute(arr, l + 1, r);
swap(&arr[l], &arr[i]); // 回溯
}
}
}
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
解释:每次生成一个排列时,都要考虑每个元素的位置,排列组合的数量是 n!,因此时间复杂度是 O(n!)。
具体分析:
- 输入规模:数组
arr
的长度n
。 - 操作数:每次递归都需要交换和回溯,共 n! 次操作。
- 应用场景:排列组合问题、旅行商问题(暴力求解)。