算法

什么是算法

算法 (Algorithm): 一个计算过程, 解决问题的方法

Niklaus Wirth: "程序=数据结构+算法"

时间复杂度

详情

⽤用来评估算法运⾏时间或者运行效率的一个式⼦

一般来说, 时间复杂度高的算法比时间复杂度低的算法慢

常见的时间复杂度排序(按效率排序)

O(1)<O(logn)<O(n)<O(nlogn)<o(n2)<O(n2logn)<O(n^3)

第一类

  • 对于一个循环,假设循环体的时间复杂度为 O(n),循环次数为 m,则这个
    循环的时间复杂度为 O(n×m)
  1. O(1)
print('Hello World')
print('Hello World')
print('Hello lxx')
print('Hello lyy')
  1. O(n)
for i in range(n):
    print('Hello World')

第二类

  • 对于多个循环,假设循环体的时间复杂度为 O(n),各个循环的循环次数分别是a, b, c...,则这个循环的时间复杂度为 O(n×a×b×c...)。分析的时候应该由里向外分析这些循环。
  1. O(n**2)
for i in range(n):
    for j in range(n):
        print('Hello World')
for i in range(n):
    print('Hello World')
    for j in range(n):
        print('Hello World')
  1. O(n**3)
for i in range(n):
    for j in range(n):
        for k in range(n):
            print('Hello World')

第三类

  • 对于顺序执行的语句或者算法,总的时间复杂度等于其中最大的时间复杂度。
void aFunc(int n) {
    // 第一部分时间复杂度为 O(n^2)
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < n; j++) {
            printf("Hello, World!\n");
        }
    }
    // 第二部分时间复杂度为 O(n)
    for(int j = 0; j < n; j++) {
        printf("Hello, World!\n");
    }
}

此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)

第四类

  • 对于条件判断语句,总的时间复杂度等于其中 时间复杂度最大的路径 的时间复杂度。
void aFunc(int n) {
    if (n >= 0) {
        // 第一条路径时间复杂度为 O(n^2)
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                printf("输入数据大于等于零\n");
            }
        }
    } else {
        // 第二条路径时间复杂度为 O(n)
        for(int j = 0; j < n; j++) {
            printf("输入数据小于零\n");
        }
    }
}

此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)。

第五类

  1. log2n / logn
while n > 1:
    print(n)
    n = n // 2
    
n = 64输出:
    64
    32
    16
    8
    4
    2

说明: 当n = 64 的时候, 程序执行了6次; 然而 2**6 = 64 ;

2**x = n ===> x=log2n

void aFunc(int n) {
    for (int i = 2; i < n; i++) {
        i *= 2;
        printf("%i\n", i);
    }
}

假设循环次数为 t:

循环第一次, i 约等于 2*2

循环第二次, i 约等于 2*2*2

循环第三次, i 约等于 2*2*2*2

循环第四次, i 约等于 2*2*2*2*2

所以 循环条件满足 2^t < n。
可以得出,执行次数t = log(2)(n),即 T(n) = log(2)(n),可见时间复杂度为 O(log(2)(n)),即 O(log n)。

简单判断时间复杂度

  • 确定问题规模n
  • 循环减半过程 ==> logn
  • K层关于n的循环 ==> n^k

复杂情况:根据算法执行过程判断

空间复杂度

  1. 用来评估算法内存占用大小的式子

  2. 空间复杂度的表示方式与时间复杂度完全一样

    • 算法使用了几个变量: O(1)
    • 算法使用了长度为n的一维列表: O(n)
    • 算法使用了m行n列的二维列表: O(mn)
  3. 空间换时间

    宁可占用更多的内存, 也要缩短程序运行的时间

    比如说分布式系统, 将本来一个机器上运算的资源分散到多个机器上, 相当于占用了多个机器的内存, 导致的结果就是程序的运行时间缩短了

递归

递归的特点

  1. 调用自身
  2. 结束条件
  • 先递归, 再打印
def func(x)
	if x>0
        func(x-1)
        print(x)

  • 先打印, 再递归
def func(x)
	if x>0
        print(x)
        func(x-1)

实例 1 汉诺塔

n个圆盘从一根柱子上移动到另一根, 圆盘从上到下是从小往大排列的

  1. 把n-1 个圆盘当成一个整体, 从柱子A经过柱子C移动到柱子B
  2. 把第n个圆盘从柱子A移动到C
  3. 把n-1 个圆盘从B经过A移动到C

def hanoi(n, a, b, c):
    if n > 0:
        hanoi(n - 1, a, c, b)
        print("movie from %s to %s" % (a, c))
        hanoi(n - 1, b, a, c)

hanoi(3, 'A', 'B', 'C')
'''
movie from A to C
movie from A to B
movie from C to B
movie from A to C
movie from B to A
movie from B to C
movie from A to C
'''

实例2 递归思路整理

long aFunc(int n) {
    if (n <= 1) {
        return 1;
    } else {
        return aFunc(n - 1) + aFunc(n - 2);
    }
}

分析:

当 调用 aFunc(3)的时候, 返回 aFunc(2) + aFunc(1)

  1. 调用 aFunc(2),继续递归调用, 调用aFunc(1) 返回1
  2. aFunc(2) 递归调用后, 返回 aFunc(1) + aFunc(0), 调用 aFunc(1) 返回1, 调用aFunc(0)也是返回1
  3. 输出的顺序就是 1 1 2 3

查找

  • 什么是查找

在一些数据元素中, 通过一定的方法找出与给定关键字相同数据元素的过程

  • 列表查找

    从列表中查找指定元素

    输入: 列表, 待查找元素

    输出: 元素下标(未找到元素时一般返回None或-1)

  • 内置列表查找函数

index()

顺序查找

也叫线性查找, 从泪飙第一个元素开始, 顺序进行搜索, 知道找到元素或搜索到列表的最后一个元素

def linear_search(li, val):
    for ind, v in enumerate(li):
        if v == val:
            return ind
    else:
        return '-1'

res = linear_search([1, 2, 3],2)
print('index:', res)

时间复杂度: O(n)

二分查找

前提是列表必须是有序列表

原理

代码实现

def binary_search(li, val):
    left = 0
    right = len(li)-1
    while left <= right: # 候选区有值
        mid = (left+right) // 2
        if li[mid] == val:
            return 'index',mid
        elif li[mid] > val: # 查找的值在mid左侧
            right = mid - 1
        elif li[mid] < val:  # 查找的值在mid右侧
            left = mid + 1
    else:
        return '-1'


li = [1, 2, 3, 4, 5, 6, 7, 8]
print(binary_search(li, 3))

时间复杂度: O(logn)

区别

index()查找用的是线性列表

二分查找更快

  • 运行时间装饰器
import time

def cal_time(func):
    def wrapper(*args, **kwargs):
        t1 = time.time()
        result = func(*args, **kwargs)
        t2 = time.time()
        print("%s running time: %s"%(func.__name__, t2 - t1))
        return result
    return wrapper

  • 二分查找
from time_cal import cal_time
import random
@cal_time
def binary_search(li, val):
    left = 0
    right = len(li)-1
    while left <= right: # 候选区有值
        mid = (left+right) // 2
        if li[mid] == val:
            return 'index',mid
        elif li[mid] > val: # 查找的值在mid左侧
            right = mid - 1
        elif li[mid] < val:  # 查找的值在mid右侧
            left = mid + 1
    else:
        return '-1'


li = list(range(100000000))
binary_search(li, random.randint(0, 10000000))
'''
binary_search running time: 0.0009920597076416016
'''

  • 线性查找
from time_cal import cal_time
import random

@cal_time
def linear_search(li, val):
    for ind, v in enumerate(li):
        if v == val:
            return ind
    else:
        return '-1'

li = list(range(100000000))
linear_search(li, random.randint(0, 100000000))

'''
linear_search running time: 6.8932390213012695
'''

排序

  • 什么是排序

    将一组无序的记录序列(列表)变成有序的记录序列

列表排序

输入: 列表

输出:有序列表

排序方式: 升序和降序

常用排序算法

排序NB三人组

  1. 冒泡排序
  2. 选择排序
  3. 插入排序

排序NB三人组

  1. 快速排序
  2. 堆排序
  3. 归并排序

其他排序

  1. 希尔排序
  2. 计数排序
  3. 基数排序

冒泡排序

列表每2个相邻的数, 如果前面的比后面的大, 则交换这2个数

一趟排序完成后, 则无序区减少一个数, 一开始有序区的数为0, 有序区增加一个数

整个排序算法排了(n-1)趟

posted @ 2019-10-16 22:27  cjw1219  阅读(269)  评论(0编辑  收藏  举报