1. 算法简介
算法: 一组完成任务的指令.
1.1. 二分查找算法:定位1-100之间的数字,不断的取数字区间的中位数来缩小目标数字区间
1. 简单查找:
1. 线性时间,数字结点:1,2,3...
2. 二分查找:
2. 对数时间,数字节点:50,75,88...
3. 对数: 幂的反运算
log2|16=4 代表:16=2*2*2*2
1.2. 大O表示法:用来比较操作数n,指出了算法运行时间的增速,注意并非以秒为时间单位.
1. 算法运行时间从其增速上考虑,用大O表示法表示.
| N | 100 | 10000 |
| :------------------ |--------:| :--: |
| 简单查找:O(n): | 100 | 10000 |
| 二分查找:O(logn) | 7 | 14 |
2^7= 128 > 100 2^14=16384 > 10000
2. 常见的大O运行时间
二分查找: O(logn)对数时间
简单查找: O(n)线性时间
快速排序: O(nlogn)divide and conquer
选择排序: O(n^2) (n+1)n/2
旅行商算法:O(n!) 后一次的排序基于前者的排序结果,多次排序之间是串联的关系
2. 选择排序
2.1 数组和链表
1. 内存的工作原理:计算机内存犹如一堆抽屉,向计算机请求内存,会分配到抽屉(内存单元)的地址.内存单元存储数据的物理形式是一组高低电位(没手动实现过都是大头理论..)
存储多项数据时的方式:数组和链表
2. 数组:数组元素的个数在初始化时就确定,其中的元素在内存中是相连的,每个元素的类型是相同的.数组元素的位置可以直接通过计算数组起始元素地址加上元素偏移位置就是每个元素的地址,所以数组的读取速度很快.数组插入和删除元素,必须将后面的元素都相应进行操作.
支持随机访问
3. 链表:链表中的元素是分开的,可以存储在内存的任何地方.链表元素存储相邻的元素地址,从而物理上可以分开存储,所以插入和删除速度很快.链表插入元素只需要修改它前边的元素指向的地址.
只能顺序访问,读取第10个元素,必须先访问前9个元素
链表元素: 当前元素地址|元素占用空间|下一元素地址
术语: 索引:元素的位置
| | Array | Link |
| :-----| ----- | ----- |
|read | O(1) | O(n) |
|insert | O(n) | O(1) |
|delete | O(n) | O(1) |
2.2 选择排序
假设对n个元素增序排序,则查找n个元素中的最大值,取出来;取出剩余n-1个元素中的最大值,取出剩余n-2个元素中的最大值...
需要的时间 n + (n-1) + (n-2) + ... + 1 = (n+1)n/2; 增长速率就是: O(n^2)
3. 递归
3.1 递归
1. 间接或直接调用函数自身的函数
2. 组成: 基线条件 base case:函数不再调用自身的条件
递归条件 recursive case:函数调用自己
3. 循环: 程序的性能可能会更高
递归: 逻辑更信息,程序更容易理解
倒计时函数:
def countdown(i):
print i
if i <= 1 : -- base case
return
else : -- recursive case
countdown(i-1)
3.2 栈
1. 只能在一端进行插入和删除的线性表,push/pop
2. 调用栈: 用于存储多个函数变量的栈
所有函数调用都进入调用栈
调用另一个函数时,当前函数暂停并处于未完成状态;.net 4.6 中的async和await关键字提供一种异步环境和插入点使得将方法作为返回结果代入下一步计算.
调用栈可能很长,将会占用大量内存
4. 快速排序
4.1 分而治之
1. D&C (divide and conquer) 分而治之: 解决问题的思路
2. 工作原理:找出基线条件; 涉及数组的递归函数,基线条件通常是数组为空或只包含一个元素.
缩小问题的规模,使其符合基线条件
4.2 快速排序
1. 数组中选择一个元素,被称为基准值
2. 分区: 找出所有比基准值大或小的数组组成子数组
3. 对两个子数组进行快速排序
def quicksort(array):
if len(array) < 2:
return array
else :
pivot = array[0]
less = [i for i in array[1:] if i <= pivot]
greater = [i for i in array[1:] if i > pivot]
return quicksort(less) + [pivot] + quicksort(greater)
print(quicksort([10,5,2,3]))
4.3 大O表示法
1. 常量: 算法所需要的固定的时间量,当操作数极大时,可以忽略
2. 平均情况:以快速排序为例,其性能高度依赖于基准值,基准值为中位数 O(logn)
3. 最糟情况:增序排列时,基准值为最大值 O(n)
5. 散列表
散列表:基本数据结构之一,根据键直接定位值,时间复杂度为O(1).
简单的以电话为例:alice 18768768768 存到一个长度为10的数组中.
将alice按照映射关系:字符对应的素数相加和除以散列表长度取余数,就是数组的索引位置
(2+37+23+5+11)%10=8
a[8] = 18768768768
a,b,c,d,e,f,g,h,i,j,k,l,m,n
2,3,5,7,11,13,17,19,23,29,31,37,41,43
散列表: 散列函数+数组
字符串-》散列函数-》索引-》数组【索引】=值
5.1 散列函数
1. 无论输入什么数据,都会返回一个字符串
2. 标准
a, 将同样的输入映射到相同的索引
b, 将不同的输入映射到不同的索引
c,根据数组的范围,返回有效的索引
5.2 应用
1. 查找,建立映射
phone_book = dict()
phone_book = {} -- = dict()
phone_book['alice'] = 123
phone_book['han'] = 187
print(phone_book['han'])
2. 防止重复
voted = {}
def check_voter(name) :
if voted.get(name) :
print('kick out')
else :
voted[name] = True
print(name + ' voted')
check_voter('alice')
check_voter('han')
check_voter('alice')
3. 用作缓存
缓存网站的静态页面: 主页,about,contact...避免服务器再次生成他们
DNS服务器
5.3 冲突
1. 冲突: 给两个键分配相同的位置
理想的情况是散列函数均匀的将键均匀的映射到散列表的不同位置
2. 性能, 避免冲突的标准
a. 填装因子: 散列表中的元素数/位置总数
经验: 一旦填装因子大于0.7,就调整散列表的长度
b. 良好的散列函数
字符对应的素数相加和除以散列表长度取余数,就是数组的索引位置
为解决重复的键,数组的元素存储链表
c. 散列表平均情况下的查询和插入,删除的性能都是常量时间