1.算法分析基础
算法分析是计算机科学中的一个重要领域,它涉及对算法效率和性能的分析和评估。这里的“算法”指的是解决问题的一系列明确指令,这些指令描述了从输入到输出的转换过程。算法可以是简单的,也可以是复杂的,它们是软件和硬件系统中的核心部分,用于执行各种任务,如数据处理、加密、图形渲染等。
在算法分析中,通常关注以下几个方面:
-
时间复杂度:衡量算法执行所需时间的量度。它通常用来评估算法随着输入规模增长时性能如何变化。时间复杂度通常用大O符号表示。
int sum(int n) {
int total = 0;
for (int i = 1; i <= n; i++) {
total += i; // 基本操作
}
return total;
}
上述代码的时间复杂度为O(n)。
-
空间复杂度:衡量算法执行过程中所需存储空间的量度。这包括算法本身占用的空间以及算法运行过程中临时占用的空间。
-
正确性:算法必须能够正确地解决问题,即在所有可能的输入上都能产生正确的输出。
-
可读性和简洁性:一个好的算法不仅要有效,还应该容易理解和实现。
-
健壮性:算法能够处理各种异常情况,而不会产生意外的结果或崩溃。
-
输入和输出:算法的输入是问题所需的初始数据,输出是算法的解决方案。
2.查找
2.1 顺序查找
将待查找的关键字为 key 的元素从头到尾与表中元素进行比较,如果中间存在关键字为 key 的元素,则返回成功;否则,则查找失败顺序查找的时间复杂度为:时间复杂度为 O(n)
int sequentialSearch(int[] array, int key) {
for (int i = 0; i < array.length; i++) {
if (array[i] == key) {
return i;
}
}
return -1; // 未找到
}
顺序查找的时间复杂度为O(n)。
2.2 折半二分查找
- 确保数组是有序的。
- 初始化两个指针,
low
指向数组的最低索引(通常是0),high
指向数组最高索引(通常是数组长度减1)。- 当
low
小于或等于high
时执行以下步骤:
- 计算中间索引
mid
=(low + high) / 2
。- 比较中间元素
array[mid]
与目标值target
:
- 如果
array[mid]
等于target
,则查找成功,返回mid
。- 如果
array[mid]
小于target
,则将low
设置为mid + 1
,因为目标值必定在中间元素的右侧。- 如果
array[mid]
大于target
,则将high
设置为mid - 1
,因为目标值必定在中间元素的左侧。- 如果
low
大于high
,则查找失败,返回-1或其他指示未找到的值。
int binarySearch(int[] sortedArray, int key) {
int left = 0;
int right = sortedArray.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (sortedArray[mid] == key) {
return mid;
} else if (sortedArray[mid] < key) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 未找到
}
3. 排序
排序是将一组数据按照特定顺序重新排列的过程。
3.1. 直接插入排序
直接插入排序通过构建有序序列,对新数据进行插入。
void insertionSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int key = array[i];
int j = i - 1;
while (j >= 0 && array[j] > key) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = key;
}
}
直接插入排序的时间复杂度为O(n^2)。
3.2. 希尔排序
希尔排序是直接插入排序的改进版,通过分组和缩小增量来提高效率。
3.3. 简单选择排序
简单选择排序通过反复寻找最小者来排序。
void selectionSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
int temp = array[i];
array[i] = array[minIndex];
array[minIndex] = temp;
}
}
}
简单选择排序的时间复杂度为O(n^2)。
3.4. 堆排序
堆排序使用二叉堆数据结构进行排序。
3.5. 冒泡排序
冒泡排序通过相邻元素的比较和交换来排序。
void bubbleSort(int[] array) {
boolean swapped;
for (int i = 0; i < array.length - 1; i++) {
swapped = false;
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
swapped = true;
}
}
if (!swapped) {
break;
}
}
}
冒泡排序的时间复杂度为O(n^2)。
3.6. 快速排序
快速排序通过分治法来对数据进行排序。
3.7. 归并排序
归并排序通过合并有序子数组来对数据进行排序。
3.8. 基数排序
基数排序是按照数字的每一位进行排序的方法。
4. 常用算法原理
4.1. 分治法
分治法是将问题分解为若干个规模较小的子问题,递归解决子问题后合并结果。
最经典的例子是归并排序(Merge Sort)和快速排序(Quick Sort)。
4.2. 回溯法
回溯法是一种深度优先搜索算法,通过逐个探索所有可能的候选解来找到问题的解。
- 排列组合问题:如全排列、组合问题等。
- 棋盘问题:如八皇后问题、骑士巡游问题等。
- 图论问题:如旅行商问题(TSP)、哈密顿回路问题等。
- 最优化问题:如0-1背包问题、最小生成树问题等。
4.3. 动态规划法
动态规划法通过将问题分解为重叠的子问题,并存储子问题的解(通常使用表格)来避免重复计算。
-
最短路径问题:如迪杰斯特拉算法(Dijkstra’s Algorithm)和贝尔曼-福特算法(Bellman-Ford Algorithm)
-
背包问题:如0-1背包问题、完全背包问题、多重背包问题等。
-
序列对齐问题:如编辑距离(Edit Distance)
-
最长公共子序列(Longest Common Subsequence, LCS)
-
最优控制问题
-
生物信息学:如DNA序列比对
4.4. 贪心法
贪心法在每一步选择中都采取当前状态下最好或最优的选择,从而希望导致结果是最好或最优的算法。
-
活动选择问题:如最小化活动的总时间。
-
图论问题:如最小生成树、最短路径等。
-
背包问题:如0-1背包问题、分数背包问题等。
-
序列问题:如最长公共子序列、最长递增子序列等。
4.5. 分支限界法
分支限界法是一种在问题的解空间树上搜索问题解的算法,通过限界技术剪枝来减少搜索范围。
分支限界法通常使用广度优先搜索策略,即先检查所有子节点,然后再检查孙节点,依此类推。
-
旅行商问题(TSP)
-
作业调度问题
-
八皇后问题
-
其他优化问题
4.6. 概率算法
概率算法在算法执行过程中涉及随机选择,可能得到不完全准确但足够好的解。
4.7. 近似算法
近似算法放弃寻找最优解,而是寻找一个接近最优解的解,以换取时间和复杂度上的降低。
4.8. 数据挖掘算法
数据挖掘算法分析大量数据,以发现有价值的信息和知识。
4.9. 智能优化算法
智能优化算法模仿自然界的优化过程,用于求解各种工程问题的优化解。