算法与数据结构学习笔记
算法与数据结构核心学习笔记
一、时间复杂度分析
1. 对数型嵌套循环
count = 0;
for(k=1 ; k <= n ;k*=2){
for(j=1 ; j<n ; j++){
操作
}
}
详细分析:
- 外层循环:变量 k 从 1 开始,每次乘以 2,直到超过 n。执行次数为 log₂n 次。
- 内层循环:变量 j 从 1 递增到 n-1,每次执行固定 n-1 次,与外层 k 无关。
- 总执行次数:log₂n × (n-1) ≈ n log n。
- 结论:时间复杂度为 O(n log n)。
2. 双层嵌套循环
def fun(lists):
sum = 0
for i in range(len(lists)):
tmp = lists[i]
for j in range(len(lists)):
sum += tmp
return sum
print(fun(数组))
详细分析:
- 外层循环:遍历整个数组,执行 len(lists) 次,记为 n。
- 内层循环:对于外层的每一次迭代,内层都完整遍历一次数组,执行 n 次。
- 总执行次数:n × n = n²。
- 结论:时间复杂度为 O(n²)。
3. 递归函数
void foo(int n, int x, int y){
int z = 0;
if(n <= 0){
z = x + y;
}else{
foo(n-1, x+1, y);
foo(n-1, x, y+1);
}
}
详细分析:
-
递归结构:每次调用产生 2 个新的递归调用,递归深度为 n。
-
调用树形态:这是一棵满二叉树。
-
节点总数计算:第 0 层 1 个,第 1 层 2 个,…,第 n 层 2ⁿ 个。
Total = 2⁰ + 2¹ + 2² + … + 2ⁿ = 2^(n+1) - 1
-
结论:时间复杂度为 O(2ⁿ)。
4. 分析方法总结
| 场景 | 分析策略 | 关键点 |
|---|---|---|
| 递归 | 递归调用树分析 | 计算总调用次数或递归深度与分支数的关系 |
| 嵌套循环 | 乘积法则 | 总次数 = 外层次数 × 内层次数 |
| 内层执行次数和 | 累加求和 | 若内层次数随外层变化,需计算 Σ |
| 单层循环 | 直接计数 | 关注循环变量的变化步长 |
二、十大排序算法详解
1. 冒泡排序
- 核心思想:重复遍历数组,比较相邻元素,若顺序错误则交换,使较大元素逐步"冒泡"到末尾。
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 稳定性:稳定
2. 选择排序
- 核心思想:将数组分为已排序和未排序两部分,每次从未排序部分选择极值(最小/最大),追加到已排序部分末尾。
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 稳定性:不稳定(交换操作可能改变相同元素的相对位置)
3. 插入排序
- 核心思想:将数组分为已排序和未排序两部分,从未排序部分取值,在已排序部分找到正确位置并插入。
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 稳定性:稳定
4. 希尔排序
- 核心思想:插入排序的改进版(缩小增量排序)。设定间隔 gap 将数组分组,对每组进行插入排序,逐步缩小 gap,最后 gap=1 时完成整体排序。
- 时间复杂度:O(n log n) ~ O(n²)(取决于间隔序列的选择)
- 空间复杂度:O(1)
- 稳定性:不稳定
5. 计数排序
- 核心思想:非比较排序。统计每个元素出现的次数,根据计数结果重建有序数组。
- 时间复杂度:O(n + k),k 为数据范围
- 空间复杂度:O(k)
- 稳定性:稳定
- 适用场景:数据范围较小的整数排序
6. 基数排序
- 核心思想:非比较排序。按位(个位、十位、百位…)依次对数据进行排序,通常结合计数排序实现。
- 时间复杂度:O(n × k),k 为数字位数
- 空间复杂度:O(n + k)
- 稳定性:稳定
- 适用场景:固定位数的整数排序
7. 归并排序
- 核心思想:分治法。将数组递归地分成两半分别排序,然后合并两个有序子数组。
- 时间复杂度:O(n log n)
- 空间复杂度:O(n)(需要辅助数组)
- 稳定性:稳定
Java 完整实现
package cn.wolfcode;
import java.util.Arrays;
public class GuiBingSorted {
public static void merge(int[] arr, int left, int mid, int right, int[] temp){
int i = 0;
int j = left;
int k = mid + 1;
// 比较左右两部分,按序放入临时数组
while(j <= mid && k <= right){
if (arr[j] < arr[k]){
temp[i++] = arr[j++];
}else {
temp[i++] = arr[k++];
}
}
// 左侧有剩余,直接追加
while (j <= mid){
temp[i++] = arr[j++];
}
// 右侧有剩余,直接追加
while (k <= right){
temp[i++] = arr[k++];
}
// 将临时数组数据写回源数组
for (int t = 0; t < i; t++) {
arr[left + t] = temp[t];
}
}
public static void mergeSort(int[] arr, int left, int right, int[] temp){
if (left < right){
int mid = (left + right) / 2;
// 左侧归并
mergeSort(arr, left, mid, temp);
// 右侧归并
mergeSort(arr, mid + 1, right, temp);
// 合并
merge(arr, left, mid, right, temp);
}
}
public static void main(String[] args) {
int[] arr = {28, 12, 19, 50, 93, 66, 60, 58, 54, 79};
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, temp);
System.out.println(Arrays.toString(arr));
}
}
8. 堆排序
- 核心思想:利用二叉堆(大顶堆/小顶堆)数据结构。将数组构造成堆,反复取出堆顶元素(最大/最小)并重建堆。
- 时间复杂度:O(n log n)
- 空间复杂度:O(1)
- 稳定性:不稳定
9. 快速排序
- 核心思想:分治法。选择一个基准元素(pivot),将数组分为小于基准和大于基准的两部分,递归排序。
- 时间复杂度:平均 O(n log n),最坏 O(n²)
- 空间复杂度:O(log n)(递归栈空间)
- 稳定性:不稳定
Java 完整实现
package cn.wolfcode;
import java.util.Arrays;
public class QuickSort {
public static void quickSort(int[] arr, int left, int right) {
int i, j, temp, t;
if (left > right){
return;
}
i = left;
j = right;
// 基准点
temp = arr[left];
while (i < j){
// 先推动右侧指针向左侧移动,找小于基准的
while (temp <= arr[j] && i < j){
j--;
}
// 推动左侧指针向右侧移动,找大于基准的
while (temp >= arr[i] && i < j){
i++;
}
// 如果两个指针没有重合,交换
if(i < j){
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
// 双指针重合,将基准值放到正确位置
arr[left] = arr[i];
arr[i] = temp;
// 左侧快排
quickSort(arr, left, i - 1);
// 右侧快排
quickSort(arr, i + 1, right);
}
public static void main(String[] args) {
int[] arr = {28, 12, 19, 50, 93, 66, 60, 58, 54, 79};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
}
10. 桶排序
- 核心思想:将数组元素分配到有限数量的"桶"中,每个桶再单独排序(可用其他排序算法),最后按顺序收集桶中元素。
- 时间复杂度:O(n + k),k 为桶的数量
- 空间复杂度:O(n + k)
- 稳定性:稳定
- 适用场景:数据均匀分布的浮点数或整数
三、十大排序算法对比总结
| 排序算法 | 时间复杂度 (最好/平均/最坏) | 空间复杂度 | 稳定性 | 排序方式 |
|---|---|---|---|---|
| 冒泡排序 | O(n) / O(n²) / O(n²) | O(1) | 稳定 | 比较 |
| 选择排序 | O(n²) / O(n²) / O(n²) | O(1) | 不稳定 | 比较 |
| 插入排序 | O(n) / O(n²) / O(n²) | O(1) | 稳定 | 比较 |
| 希尔排序 | O(n log n) / O(n^1.3) / O(n²) | O(1) | 不稳定 | 比较 |
| 归并排序 | O(n log n) / O(n log n) / O(n log n) | O(n) | 稳定 | 比较 |
| 快速排序 | O(n log n) / O(n log n) / O(n²) | O(log n) | 不稳定 | 比较 |
| 堆排序 | O(n log n) / O(n log n) / O(n log n) | O(1) | 不稳定 | 比较 |
| 计数排序 | O(n+k) / O(n+k) / O(n+k) | O(k) | 稳定 | 非比较 |
| 桶排序 | O(n+k) / O(n+k) / O(n²) | O(n+k) | 稳定 | 非比较 |
| 基数排序 | O(n·k) / O(n·k) / O(n·k) | O(n+k) | 稳定 | 非比较 |
四、时间复杂度速查表
| 复杂度量级 | 名称 | 典型算法/场景 |
|---|---|---|
| O(1) | 常数阶 | 哈希表查找、数组下标访问、简单赋值 |
| O(log n) | 对数阶 | 二分查找、平衡二叉搜索树操作 |
| O(n) | 线性阶 | 线性扫描、计数排序、桶排序 |
| O(n log n) | 线性对数阶 | 归并排序、快速排序(平均)、堆排序 |
| O(n²) | 平方阶 | 冒泡排序、选择排序、插入排序、双层嵌套循环 |
| O(n³) | 立方阶 | Floyd-Warshall 算法、三层嵌套循环 |
| O(2ⁿ) | 指数阶 | 暴力递归(如斐波那契递归)、子集枚举 |
| O(n!) | 阶乘阶 | 旅行商问题(TSP)暴力解、全排列生成 |