2.4归并排序:
2.4.1算法介绍:
归并排序是建立在归并操作上的一种有序的排序算法,该算法是采用分治法的一个非常经典的应用(所谓‘分’就是将问题分成一些小的问题然后递归求解,而‘治’就是将分的阶段的所呈现的结果‘合并’在一起,即分而治之)
2.4.2算法举例:
对于一个包含8个元素的数组,可以将其分为两个大小各位4的子数组,对这两个数组进行排序,然后合并他们,生成有序数组同样,可以将每个大小为4的子数组进一步划分为两个大小为2的子数组,然后对这些子数组进行排序和合并,不断进行此操作,直到被分成最小的单位为止。这一过程就称为“归并排序”
def merge_sort(list):
if len(list)==1:
return list
middle=len(list)//2
#拆分成左右两侧字符串
left=list[:middle]
right=list[middle:]
#不断进行拆分,一直分到只有一个元素为止
l1=merge_sort(left)
l2=merge_sort(right)
return merge(l1,l2)
def merge(left,right):
result=[] #定义一个空的数组,用于存放拍好序的元素
while len(left)>0 and len (right)>0:
if left[0]<=right[0]:
result.append(left.pop(0))#pop()方法,用于移除列表中的元素
else:
result.append(right.pop(0))
result+=left
result+=right
return result
if __name__=='__main__':
list=[9,5,1,7,3,6,4]
l=merge_sort(list)
print(l)
结果:
[1, 3, 4, 5, 6, 7, 9]
2.4.3代码分析:
merge_sort将输入的数组进行拆分,不断递归当进行拆分,直到不能再分时(即len(list==1)。之后就是合并的过程(即merge函数),将最后一行最后分开的数排序后绑定,然后再将倒数第二行在进行合并,之后会变成两个排序好的数列。最后将每个数列的最小值拿出进来进行比较,将较小的数放入到result[]中,在进行下一次比较,当有一个数列为空时,直接将另一个数列添加到result[]中,最后result[]就是排序好的数组
2.4.3算法分析:
归并排序是稳定排序,它也是一种十分高效的排序,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。
2.5快速排序:
2.5.1算法介绍:
它是由Hoare在1962年开发的。快速排序在某些地方与归并排序类似,因为它的完成过程也是将数组拆分成两部分,然后对每部分进行排序。但是在快速排序中,在划分数组时,是将所有小于某个基准数的元素放到该项目之前,将所有大于该基准数的元素放到该项目之后(可以任意指定初值,但为了方便起见,直接选择第一个元素为基准数)
def quick_sort(array,low,high):
if low<high:
key_index=sub_sort(array,low,high)
quick_sort(array,low,key_index) #递归前半部分
quick_sort(array,key_index+1,high) #递归后半部分
return array
def sub_sort(array,low,high):
key=array[low]
while low<high:
while low<high and array[high]>=key:
high-=1
if low<high:
array[low],array[high]=array[high],array[low]
else:
break
while low<high and array[low]<key:
low+=1
if low<high:
array[high], array[low] = array[low], array[high]
else:
break
return low
if __name__=="__main__":
array=[9,5,7,6,1,3,4,8,2]
print("未排序前",array)
array_result=quick_sort(array,0,len(array)-1)
print("排序后",array_result)
结果:
未排序前 [9, 5, 7, 6, 1, 3, 4, 8, 2]
排序后 [1, 2, 3, 4, 5, 6, 7, 8, 9]
2.5.2代码分析:
先定义个基准数(key=array[low]),接着定义数列的头和尾(low和high)先从low下标位置开始,high端从右向左边遍历,查找不大于基准数的元素,然后交换他们。然后从high下标位置开始,向右边遍历,查找不小于基准数的元素,不断进行这样的交换,直到不能交换为止,返回排好序的数列。
2.5.3算法分析:
在最差的情况下,划分由n个元素构成的数组需要进行n次比较和n次移动,所以划分所需时间为O(n),最差情况下,每次基准数会将数组划分为一个大的子数组和一个空数组,然而这个大的子数组的规模是在上次划分的子数组的规模减1,。该算法需要(n-1)+(n-2)+...+2+1=O(n^2)。
在平均情况下,我们认为数组中的数字不具有任何规律,所以基准数在数组中返回的取值1至n中的任意数值概率相同。因此,这里获得的均值是对每种可能顺序相同次数的排序所得到的平均时间。因此,和归并排序的分析特别相似,为O(nlogn),推导过程过于繁琐和深奥,在此就不证明了,有兴趣的同学自行查找。
2.6选择排序:
2.6.1算法介绍:
选择排序是一种简单直观的排序算法,初始时在序列中找到最大(小)值,放在序列的起始位置作为已排序的序列,然后从剩余的序列中继续寻找最小(大)的元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)的元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置。最后仅需一次交换操作即可将其放到合适的位置。
def select_sort(list):
length=len(list)
for i in range(length-1):
smallest=i
for j in range(i+1,length):
if list[j]<list[smallest]:
list[smallest],list[j]=list[j],list[smallest]
else:
continue
return list
if __name__=="__main__":
list=[5,4,9,1,7,2,3,8,6]
print("result:",select_sort(list))
结果:
result: [1, 2, 3, 4, 5, 6, 7, 8, 9]
2.6.2程序分析:
需要双层循环,第一层将循环的初值定义为最小值(smallest=i),第二层循环是将数组剩下的元素依次与smallest比较,如果小于smallest就将其与smallest进行交换,以此类推,直到
2.6.3算法分析:
外层循环一共循环len(list)-1次循环,而内层循环需要比较(n-1)+(n-2)+...+2+1=n^2/2,舍去最高项系数,所以其时间复杂度为O(n^2)。
2.7矩阵的Strassen算法:
2.7.1算法介绍:
Volker Strassen是一位出生于1936年的德国数学家。他因为在概率论上的工作而广为人知,但是在计算机科学和算法领域,他却因为矩阵相乘算法而被大部分人认识,这个算法目前仍然是比通用矩阵相乘算法性能好的主要算法之一。
在计算二阶矩阵时,斯特拉斯算法将乘法次数由八次减少到了7次,但是增加了加,减的运算次数。这个差值1虽然看起来不起眼,但对于程序的时间复杂度却有很大的提高,而且是数据越大效果越明显。
x1=(a11+a22)*(b11+b22)
x2=(a21+a22)*b11
x3=a11*(b12-b22)
x4=a22*(b21-b11)
x5=(a11+a12)*b22
x6=(a21-a11)*(b11+b12)
x7=(a12-a22)*(b21+b22)
c11=x1+x4-x5+x7
c12=x3+x5
c21=x2+x4
c22=x1+x3-x2+x6
def divide11(a, n):
k = int(n / 2)
a11 = [[[0] for i in range(0, k)] for j in range(0, k)]
for j in range(0, k):
a11[i][j] = a[i][j]
return a11
def divide12(a, n):
k = int(n / 2)
a12 = [[[0] for i in range(0, k)] for j in range(0, k)]
for i in range(0, k):
for j in range(0, k):
a12[i][j] = a[i][j + k]
return a12
def divide21(a, n):
k = int(n / 2)
a21 = [[[0] for i in range(0, k)] for j in range(0, k)]
for i in range(0, k):
for j in range(0, k):
a21[i][j] = a[i + k][j]
return a21
def divide22(a, n):
k = int(n / 2)
a22 = [[[0] for i in range(0, k)] for j in range(0, k)]
for i in range(0, k):
for j in range(0, k):
a22[i][j] = a[i + k][j + k]
return a22
def Merge(a11, a12, a21, a22, n):
k = int(2 * n)
a = [[[0] for i in range(0, k)] for j in range(0, k)]
for i in range(0, n):
for j in range(0, n):
a[i][j] = a11[i][j]
a[i][j + n] = a12[i][j]
a[i + n][j] = a21[i][j]
a[i + n][j + n] = a22[i][j]
return a
def Plus(a, b, n):
c = [[[0] for i in range(0, n)] for j in range(0, n)]
for i in range(0, n):
for j in range(0, n):
c[i][j] = a[i][j] + b[i][j]
return c
def Minus(a, b, n):
c = [[[0] for i in range(0, n)] for j in range(0, n)]
for i in range(0, n):
for j in range(0, n):
c[i][j] = a[i][j] - b[i][j]
return c
def Strassen(a, b, n):
k = n
if k == 2:
d = [[[0] for i in range(2)] for i in range(2)]
d[0][0] = a[0][0] * b[0][0] + a[0][1] * b[1][0]
d[0][1] = a[0][0] * b[0][1] + a[0][1] * b[1][1]
d[1][0] = a[1][0] * b[0][0] + a[1][1] * b[1][0]
d[1][1] = a[1][0] * b[0][1] + a[1][1] * b[1][1]
return d
else:
a11 = divide11(a, n)
a12 = divide12(a, n)
a21 = divide21(a, n)
a22 = divide22(a, n)
b11 = divide11(b, n)
b12 = divide12(b, n)
b21 = divide21(b, n)
b22 = divide22(b, n)
k = int(n / 2)
m1 = Strassen(a11, Minus(b12, b22, k), k)
m2 = Strassen(Plus(a11, a12, k), b22, k)
m3 = Strassen(Plus(a21, a22, k), b11, k)
m4 = Strassen(a22, Minus(b21, b11, k), k)
m5 = Strassen(Plus(a11, a22, k), Plus(b11, b22, k), k)
m6 = Strassen(Minus(a12, a22, k), Plus(b21, b22, k), k)
m7 = Strassen(Minus(a11, a21, k), Plus(b11, b12, k), k)
c11 = Plus(Minus(Plus(m5, m4, k), m2, k), m6, k)
c12 = Plus(m1, m2, k)
c21 = Plus(m3, m4, k)
c22 = Minus(Minus(Plus(m5, m1, k), m3, k), m7, k)
c = Merge(c11, c12, c21, c22, k)
return c
a = [[1,2,3,4],[5,6,7,8],[4,3,2,1],[8,7,6,5]]
b = [[1,2,3,4],[5,6,7,8],[4,3,2,1],[8,7,6,5]]
print(Strassen(a, b, 4))
结果:
[[55, 51, 47, 43], [127, 123, 119, 115], [35, 39, 43, 47], [107, 111, 115, 119]]
2.7.2算法分析:
Strassen算法仅仅比通用矩阵相乘算法好一点点。通用矩阵相乘算法时间复杂度是O(n^3),然而Strassen算法复杂度则是O(n^2.80)。另一方面,Strassen算法并不比n^3复杂度的通用矩阵相乘算法快很多。这很重要,因为对于一个很小的n(通常n<45)来说,通用矩阵相乘算法在实践中往往是更好的选择。然而,你可以从以下的图片中看到,对于n>100的情况来说,这两个算法的差别还是相当大的