算法


title: 算法
tags: 算法

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

递归

递归的两个特点:

  • 调用自身
  • 结束条件
def func3(x):
    if x > 0:
        print(x)
        func3(x - 1)
func3(3)

def func3(x):
    if x > 0:
        func3(x - 1)
        print(x)
func3(3)

![]
(http://images2017.cnblogs.com/blog/658994/201708/658994-20170825224958418-638409811.jpg)
![]
(http://images2017.cnblogs.com/blog/658994/201708/658994-20170825225002980-1149857509.jpg)

我的小鲤鱼
![]
(http://images2017.cnblogs.com/blog/658994/201708/658994-20170825225900949-574999163.png)

def func(depth):
    if depth == 0:
        print('我的小鲤鱼',end='')  # end = '' 代表不换行
    else:
        print('抱着',end='')
        func(depth-1)
        print('的我',end='')
func(3)

时间复杂度

评估算法运行效率的

时间复杂度是算法运行时间的式子

时间复杂度可以用表示时间复杂度的数和单位共同组成 ,单位就是n的几次方

O(1)
O(n)
O(n^2)
O(n^3)

![]
(http://images2017.cnblogs.com/blog/658994/201708/658994-20170825230649480-1075677653.jpg)

while循环 折半
O(log2n)

总结

时间复杂度是用来估计算法运行时间的一个式子(单位)。
一般来说,时间复杂度高的算法比复杂度低的算法慢。不同量级的不能比较

  • 常见的时间复杂度(按效率排序)
    O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n^3)
  • 不常见的时间复杂度(看看就好)
    O(n!) O(2n) O(nn) …

如何一眼判断时间复杂度?
循环减半的过程 O(logn)
几次循环就是n的几次方的复杂度

时间复杂度按照最大的来判断

空间复杂度

内存占用大小

O(1)
Sn = O(n)

空间换时间 算法跑的快

列表查找

从列表中查指定元素

输出:元素下标或未找到元素

顺序查找

从头开始查找,直到查找到结果

二分查找

有序的列表查找

val < mid : high = mid -1
val > mid : low = mid +1





二分法和顺序查找测试

In [1]: import random
   ...: n = 10000
   ...: li = list(range(n))
   ...:
   ...: def bin_searhc(li, val):
   ...:     low = 0
   ...:     high = len(li) - 1
   ...:     while low <= high:
   ...:         mid = (low + high) // 2
   ...:         if li[mid] == val:
   ...:             return mid
   ...:         elif li[mid] < val:
   ...:             low = mid + 1
   ...:         else:
   ...:             high = mid - 1
   ...:     return None  # 单high < low 的时候不存在
   ...:

In [2]:

In [2]: %timeit bin_searhc(li,3200)
The slowest run took 4.12 times longer than the fastest. This could
 intermediate result is being cached.
100000 loops, best of 3: 5.08 µs per loop

In [3]: %timeit li.index(3200)
10000 loops, best of 3: 64.3 µs per loop
  • index 的时间复杂度是O(n)
  • bin_search的时间复杂度是O(logn)

2**64 仅仅查找64次
O(logn)

所以最后相差的会很大

尾递归的和二分速度差不多

尾递归没有在出栈的时候的保存,所以速度快

出栈的时候不会保存变量

def bin_search_rec(data_set, value, low, high):
    if low <= high:
        mid = (low + high) // 2
        if data_set[mid] == value:
            return mid
        elif data_set[mid] > value:
            return bin_search_rec(data_set, value, low, mid - 1)
        else:
            return bin_search_rec(data_set, value, mid + 1, high)
    else:
        return
li =[
    {'id': 1001, 'name': "张三", 'age': 20},
    {'id': 1002, 'name': "李四", 'age': 25},
    {'id': 1004, 'name': "王五", 'age': 23},
    {'id': 1007, 'name': "赵六", 'age': 33}
]


def bin_searhc(li, val):
    low = 0
    high = len(li) - 1
    while low <= high:
        mid = (low + high) // 2
        if li[mid]['id'] == val:
            return li[mid]
        elif li[mid]['id'] < val:
            low = mid + 1
        else:
            high = mid - 1
    return None  # 单high < low 的时候不存在

res = bin_searhc(li,1002)
print(res)

列表排序

sort()

LowB 三人组

关键点:

有序区
无序区

冒泡排序

"""
冒泡排序,注意的关键点是有序区和无序区
比较相邻的两个数的大小,如果前面的数比后面的数大,则交换,
交换完一次称为一趟,每一趟的指针:n-i-1(ns是总数,i是趟数),即每一趟遍历的个数
"""
import time
import random as rd


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

    return wrapper


@cal_time
def bubble_sort(li):
    """
    第一层的for循环是趟
    第二层的for循环是每一趟中比较相邻两个数的大小
    :param li:
    :return:
    """
    for i in range(len(li) - 1):
        for j in range(len(li) - i - 1):
            if li[j] > li[j + 1]:
                li[j], li[j + 1] = li[j + 1], li[j]
    return li


@cal_time
def sys_sort(li):
    li.sort()
    return li

"""
冒泡排序的时间测试
"""
# li = [ i for i in range(1000)]
li = list(range(5000))
rd.shuffle(li) # 用shuffle对li洗牌 是直接操作的li
li1 = copy.deepcopy(li)
li2 = copy.deepcopy(li)
# print(bubble_sort(li))
bubble_sort(li1)
sys_sort(li2)

冒泡排序的时间复杂度是O(n^2)
通过和系统的sort运行比较,内置的sort高两个数量级:
bubble_sort running time is 3.966226577758789 sec
sys_sort running time is 0.0020003318786621094 sec

冒泡排序的改进

最坏情况和一般情况下,冒泡排序的时间复杂度是O(n^2),最好情况是O(n)

def bubble_sort(li):
    """
    第一层的for循环是趟
    第二层的for循环是每一趟中比较相邻两个数的大小
    最好情况:相邻的数已经排列好了,没有交换,通过设置标志位
    :param li:
    :return:
    """
    for i in range(len(li) - 1):
        exchange = False
        for j in range(len(li) - i - 1):
            if li[j] > li[j + 1]:
                li[j], li[j + 1] = li[j + 1], li[j]
                exchange = True
        if not exchange:
            break
    return li

选择排序

在一个列表中中删除一个元素的时候 时间复杂度是O(n),
后面的补过来 len()-1

把最小的数放到有序区

把第一个数定义成最小的
第一趟找到最小的数

"""
选择排序:一趟遍历中找最小的数,放到有序区的第一个位置,后面依次找最小的数,并依次排列
"""
import time
import random as rd
import copy


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

    return wrapper


@cal_time
def select_sort(li):
    for i in range(len(li) - 1): # i 是趟
        min_lock = i # i是索引
        # 找i位置到最后最小的数
        for j in range(i, len(li)): # i可以是i+1 省去和自己比 也代表了无序区的第一个数
            if li[j] < li[min_lock]:
                min_lock = j
        # 和无序区第一个数交换 此时的i是无序区的第一个数的索引
        li[min_lock], li[i] = li[i], li[min_lock]
    return li


@cal_time
def sys_sort(li):
    li.sort()
    return li


"""
冒泡排序的时间测试
"""
# li = [ i for i in range(1000)]
li = list(range(1000))
rd.shuffle(li)  # 用shuffle对li洗牌 是直接操作的li
li1 = copy.deepcopy(li)
li2 = copy.deepcopy(li)
# print(select_sort(li1))
select_sort(li1)
sys_sort(li2)

插入排序

关键点:
摸到的牌
手里的牌

"""
插入排序:列表被分成有序区和无序区两部分,最初有序区只有一个元素,无序区从1~n-1,从无序区中找摸牌,插入到有序区中
插入排序的有序区是变化的,
把摸到的牌存到临时变量,然后和手里的最后一张牌比较,指针开始在最后一张牌,(指针是j)
"""
import time
import random as rd
import copy


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

    return wrapper


@cal_time
def insert_sort(li):
    for i in range(1, len(li)):  # i代表的是手里的牌,默认第一张是放在有序区,从无序区中摸牌
        tmp = li[i]  # 从后面开始摸牌,存到临时变量
        j = i - 1  # j代表的是手里的最后一张牌的下标
        while True:
            if j < 0 or tmp >= li[j]:
                break
            li[j + 1] = li[j]  # 手里的最后一张牌向后移
            j -= 1  # 指针向后移动一位
        li[j + 1] = tmp  # 把临时变量放在j+1的位置
    return li

@cal_time
def sys_sort(li):
    li.sort()
    return li


"""
排序的时间测试
"""
# li = [ i for i in range(1000)]
li = list(range(1000))
rd.shuffle(li)  # 用shuffle对li洗牌 是直接操作的li
li1 = copy.deepcopy(li)
li2 = copy.deepcopy(li)
# print(insert_sort(li1))
insert_sort(li1)
sys_sort(li2)

快速排序

归位,左右进行递归 还有0个 一个元素的时候停止递归

返回的是下标

递归至少有2个条件 left<right

position函数最重要
临时变量data[left]

对称
防止超过递归深度
设置递归层数

快排的时间复杂度:
partition O(n)

"""
快速排序:关键是归位和递归,归位后返回的是归位的下标mid,mid把列表分成左右两部分,
递归的条件:left<right,至少有两个元素,只有一个元素的时候,结束递归
左侧范围:left~mid-1
右侧范围:mid+1~right
"""
import time
import random as rd
import copy
sys.setrecursionlimit(99999)  # setrecursionlimit 设置递归最大深度限制


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

    return wrapper


def _quick_sort(data, left, right):
    if left < right:
        mid = partition(data, left, right)
        _quick_sort(data, left, mid - 1)  # 左边的范围
        _quick_sort(data, mid + 1, right)  # 右边的范围


# 归位函数
def partition(data, left, right):
    tmp = data[left]
    while left < right:
        while left < right and data[right] >= tmp:
            right -= 1
        data[left] = data[right]  # 把右面比tmp小的放左边
        while left < right and data[left] <= tmp:
            left += 1
        data[right] = data[left]  # 把左边比tmp大的放到右边
    data[left] = tmp  # 此时left = right
    return left # 返回tmp的坐标


@cal_time
def quick_sort(data):
    # 快排中有递归 装饰器会多次执行
    return _quick_sort(data, 0, len(data) - 1)


@cal_time
def sys_sort(li):
    li.sort()
    return li


"""
排序的时间测试
"""
# li = [ i for i in range(1000)]
li = list(range(1000))
rd.shuffle(li)  # 用shuffle对li洗牌 是直接操作的li
li1 = copy.deepcopy(li)
li2 = copy.deepcopy(li)
# print(quick_sort(li1))
sys_sort(li2)
quick_sort(li1)

快排的问题:

  • O(nlogn) 每一层是logn 一共有n层
  • 倒序的列表并没有分成两部分,每一次只排一个 O(n^2)
  • 最坏情况的复杂度是O(n^2)

解决方式:随机化的快排

随机选一个数,放到最左侧(算法导论是右侧)

递归过多的时候会导致栈溢出

降序排列

堆排序

树与二叉树

树是一种数据结构

二叉树的存储方式

最后的非叶子节点

len(data)//2 -1

向下调整

二叉树从根----叶子 logn

python内置heapq 堆模块

归并排序

把一个列表分成两部分,一次归并

挨着换的稳定 飞换的是不稳定的

希尔排序

分组 + 插入

"""
希尔排序:先分组 后插入排序
"""
import time
import random as rd
import copy


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

    return wrapper


def insert_sort_gap(li, gap):
    for i in range(gap, len(li)):
        tmp = li[i]
        j = i - gap
        while j >= 0 and tmp < li[j]:
            li[j + gap] = li[j]
            j = j - gap
        li[j + gap] = tmp

@cal_time
def shell_sort(li):
    d = len(li) // 2
    while d > 0:
        insert_sort_gap(li, d)
        d = d // 2
    return li

@cal_time
def sys_sort(li):
    li.sort()
    return li


"""
排序的时间测试
"""
# li = [ i for i in range(1000)]
li = list(range(10000))
rd.shuffle(li)  # 用shuffle对li洗牌 是直接操作的li
li1 = copy.deepcopy(li)
li2 = copy.deepcopy(li)
# print(shell_sort(li1))
sys_sort(li2)
shell_sort(li1)

计数排序

内存占用大,用空间换时间

原文地址:https://www.cnblogs.com/Python666/p/7479645.html