算法
算法是独立存在的一种解决问题的方法和思路。
算法的五大特征:
(1)输入性:有零个或多个外部量作为算法的输入
(2)输出性:算法至少有一个量作为输出(a,b,c)
(3)确定性:算法中每条指令清晰,无歧义(思想)
(4)有穷性:算法中每条指令的执行次数有限,执行每条指令时间也有限
(5)可行性:算法原则上能够精确的运行,而且人们用纸和笔做有限次运算后即可完成
#案例:如果 a+b+c=1000,且 a^2+b^2=c^2(a,b,c 为自然数),如何求出所有 a、b、c可能的组合?
import time
start_time = time.time()
for a in range(1001):
for b in range(1001):
for c in range(1001):
if a+b+c==1000 and a**2+b**2 == c**2:
print('a,b,c',a,b,c)
end_time = time.time()
print("耗费时间:",(end_time-start_time))
#案例优化
import time
start_time = time.time()
for a in range(1001):
for b in range(1001):
c = 1000 - a - b
if a**2+b**2 ==c**2:
print('a,b,c',a,b,c)
end_time = time.time()
print("耗费时间:",(end_time-start_time))
实现算法程序的执行时间可以反应出算法的效率,但单纯依靠运行的时间来比较算法的优劣并不一定是客观准确的。
时间复杂度
一个算法花费的时间与算法中语句的执行次数是成正比的。哪个算法语句执行的次数多,它花费的时间就多。
def test(n):
count = 0
for i in range(count,n):
for j in range(count,n):
count+=1
for k in range(0,2*n):
count+=1
icount = 10
while icount>0:
count+=1
icount-=1
#执行次数为:f(n)=n^2+2*n+10
对于算法最重要的是数量级和趋势,这些是分析算法主要的部分,而计量算法基本操作数量的规模函数中哪些常量因子可以忽略不计。
时间复杂度实际上就是一个函数,该函数计算的是执行基本操作的次数。一个算法语句总的执行次数是关于问题规模 N 的某个函数,记为分 f(N),N 称为问题的规模,语句总的执行次数。记为 T[N],当 N 不断变化时,T[N]也在变化,算法的执行次数的增长速率和 f(N)的增长速率相同。则 T[N]=O(f(N)),称 O(f(N))为时间复杂度的 O 渐进表示法。
算法完成工作最少需要多少基本操作,即最优时间复杂度;算法完成工作最多需要多少基本操作,即最坏时间复杂度;算法完成工作平均需要多少基本操作,即平均时间复杂度(全面评价)
基本计算规则:(最坏时间复杂度)
(1) 基本操作,即只有常数项,认为其时间复杂度为 O(1)
(2) 顺序结构,时间复杂度按加法进行计算
(3) 循环结构,时间复杂度按乘法进行计算
(4) 分支结构,时间复杂度取最大值
(5) 判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略
(6) 在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度
以上案例第一种解决方式:T(n) = O(nnn) = O(n^3),第二种解决方式:T(n) = O(nn(1+1)) = O(n*n) = O(n^2)
常见时间复杂度:
所消耗的时间从小到大:O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
空间复杂度
一个程序的空间复杂度是指运行完一个程序所需内存的大小。一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。主要包括:
(1)固定部分,这部分大小与输入输出的数据个数多少、数值无关。主要包括指令空间(代码空间)、数据空间(常量、简单变量)等所占的空间,这部分属于静态空间。
(2)可变部分,主要包括动态分配的空间,以及递归栈所需的空间等。
一个算法所需的存储空间用 f(n)表示。S(n)=O(f(n)) 其中 n 为问题的规模,S(n)表示空间复杂度
def reverse(a,b):
n = len(a)
for i in range(n):
b[i] = a[n-1-i]
要分配的内存空间:引用a,引用b,局部变量n,局部变量i,此 f(n)=4 ,4 为常量。所以该算法的空间复杂度 S(n)=O(1)
排序算法
排序算法是一种能将一串数据依照特定顺序进行排列的一种算法。
排序算法的稳定性,稳定排序算法会让原本有相等键值的纪录维持相对次序,不稳定排序算法可能会在相等的键值中改变纪录的相对次序
#案例
(4, 1) (3, 1) (3, 7)(5, 6)(原始序列)
(3, 1) (3, 7) (4, 1) (5, 6) (维持次序)
(3, 7) (3, 1) (4, 1) (5, 6) (次序被改变)
冒泡排序
算法:比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。
最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)
最坏时间复杂度:O(n^2)
稳定性:稳定
def Bubble_sort(alist):
#相邻两个元素进行比较,如果发现位置错误则进行交换
n = len(alist)
for k in range(n-1):
count = 0
for i in range(n - 1 -k):
if alist[i] > alist[i + 1]:
alist[i], alist[i + 1] = alist[i + 1], alist[i]
count+=1
#判断count是否等于0,等于0说明没有交换
if count==0:
print(count)
break
alist = [1,2,3,4,5]
Bubble_sort(alist)
print(alist)
###要点!!!
#1、我们比较是相邻之间的元素,所以用索引更合适
#2、既然比较的是索引,需要关注数组索引是否越界,最后比较是n-1和n这两个元素,所以索引最大取到n-1
#3、每进行一次循环,都把最大的元素排到后面,第一轮n-1和n比较,第二轮n-2和n-1比较,因此总共比较n-1次,每一轮元素交换n-1-k次
#4、最后对排序进行优化,当传入的序列是有序的,则进行一次比较即可跳出循环,无需再进行多轮比较,最优时间复杂度是O(n)
选择排序
算法:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。(依次进行一轮循环,每次循环都把当前元素当成是最小的元素,在把默认最小元素和剩余的元素比较,如果有比默认元素还要小的,就交换)
最优时间复杂度:O(n2)
最坏时间复杂度:O(n2)
稳定性:不稳定(考虑升序每次选择最大的情况) 比如:[2,(99,1),3,6,1,(99,2),7],[2,7,3,6,1,(99,2),(99,1)]
def Select_sort(alist):
n = len(alist)
for i in range(n-1):#倒数第二个元素
#首先把i当成最小值索引
min_index = i
for j in range(i+1,n):
# 找剩余列表中最小值的索引
if alist[min_index] > alist[j]:
min_index = j
if min_index != i:
alist[i],alist[min_index] = alist[min_index],alist[i] #找最小值进行交换
alist = [7,5,3,8,4]
Select_sort(alist)
print(alist)
插入排序
它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
最坏时间复杂度:O(n*n) (最小的元素排在最后面,需要和前面的n-1个有序序列元素交换)
稳定性:稳定 [17, 20, 31, 44, 54, 55, 77, 93,77, 226] [17, 20, 31, 44, 54, 55, 77,,77,93 226]
#前一段是有序序列,每次取后续无序列表的元素相比
def insert_sort(a):
n = len(a)
for j in range(1,n):#从第二个元素开始到最后一个元素 循环n-1次
i = j
while i>0:#和有序列表中每个元素进行比较,从最后一个开始
if a[i]<a[i-1]:
a[i],a[i-1] = a[i-1],a[i]
else:#否则已经是有序列表,不需要进行交换
break
i-=1
alist = [54,226,93,17,77,31,44,55,20]
insert_sort(alist)
print(alist)
快速排序(交换排序)
找一个基准数据,经过排序将当前列表比基准数据小的放在左边,比基准数据大的放在右边.一趟快排O(n),而时间复杂度等于一趟快排次数 第一次分两部分 第二次分四部分 第三次分八部分……n/2x = 1 x = log2n
最优时间复杂度:O(nlogn)
最坏时间复杂度:O(n2)(有序序列,分n次)
稳定性:不稳定
def quick_sort(alist,start,end):
#递归出口
if start>=end:
return
#基准数
mid = alist[start]
low = start
high= end
while low<high: #low high重叠,退出循环
#从右向左排序
if low < high and alist[high]>=mid: #考虑元素相等的情况
high-=1
alist[low] = alist[high] #将high索引对应的元素赋值给low
#从左向右排序
if low <high and alist[low]<mid:
low+=1
alist[high] = alist[low]
alist[low] = mid #将基准数放置到对应的位置
#比基准数小的即左边的数据,重复调用quick_sort()
quick_sort(alist,start,low-1)
#比基准数大的即右边的数据,重复调用quick_sort()
quick_sort(alist,low+1,end)
alist = [54, 26, 93, 17, 77, 44, 44, 55, 20]
quick_sort(alist,0,len(alist)-1)
print(alist)
归并排序
先递归分解数组,在合并数组
将数组分解到最小之后然后合并两个有序数组,基本思路就是比较两个数组的最前面的数,谁小就取谁,取了之后相应的指针往后移一位。然后再比较,直到最后一个为空,最后把另一个数组的剩余部分复制过来即可。
最优时间复杂度:O(nlogn)
最坏时间复杂度:O(nlogn)
稳定性:稳定
def merge_sort(alist):
#分解
n = len(alist)
if n<=1:#递归的出口,分解到最小
return alist
mid = n // 2
left = merge_sort(alist[0:mid])
right = merge_sort(alist[mid:])
#合并
result = []
left_pointer,right_pointer = 0,0
while left_pointer < len(left) and right_pointer <len(right):
if left[left_pointer] < right[right_pointer]:
result.append(left[left_pointer])
left_pointer += 1
else:
result.append(right[right_pointer])
right_pointer += 1
#退出循环将不为空的列表剩余元素添加到result中
result+=left[left_pointer:]
result+=right[right_pointer:]
return result
alist = [4,3,2,7]
print(merge_sort(alist))
查找算法
顺序查找法
从表中的第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录;如果直到最后一个(或第一个)记录,其关键字和给定值比较都不等时,则表示没有查到记录,查找不成功。
def sequence_search(alist,v):
for i in range(len(alist)):
if alist[i] == v:
return i
return -1
alist = [1,2,3,4,5,6]
index = sequence_search(alist,5)
if index == -1:
print("没有找到此元素")
else:
print("此元素的索引值为:",index)
二分查找法
又称为折半查找,优点是比较次数少,查找速度快,平均性能好,缺点是要求有序表
#非递归实现
def binary_search(alist,v):
n = len(alist)
start = 0
end = n-1
while start <= end:
mid = (start+end)//2
if alist[mid]==v:
return True
elif alist[mid]>v: #从左边查找
end = mid-1
else:
start = mid +1
return False
if __name__ == '__main__':
alist = [1,2,3,4,5,6]
index = binary_search(alist,6)
print(index)
#递归实现
def binary_search1(alist,v):
n = len(alist)
mid = n//2
if n == 0:
return False
else:
if alist[mid] == v:
return True
elif alist[mid]<v: #从右边查找
return binary_search1(alist[mid+1:],v)
else:
return binary_search1(alist[0:mid-1],v)
alist = [1,2,3,4,5]
print(binary_search1(alist,6))