第一章
二分查找
二分查找是一种算法, 其输入是一个有序的元素列表. 如果要查找的元素包含在列表中, 二分查找返回其位置: 否则返回 NULL
一般而言, 对于包含N个元素的列表, 用二分查找最多需要log2^N步, 而简单查找最多需要N步.
# –*– coding: utf-8 –*–
# @Time : 2019/1/4 21:53
# @Author : Damon_duan
# @FileName : binary_search.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import time
import random
def binary_search(find_list, search_info):
low = 0
high = len(find_list) - 1
while low <= high:
mid = (low + high) // 2
if search_info == find_list[mid]:
return mid
if search_info > find_list[mid]:
low = mid + 1
else:
high = mid - 1
return None
def easy_search(find_list, search_info):
for i in range(len(find_list) - 1):
if search_info == find_list[i]:
return i
return None
if __name__ == '__main__':
list_a = [i for i in range(10000000)]
find_num = random.randint(0, 10000000)
print("find number is : {}".format(find_num))
t1 = time.time()
binary_search(list_a, find_num)
t2 = time.time()
easy_search(list_a, find_num)
t3 = time.time()
cost_1 = t2 - t1
cost_2 = t3 - t2
print("二分法查找花费时间:{}".format(cost_1))
print("简单查询法查找花费时间:{}".format(cost_2))
运行结果:
>>>
find number is : 8645596
二分法查找花费时间:0.0
简单查询法查找花费时间:0.3390543460845947
一些常见的大 O 运行时间
O( log n ) , 也叫对数时间, 这样的算法包括二分查找
O( n ) , 也叫线性时间, 这样的算法包括简单查找
O( n * log n ) , 这样的算法包括快速排序法
O( n^2 ) , 这样的算法包括 冒泡排序 插入排序 选择排序
O( n! ), 旅行家算法
O( log n ) 比 O( n ) 快, n 越大, 前者比后者就快的越多.
选择排序
数组和链表
链表: 链表中的元素可以储存在内存的任何地方. 链表的每个元素都存储了下一个元素的地址, 从而使一系列随机的内存地址串在一起. 只要有足够的内存空间, 就能为链表分配内存.链表的优势在插入元素,删除元素方面.
数组: 数组在内存中都是相连的,添加元素可能需要开辟新的内存空间将所有元素转移,对于数组,我们知道其中每个元素的地址. 数组的优势在查询方面.
**结论:**数组的元素都在一起, 链表的元素是分开的, 其中每个元素都存储下一个元素的地址. 数组的读取速度很快, 链表的插入和删除速度很快.
选择排序
以下代码为冒泡, 插入, 选择 排序示例
# –*– coding: utf-8 –*–
# @Time : 2019/1/4 20:24
# @Author : Damon_duanlei
# @FileName : sort_methord.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import random
import copy
import time
def bubble_sort(sort_list):
for i in range(len(sort_list) - 1):
for j in range(len(sort_list) - 1 - i):
if sort_list[j] > sort_list[j + 1]:
sort_list[j], sort_list[j + 1] = sort_list[j + 1], sort_list[j]
def insert_sort(sort_list):
for i in range(1, len(sort_list)):
j = i
insert_num = sort_list[i]
while insert_num < sort_list[j - 1]:
sort_list[j] = sort_list[j - 1]
j -= 1
if j == 0:
sort_list[0] = insert_num
break
if insert_num >= sort_list[j - 1]:
sort_list[j] = insert_num
def selection_sort(sort_list):
new_list = []
while len(sort_list):
index = 0
min_num = sort_list[0]
for i in range(1, len(sort_list)):
if min_num > sort_list[i]:
min_num = sort_list[i]
index = i
new_list.append(sort_list.pop(index))
return new_list
if __name__ == '__main__':
list_a = [random.randint(0, 100) for i in range(20000)]
list_1 = copy.deepcopy(list_a)
list_2 = copy.deepcopy(list_a)
list_3 = copy.deepcopy(list_a)
t1 = time.time()
bubble_sort(list_1)
t2 = time.time()
insert_sort(list_2)
t3 = time.time()
new_list = selection_sort(list_3)
t4 = time.time()
print("冒泡排序用时: {}".format(t2 - t1))
print("插入排序用时: {}".format(t3 - t2))
print("选择排序用时: {}".format(t4 - t3))
运行结果:
>>>
冒泡排序用时: 27.151382446289062
插入排序用时: 20.87420392036438
选择排序用时: 7.898883581161499
以上三种排序方法的时间复杂度均为 O( n^2 ) 但是同样的元素排序后花费的时间出现的较大差异, 这与大 O 表示法中的常数有关, 后续部分将详细解释.
第三章 递归
递归
函数自己调用自己的过程
递归只是让解决方案更清晰, 并咩有性能上的优势. 甚至有些情况下, 使用循环的性能更好.
基线条件(递归出口)和递归条件
由于递归函数调用自己, 因此编写这样的函数很容易出错, 进而导致无线循环. 编写递归函数时, 必须告诉它何时停止递归. 正因为如此,每个递归函数都用两部分: 基线条件和递归条件. 递归条件指的是函数调用自己, 而基线条件则指的是函数不再调用自己, 从而避免形成无限循环.
栈
栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
计算机在内部使用被称为调用栈的栈.计算机如何使用调用栈,代码示例如下:
# –*– coding: utf-8 –*–
# @Time : 2019/1/6 17:05
# @Author : Damon_duanlei
# @FileName : demon_01.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
def greet(name):
print("hello, {} !".format(name))
greet2(name)
print("getting ready to say bye ...")
bye()
def greet2(name):
print("how are you {} ?".format(name))
def bye():
print("good bye !")
if __name__ == '__main__':
greet("Demon")
过程:
当你调用 greet(“damon”) , 计算机首先为该函数调用分配一块内存. 我们使用这些内存. 变量 name 被设置成 Damon 并存储到内存中. 每当调用函数时, 计算机都会想这样将函数调用涉及的所有变量的值存储到内存中.
接下来, 打印 hello Damon ! , 在调用greet2(“Damon”). 同样, 计算机也为这个函数调用分配一块内存.
计算机使用一个栈来表示这些内存块, 其中第二个内存块位于第一个内存块上面. 当打印 how are you Demon ? ,然后从函数调用返回.此时, 栈顶的内存块被弹出.
此时, 栈顶的内存块是函数greet的, 这意味着运行返回到了函数greet. 当调用函数greet2时, 函数 greet 只执行了一部分. 调用一个函数是, 当前函数暂停并处于未完成状态, 该函数的所有变量的值都还在内存中. 执行完greet2后, 回到函数 greet ,并从离开的地方开始接着往下执行: 首先打印 getting ready to say bye … ,在调用bye.
在栈顶添加了函数bye的内存块. 然后打印 good bye ! ,并从这个函数返回. 当回到函数 greet. 由于没有别的事情要做, 就从函数 greet 返回. 这个栈用于存储多个函数的变量, 被称为调用栈
递归调用栈
递归函数也使用调用栈! 过程和普通行数调用相同区别只是函数多次调用自己.栈在递归中扮演着重要角色. 使用栈很方便,但是也要付出代价: 存储详尽的信息可能占用大量的内存. 每个函数调用都占用一定的内存, 如果栈很高, 就意味着计算机存储了大量函数调用的信息. 在这种情况下,有两种选择: 1. 重新编写代码,转而使用循环 2.使用尾递归
以下代码为使用递归方式去斐波那锲数列
# –*– coding: utf-8 –*–
# @Time : 2019/1/6 17:39
# @Author : Damon_duanlei
# @FileName : Fibomacci.py
# @BlogsAddr : https://blog.csdn.net/Damon_duanlei
import time
def fibomacci(n):
if n == 0:
return 0
if n == 1:
return 1
ret = fibomacci(n - 1) + fibomacci(n - 2)
return ret
fibomacci_list = []
for i in range(50):
t1 = time.time()
fibomacci_list.append(fibomacci(i))
t2 = time.time()
print("第{}位数计算话费时间: {} 秒".format(i + 1, t2 - t1))
print(fibomacci_list)
当 n 取值超过 30 时,函数执行效率明显下降.
下一章快速排序 学习进行中…
谢谢