引言
目前,被广泛认可的算法专业定义是:算法是模型分析的一组可行的,确定的,有穷的规则。简单的来说,算法是用来解决某个问题做一系列计算的方法。从计算机程序设计的角度看,算法是一组完成任务的指令,任何一个代码片段都可以视为一个算法。一个典型的算法一般都具有:可行性,确切性,有穷性,输入,输出等特征。
目录
汉诺塔
是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
#汉诺塔
def hanoi(n,a,b,c):
if n > 0:
hanoi(n-1,a,c,b)
print("moving from %s to %s" % (a,c))
hanoi(n-1,b,a,c)
hanoi(2,'a','b','c')
顺序查找
按照序列原有顺序对数组进行遍历比较查询的基本查找算法。
# 顺序查找
def line_search(li,val):
for ind,v in enumerate(li):
if val == v:
return ind
return None
print(line_search([1,5,6,7,8],5))
二分查找
二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想,每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。
#二分查找
def binary_search(li,val):
left = 0
right = len(li) - 1
while left <= right:
mid = (right + left) // 2
if li[mid] == val:
return mid
elif li[mid] > val:
right = mid - 1
else:
left = mid + 1
else:
return None
print(binary_search([9,1,5,6,7,8,10,50,13,45],9))
冒泡排序
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
# 冒泡排序
def maopao(li):
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
print(li)
if not exchange:
return
li = [1,2,3,5,7,8,9]
maopao(li)
选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
#选择排序
def xz(li):
for i in range(len(li)-1):
min_val = i
for j in range(i+1,len(li)):
if li[min_val] > li[j]:
min_val = j
li[i],li[min_val] = li[min_val],li[i]
print(li)
li= [1,5,7,8,6,5,4,3,5]
xz(li)
插入排序
插入排序的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 插入排序在实现上,通常采用in-place排序(即只需用到O (1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 趣味解释: 插入排序操作类似于摸牌并将其从大到小排列。 每次摸到一张牌后,根据其点数插入到确切位置。
#插入排序
def cr(li):
for i in range(1,len(li)):
val = li[i]
q = i - 1
while q >= 0 and li[q] > val:
li[q+1] = li[q]
q -= 1
li[q+1] = val
print(li)
li = [1,5,3,9,8,7,5,4,6,2]
cr(li)
快速排序
通过选择一个基准元素,将数据分成小于基准的左子数组和大于基准的右子数组,然后递归地对子数组进行排序,最终实现整个数组的排序。
# 快速排序
def part(li,left,right):
val = li[left]
while left < right:
while li[right] >= val and right > left:
right -= 1
li[left] = li[right]
print(li)
while li[left] <= val and right > left:
left += 1
li[right] = li[left]
print(li)
li[left] = val
return left
def quckly(li,left,right):
if left < right:
mid = part(li,left,right)
quckly(li,left,mid-1)
quckly(li,mid+1,right)
return li
li=[6,5,9,6,3,4,5,3]
print(quckly(li,0,len(li)-1))
堆排序
1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。
#堆排序
def max_sift(li,low,high):#大根堆
i = low
j = i*2+1
val = li[i]
while j <= high:
if j + 2 <= high and li[j] < li[j+1]:
j += 1
if li[j] > val:
li[i] = li[j]
i = j
j = i*2+1
else:
li[i] = val
break
else:
li[i] = val
def dui(li):
n = len(li)
for i in range((n-2)//2,-1,-1):
max_sift(li,i,n-1)
for j in range(n-1,-1,-1):
li[0],li[j] = li[j],li[0]
max_sift(li,0,j-1)
print(li)
def xiao_sift(li,low,high):#小根堆
i = low
j = i * 2 +1
val = li[i]
while j <= high:
if j + 2 <= high and li[j] > li[j+1]:
j += 1
if li[j] < val:
li[i] = li[j]
i = j
j = i*2+1
else:
li[i] = val
break
else:
li[i] = val
def dui(li):
n = len(li)
for i in range((n-2)//2,-1,-1):
xiao_sift(li,i,n-1)
for j in range(n-1,-1,-1):
li[0],li[j] = li[j],li[0]
xiao_sift(li,0,j-1)
print(li)
堆排序解决top问题
从arr[1, n]这n个数中,找出最大的k个数,这就是经典的TopK问题。
#堆排序解决top问题
def xiao_sift(li,low,high):
i = low
j = i*2+1
val = li[i]
while j <= high:
if j +1 <= high and li[j] > li[j+1]:
j += 1
if li[j] < val:
li[i] = li[j]
i = j
j = i*2+1
else:
break
li[i] = val
def dui(li,k):
heap = li[0:k]
for i in range(k//2-1,-1,-1):
xiao_sift(heap,i,k-1)
for j in range(k,len(li)-1):
if heap[0] < li[j]:
heap[0] = li[j]
xiao_sift(heap,0,k-1)
for v in range(k-1,-1,-1):
heap[0],heap[v] = heap[v],heap[0]
xiao_sift(heap,0,v-1)
return heap
import random
o = [i for i in range(100)]
random.shuffle(o)
print(o)
print(dui(o,10))
归并排序
1. 分解(Divide):将n个元素分成个含n/2个元素的子序列。 2. 解决(Conquer):用合并排序法对两个子序列递归的排序。 3. 合并(Combine):合并两个已排序的子序列已得到排序结果。
#归并排序
def merge_sort(li,low,mid,high):
i = low
j = mid+1
a = []
while i <= mid and j <= high:
if li[i] < li[j]:
a.append(li[i])
i += 1
else:
a.append(li[j])
j += 1
while i <= mid:
a.append(li[i])
i += 1
while j <= high:
a.append(li[j])
j += 1
li[low:high+1] = a
def merge(li,low,high):
mid = (low + high)//2
if low < high:
merge(li,low,mid)
merge(li,mid+1,high)
merge_sort(li,low,mid,high)
import random
li = [i for i in range(20)]
random.shuffle(li)
print(li)
merge(li,0,len(li)-1)
print(li)
希尔排序
现将待排序的数组元素分成多个子序列,使得每个子序列的元素个数相对较少,然后对各个子序列分别进行直接插入排序,待整个待排序列“基本有序”后,最后在对所有元素进行一次直接插入排序。因此,我们要采用跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
#希尔排序
def insert_sort(li,page):
for i in range(page,len(li)):
val = li[i]
j = i-page
while j >= 0 and val < li[j]:
li[j+page] = li[j]
j -= page
li[j+page] = val
print(li)
def xe(li):
i = len(li) // 2
while i >= 1:
insert_sort(li,i)
i //= 2
import random
li = [i for i in range(20)]
random.shuffle(li)
print(li)
xe(li)
计数排序
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
#计数排序
def count_sort(li,maxcount):
a = [0 for i in range(maxcount+1)]
for j in li:
a[j] += 1
li.clear()
for ind,val in enumerate(a):
for k in range(val):
li.append(ind)
print(li)
import random
c = [random.randint(0,100) for _ in range(0,100)]
count_sort(c,100)
桶排序
工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后依次把各个桶中的记录列出来记得到有序序列。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到O(n log n)下限的影响。
#桶排序
import random
def tong_sort(li,n=100,max_count=10000):
a = [[] for i in range(n)]
for i in li:
t = min(i//(max_count//n),n-1)
a[t].append(i)
for j in range(len(a[t])-1,0,-1):
if a[t][j] < a[t][j-1]:
a[t][j],a[t][j-1] = a[t][j-1],a[t][j]
else:
break
l = []
for k in a:
l.extend(k)
print(l)
li = [random.randint(0,1000) for i in range(10000)]
tong_sort(li)
基数排序
原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
#基数排序
import random
def js_sort(li):
max_count = max(li)
it = 0
while 10 ** it <= max_count:
t = [[] for i in range(10)]
for j in li:
c = (j // 10**it) %10
t[c].append(j)
li.clear()
for k in range(len(t)):
li.extend(t[k])
it += 1
li = [i for i in range(1000)]
random.shuffle(li)
js_sort(li)
print(li)
贪心问题、找零问题
从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。 每一步只考虑一个数据,他的选取应该满足局部优化的条件。 若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止 。
#贪心问题
#找零问题
change = [100,50,20,10,1]
def change_money(change,n):
count = [0 for _ in range(len(change))]
for ind,val in enumerate(change):
count[ind] = n // val
n = n % val
return count
print(change_money(change,375))
分数背包问题
有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
#分数背包问题
goods = [(100,20),(60,10),(120,30)]
goods.sort(key = lambda x:x[0]/x[1],reverse=True)
def fraction_bookpack(goods,w):
count = [0 for _ in range(len(goods))]
for ind,(val,weight) in enumerate(goods):
if w >= weight:
count[ind] = 1
w -= weight
else:
count[ind] = w / weight
w = 0
return count
print(fraction_bookpack(goods,60))
数字拼接问题
给出N个数组(2<=N<=9),每个数字长度是2-6位,每一位是1-4。 如果某个数字尾数和其他的数头相同,即可拼接成一个新的 数字。 如:123,234 可以拼接成1234。 连接成的新数字可以和其他未使用过的数字再按照规则连接成为新的数字。
#数字拼接问题
from functools import cmp_to_key
a = [12,456,123,121,98,87,7]
def change_wz(x,y):
if x+y > y+x:
return -1
elif x + y < y + x:
return 1
else:
return 0
def count_pj(li):
li = list(map(str,li))
li.sort(key = cmp_to_key(change_wz))
return ''.join(li)
print(count_pj(a))
活动安排问题
活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合,是可以用贪心算法有效求解的很好例子。该问题要求高效地安排一系列争用某一公共资源的活动。贪心算法提供了一个简单、漂亮的方法使得尽可能多的活动能兼容地使用公共资源。
#活动安排问题
a = [(1,3),(2,6),(3,5),(5,8),(6,9),(8,11)]
a.sort(key=lambda x:x[1])
def activity_ap(li):
r = [li[0]]
for i in range(1,len(li)):
if li[i][0] >= r[-1][1]:
r.append(li[i])
return r
print(activity_ap(a))
斐波那契
因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称“兔子数列”,其数值为:1、1、2、3、5、8、13、21、34……在数学上,这一数列以如下递推的方法定义:F(0)=1,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)。
#递归实现
def feibonaque(n):
if n == 1 or n == 2:
return 1
else:
return feibonaque(n-1) + feibonaque(n-2)
#非递归实现
def feibonoque2(n):
a = [0,1,1]
if n <= 2:
return a[n]
if n > 2:
for i in range(n-2):
v = a[-1] + a[-2]
a.append(v)
return a[-1]
print(feibonoque2(100))
钢条切割问题
钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表pi(i=1,2,…n),求切割钢条方案,使得销售收益rn最大。 注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。 长度为n英寸的钢条共有2n-1种不同的切割方案,因为在距离钢条左端i (i=1,2,…n)英寸处,总是可以选择切割或不切割。 如果一个最优解将钢条切割为k段(对某个),那么最优切割方案,将钢条切割为长度分别为i1,i2...ik的小段得到的最大收益为。 对于,。 其中,pn对应不切割,对于每个i=1,2,…,n-1,首先将钢条切割为长度为i和n-i的两段,接着求解这两段的最优切割收益ri和rn-i(每种方案的最优收益为两段的最优收益之和)。
#钢条切割问题
p = [0,1,3,5,7,9,11,13]
def qg1(p,n):
if n == 0:
return n
else:
res = p[n]
for i in range(1,n):
res = max(res,qg1(p,i)+qg1(p,n-i))
return res
#钢条切割问题,自上而下
p = [0,1,3,5,7,9,11,13]
def qg2(p,n):
if n == 0:
return n
else:
res = p[n]
for i in range(1,n):
res = max(p[n],p[i]+qg2(p,n-i))
return res
#钢条切割问题,自下而上
p = [0,1,5,6,7,9,11,13]
def qg3(p,n):
a = [0]
for i in range(1,n+1):
res = 0
for j in range(1,i+1):
res = max(res,p[j]+a[i-j])
a.append(res)
return a[n]
print(qg3(p,7))
#钢条切割问题
p = [0,1,5,6,7,9,11,13]
def qg4(p,n):
a = [0]
b = [0]
for i in range(1,n+1):
res = 0
val = 0
for j in range(1,i+1):
if res < p[j] + a[i - j]:
res = p[j] + a[i - j]
val = j
a.append(res)
b.append(val)
return a[n],b
def qgcd(p,n):
m = []
a,b = qg4(p,n)
while n > 0:
m.append(b[n])
n -= b[n]
return m
print(qgcd(p,7))
最长公共子序列
一个数列S
,如果分别是两个
或多个已知数列
的子序列
,且是所有符合此条件序列中最长
的,则 S
称为已知序列的最长公共子序列
。
例如:输入两个字符串BDCABA
和 ABCBDAB
,字符串 BCBA
和BDAB
都是是它们的最长公共子序列
,则输出它们的长度4
,并打印任意一个子序列. (Note:
不要求连续)
判断字符串相似度的方法之一 - 最长公共子序列越长,越相似。
#最长公共子序列
def lsc(x,y):
m = len(x)
n = len(y)
l = [[0 for _ in range(n+1)] for _ in range(m+1)]
for i in range(1,m+1):
for j in range(1,n+1):
if x[i-1] == y[j-1]:
l[i][j] = l[i-1][j-1] + 1
else:
l[i][j] = max(l[i-1][j],l[i][j-1])
else:
return l[m][n]
def lsc1(x,y):
m = len(x)
n = len(y)
l = [[0 for _ in range(n+1)] for _ in range(m+1)]
v = [[0 for _ in range(n+1)] for _ in range(m+1)]
for i in range(1,m+1):
for j in range(1,n+1):
if x[i-1] == y[j-1]:
v[i][j] = 1
l[i][j] = l[i-1][j-1] + 1
elif l[i-1][j] > l[i][j-1]:
v[i][j] = 2
l[i][j] = l[i-1][j]
else:
v[i][j] = 3
l[i][j] = l[i][j-1]
return l[m][n],v
def way(x,y):
m = len(x)
n = len(y)
w = []
val,lu = lsc1(x,y)
while m > 0 and n > 0:
if lu[m][n] == 1:
w.append(x[m-1])
m -= 1
n -= 1
elif lu[m][n] == 2:
m -= 1
else:
n -= 1
return ''.join(reversed(w))
print(way('abcdsfg','abcdasda'))
欧几里得算法
指用于计算两个非负整数a,b的最大公约数。应用领域有数学和计算机两个方面。计算公式gcd(a,b) = gcd(b,a mod b)。
两个整数的最大公约数是能够同时整除它们的最大的正整数。辗转相除法基于如下原理:两个整数的最大公约数等于其中较小的数和两数相除余数的最大公约数。
#欧几里得算法
def zdgys1(x,y):
if y == 0:
return x
else:
print(x%y)
return zdgys1(y,x%y)
print(zdgys1(12,16))
def zdgys2(x,y):
while y > 0:
v = x % y
x = y
y = v
return x
def zxgbs(x,y):
a = zdgys1(x,y)
v = x / a * y
return v
print(zxgbs(12,16))
def add(x,y):
a = zdgys1(x,y)
x /= a
y /= a
return '{0}/{1}'.format(x,y)
print(add(12,16))
class node:
def __init__(self,data):
self.data = data
self.next = None
class line:
def __init__(self,node):
self.head = node
self.tail = self.head
def e_add(self,data):
a = self.tail
a.next = data
self.tail = data
def f_add(self,data):
a = self.head
b = data
b.next = a
self.head = b
def a_insert(self,x,y):
if x == 0:
self.f_add(y)
else:
a = self.head
while x - 1:
a = a.next
b = y
b.next = a.next
a.next = b
def binary(self):
a = self.head
while a:
print(a.data)
a = a.next
a = line(node(1))
a.e_add(node(8))
a.f_add(node(2))
a.a_insert(1,node(3))
a.e_add(node(7))
a.binary()
class Stack:
def __init__(self):
self.stack = []
def a_add(self,data):
self.stack.append(data)
def push(self):
return self.stack.pop(0)
a = Stack()
a.a_add(18)
a.a_add(38)
a.a_add(8)
print(a.push())