1. 什么是数据结构
数据结构是计算机存储,组织数据的方式。
-
线性结构:线性表,具有n个相同类型元素的有序列(n>=0)(数组,链表,栈,队列,哈希表)
-
树形结构:二叉树,AVL树,红黑树,B树,堆,Trie,哈夫曼树,并查集
-
图形结构:邻接矩阵,邻接表
在实际应用中,根据使用场景来选择最合适的数据结构
2. 什么是算法
算法是用来解决特定问题的一系列的执行步骤
//计算a+b的和
func plus(_ a:Int,_ b:Int) -> Int {
a+b
}
//计算1+2+3+4+5+...+n的和
func sum(n:Int) -> Int {
var result:Int = 0;
for _ in 0...n {
result += n
}
return result
}
- 使用不同的算法解决同一个问题,效率可能相差非常大
- 比如求第n个斐波那契数
3. 如何评价算法的好坏?
- 如果单从执行效率上进行评估,可能会想到这么一种方案
- 比较不同算法对同一组输入的执行处理时间。也叫事后统计法
- 执行时间会严重依赖于硬件以及运行时各种不确定的环境因素
- 必须编写相应的测算代码
- 测试数据的选择比较难保证公正性
- 所以一般以以下维度来评估算法的优劣
- 正确性,可读性,健壮性(对不合理输入的反应能力和处理能力)
- 时间复杂度:估算程序指令的执行次数(执行时间)
- 空间复杂度:估算所需占用的存储空间
3.1 大O表示法
一般用大O表示法来描述复杂度,它表示的是数据规模n对应的复杂度
- 忽略常数,系数,低阶,对数阶一般忽略底数
- 9 >> O(1)
- 2n+3 >> O(n)
- n^2+2n+6 >> O(n^2)
- 4n^3+3n^2+22n+100 >> O(n^3)
- log2n = log29 ∗ log9。所以 log2n 、log9n 统称为 logn。
- 注意,大O表示法仅仅是一种粗略的分析模型,是一种估算,能帮助我们短时间内了解一个算法的执行效率
3.2 常见的复杂度
执行次数 | 复杂度 | 非正式术语 |
---|---|---|
12 | O(1) | 常数阶 |
2n+3 | O(n) | 线性阶 |
4n^2 +2n+6 | O(n^2) | 平方阶 |
4log2^n + 25 | O(log^n) | 对数阶 |
3n + 2nlog3^n + 15 | O(nlog^n) | nlogn阶 |
4n^3 +3n2 +22n+100 | O(n^3) | 立方阶 |
2^n | O(2^n) | 指数阶 |
- O(1) < O(log^n) < O(n) < O(nlog^n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
- 可以借助函数生成工具对比复杂度的大小 zh.numberempire.com/graphingcal…
数据规模较小时
数据规模较大时
3.2 斐波那契数列fib1函数的时间复杂度分析
- 递归法
func fib1(_ index:Int64) -> Int64 {
if index <= 1 {
return index;
}
return fib1(index-1) + fib1(index-2)
}//时间复杂度O(2^n)
- 迭代法
func fib2(_ n:Int64) -> Int64 {
if n <= 1 {
return n
}
var first:Int64 = 0
var second:Int64 = 1
for _ in 0...n-2 {
let sum:Int64 = first + second
first = second
second = sum
}
return second
}//时间复杂度O(n)
- 代数公式法(并不是所有的算法都公式可以直接解决),时间复杂度O(1)
- 递归法和迭代法的差别有多大(n为64)
- 如果有一台1GHz的普通计算机,运算速度为10^9次每秒
- O(n)大约耗时6.4*10^-8秒
- O(2^n)大约耗时584.84年
- 有时候算法之间的差距,往往比硬件方面的差距还要大
4. 多个数据规模的情况下
func test(_ n:Int,_ k:Int){
for _ in 0...n {
print("test")
}
for _ in 0...k {
print("test")
}
}
复杂度O(n+k)
5. 算法的优化方向
- 用尽量少的存储空间
- 用尽量少的执行步骤(时间)
- 根据情况,可以:时间换空间,空间换时间
更多复杂度的知识
- 最好,最坏复杂度。
- 均摊复杂度。
- 复杂度震荡
- 平均复杂度 *......