zl程序教程

您现在的位置是:首页 >  其他

当前栏目

大学课程 | 《算法分析与设计》笔记

2023-04-18 14:57:13 时间

大三算法设计与分析笔记总结与知识点整理

笔记总结

第一章 算法引论

1.1 算法与程序

算法定义:解决问题的方法或过程

算法的性质:

(1)输入:有零个或多个外部量作为算法的输入

(2)输出:算法产生至少一个量作为输出

(3)确定性:组成算法的每条指令是清晰的,无歧义的

(4)有限性:算法中每条指令的执行次数有限,执行每条指令的时间也有限

有时还会加入通用性或可行性

程序的定义:是算法用某种程序设计语言的具体实现。

程序与算法的区别:程序可以不满足算法的第四点性质即有限性。例如操作系统,是在无限循环中执行的程序。

1.2 表达算法的抽象机制

为了将顶层算法与底层算法隔开,使二者在设计时不互相牵制,互相影响,必须对二者的接口进行抽象。让底层只通过接口为顶层服务,顶层也只通过接口调用底层运算。这个接口就是抽象数据类型(ADT)。

1.3 描述算法

有多种方式,如:自然语言方式,表格方式,高级程序语言方式等…

1.4 算法复杂性分析

算法分析的目的:分析算法占用计算机资源的情况,对算法做出比较和评价,设计出更好的算法

算法的复杂性是算法运行时所需的计算机资源的量,需要时间资源的量称为时间复杂性,需要空间资源的量称为空间复杂性。

C=F(N,I,A),用N,I,A分别表示算法要解的问题的规模,算法的输入和算法本身,F表示是上诉N,I,A的确定的三元函数,C表示复杂性

一般只考虑3种情况下的时间复杂性,即最坏情况,最好情况,平均情况

实践表明,可操作性最好且最有实际价值的是最坏情况下的时间复杂性。

复杂性渐进性态:

EXCEL
对于T(N),如果存在~T(N),使得当N→∞时有(T(N)-~T(N))/T(N)→0,那么就说~T(N)是T(N)当N→∞时的渐进性态。

如果存在正的常数C和自然数N0,使得当N≥N0时有f(N)≤Cg(N),则称函数f(N)当N充分大时上有界,且g(N)是它的一个上界,记为f(N)=O(g(N))。这时还说f(N)的阶不高于g(N)的阶。

对于符号O,有如下运算规则:

O(f)+O(g)=O(max(f,g))

O(f)+O(g)=O(f+g)

O(f)O(g)=O(fg)

如果g(N)=O(f(N)),则O(f)+O(g)=O(f)

O(Cf(N))=O(f(N)),其中C是一个正的常数

f=O(f)

第二章 递归与分治策略

2.1 递归的概念

直接或间接地调用自身的算法称为递归算法。用函数自身给出定义的函数称为递归函数

递归函数的两个要素:边界条件和递归方程

阶乘函数:

C++
#include<iostream>
using namespace std;

int factorial(int n){
    if(n==0) return 1;
    else{
        return n*factorial(n-1);
    }
}

Fibonacci数列:

C++
#include<iostream>
using namespace std;

int fibonacci(int n){
    if(n<=1) return 1;
    else return fibonacci(n-1)+fibonacci(n-2);
} 

hanoi塔:

PYTHON
def hanoi(n,a,b,c):
    #将a上的n个圆盘经过c移动到b
    if(n>0):
        hanoi(n-1,a,c,b)
        move(a,b)
        hanoi(n-1,c,b,a)

递归算法的优点:结构清晰,可读性强,容易用数学归纳法来证明算法的正确

递归算法的缺点:运行效率低,无论是耗费的计算时间还是占用的存储空间都比非递归算法多

消除递归的方法:①采用一个用户定义的栈来模拟系统的递归调用工作栈,从而达到将递归算法改为非递归算法的目的②用递推来实现递归函数

2.2 分治法的基本思想

分治法的基本思想:将一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同。递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

分治法的适用条件:

①该问题的规模缩小到一定程度容易解决。

②该问题可以分解为若干个规模较小的相同问题。即该问题具有最优子结构性质。

③该问题分解出的子问题的解可以合并为该问题的解。

④子问题间不包含公共的子问题(各子问题相互独立)

分治法的步骤:

划分

解决

合并

2.3 二分搜索技术

PYTHON
def binarySearch(a,x):
    #a是数组,x是要搜索的数
    a=sorted(a)
    #a要求有序(从小到大)
    n=len(a)
    left,right=0,n-1
    while(left<=right):
        middle=(left+right)//2
        if(x==a[middle]):
            return middle
        elif(x>a[middle]):
            left=middle+1
        else:
            right=middle-1
    #未找到
    return -1

最坏情况下,时间复杂度是O(logn)

2.4 大整数乘法

设x和y都是n位的二进制整数,现在要计算他们的乘积xy。如果直接相乘,需要O(n^2)步,而其分治法是:将n位二进制整数X和Y都分为2段,每段的长为n/2位:

MATHEMATICA
X=[A][B],Y=[C][D],其中X,Y有n位;A,B,C,D均有n/2位
由此可以得到:
X=A*2^(n/2)+B , Y=C*2^(n/2)+D

XY=(A*2^(n/2)+B)(C*2^(n/2)+D)
  =A*C*2^n+(A*D+C*B)*2^(n/2)+B*D
  =A*C*2^n+((A-B)(D-C)+A*C+B*D)*2^(n/2)+B*D

最后一个式子看起来似乎复杂了,但是它仅需做3次n/2位整数的乘法,6次加减法和2次移位

2.5 Strassen矩阵乘法

对于方阵(n*n)A,B,C,有C=A*B,将它们都分块成4个大小相等的子矩阵,每个子矩阵都是(n/2)*(n/2)的方阵

2.7 合并排序

PYTHON
def merge(arr,left,mid,right):
    #left,right为需要合并的数组范围
    #mid为中间下标,左边比中值小,右边比中值大
    i=left
    j=mid+1
    #复制一个临时数组
    aux=arr[:]
    for k in range(left,right+1):
        #如果左指针超过mid,即右边还有剩余
        if(i>mid):
            arr[k]=aux[j]
            j=j+1
        #如果右指针超过right,即左边还有剩余
        elif(j>right):
            arr[k]=aux[i]
            i=i+1
        #如果左边小,则左边合并
        elif(aux[i]<aux[j]):
            arr[k]=aux[i]
            i=i+1
        #如果右边小
        else:
            arr[k]=aux[j]
            j=j+1


def mergeSort(arr,left,right):
    #如果已经遍历完
    if(left>=right):
        return ;
    #取中值,拆成左右两边
    mid=(left+right)//2
    #对左半边进行归并排序
    mergeSort(arr,left,mid)
    #对右半边进行归并排序
    mergeSort(arr,mid+1,right)
    #合并算法
    merge(arr,left,mid,right)

最坏情况下的时间复杂度为O(nlogn)

2.8 快速排序

步骤:分解,递归求解,合并

PYTHON
def quicksort(arr,low,high):
    if low<high :
        index=getindex(arr,low,high)
        quicksort(arr,low,index-1)
        quicksort(arr,index+1,high)

#快速排序算法核心
#作用:将小于基准值的数放在其左边,大于在右边
def getindex(arr,low,high):
    #默认第一个数字为标准值
    temp=arr[low]
    #当未遍历完,即左右指针未相遇
    while(low<high):
        #如果右边大于标准值,右指针左移
        while((low<high)and(arr[high]>=temp)):
            high=high-1
        #此时右指针对应值小于标准值,将其复制给左指针位置
        arr[low]=arr[high]
        #当左边小于标准值,左指针右移
        while((low<high)and(arr[low]<=temp)):
            low=low+1
        #此时左指针对应值大于标准值,将其复制给右指针位置
        arr[high]=arr[low]
    #将标准值赋值给左右指针相遇的位置
    arr[low]=temp
    #此时low左边全部小于等于arr[low],low右边全部大于等于arr[low]
    return low

快排平均情况下的时间复杂度是O(nlogn),最坏情况下的时间复杂度是O(n^2)

2.9 线性时间选择

找出一组数中,第X大(小)的数

采用了随机划分算法

2.10 最近点对问题

时间复杂度分析O(nlogn)

PYTHON
"""
Copyright: Copyright (c) 2019
Author: Justlovesmile
Title: 最近点对问题
"""

#按x坐标排序的点
class Point1:
    #x,y为坐标,id为序号
    def __init__(self,xx,yy,index):
        self.x=xx
        self.y=yy
        self.id=index


#按y坐标排序的点
class Point2(Point1):
    #x,y为坐标,id为该点按x排序时的序号
    def __init__(self,xx,yy,index):
        self.x=xx
        self.y=yy
        self.id=index
        
#表示输出的平面点对
class Pair:
    #a,b为点,dist为距离
    def __init__(self, aa, bb,dd):
        self.a=aa
        self.b=bb
        self.dist=dd
    
#求平面上任意两点u,v的距离
def dist(u,v):
    dx=u.x-v.x
    dy=u.y-v.y
    return dx*dx+dy*dy

#归并排序
def merge(S,order,left,mid,right):
    i=left
    j=mid+1
    aux=S[:]
    #按x排序
    if(order=='x'):
        for k in range(left,right+1):
            if(i>mid):
                S[k]=aux[j]
                j=j+1
            elif(j>right):
                S[k]=aux[i]
                i=i+1
            elif(S[i].x<aux[j].x):
                S[k]=aux[i]
                i=i+1
            else:
                S[k]=aux[j]
                j=j+1
    #按y排序
    elif(order=='y'):
        for k in range(left,right+1):
            if(i>mid):
                S[k]=aux[j]
                j=j+1
            elif(j>right):
                S[k]=aux[i]
                i=i+1
            elif(S[i].y<aux[j].y):
                S[k]=aux[i]
                i=i+1
            else:
                S[k]=aux[j]
                j=j+1

#归并排序
def mergeSort(S,x,left,right):
    if(left>=right):
        return ;
    mid=(left+right)//2
    mergeSort(S,x,left,mid)
    mergeSort(S,x,mid+1,right)
    merge(S,x,left,mid,right)

#计算最接近点对
def closePair(S,Y,Z,l,r):
    #两个点
    if(r-l==1):
        return Pair(S[l],S[r],dist(S[l],S[r]))
    #三个点
    if(r-l==2):
        d1=dist(S[l],S[l+1])
        d2=dist(S[l+1],S[r])
        d3=dist(S[l],S[r])
        if((d1<=d2)and(d1<=d3)):
            return Pair(S[l],S[l+1],d1)
        if(d2<=d3):
            return Pair(S[l+1],S[r],d2)
        else:
            return Pair(S[l],S[r],d3)
    #多于三个点
    m=(l+r)//2
    f=l
    g=m+1
    for i in range(l,r+1):
        if(Y[i].id>m):
            Z[g]=Y[i] 
            g=g+1
        else:
            Z[f]=Y[i]
            f=f+1
    #递归求解
    best = closePair(S,Z,Y,l,m)
    right = closePair(S,Z,Y,m+1,r)
    #选最近的点对
    if(right.dist<best.dist):
        best=right
    merge(Y,"y",l,m,r)

    k=l
    #距离中线最近的
    for i in range(l,r+1):
        if(abs(S[m].x-Y[i].x)<best.dist):
            Z[k]=Y[i]
            k=k+1
    for i in range(l,k):
        for j in range(i+1,k):
            if(Z[j].y-Z[i].y<best.dist):
                dp=dist(Z[i],Z[j])
                if(dp<best.dist):
                    best=Pair(S[Z[i].id],S[Z[j].id],dp)
    #返回最近点对
    return best


#一维点集
def cpair1(S):
    #先设为正无穷
    min_d=float("inf")
    S=sorted(S)
    for i in range(1,len(S)):
        dist=abs(S[i]-S[i-1])
        if(dist<min_d):
            pair=[]
            min_d=dist
            pair.append([S[i-1],S[i]])
        elif(dist==min_d):
            pair.append([S[i-1],S[i]])
    print("Closest point:")
    for i in pair:
        print(i,end=" ")
    print("
Min_dist:",min_d)

#二维点集
def cpair2(S):
    Y=[]
    n=len(S)
    if(n<2):
        return ;
    #按X坐标排序
    mergeSort(S,"x",0,n-1)
    #以Point2类型赋值
    for i in range(n):
        p=Point2(S[i].x,S[i].y,i)
        Y.append(p)
    #按y坐标排序
    mergeSort(Y,"y",0,n-1)
    Z=Y[:]
    return closePair(S,Y,Z,0,n-1)
    
    
def main():
    #输入一维还是二维点平面
    model=input("Please choose model of '1' or '2':").split()[0]
    S=[]
    #一维点对
    if(model == '1'):
        point=input("Please input a group of number in order:
").split()
        #如果输入空点对
        if(len(point)==0):
            raise ValueError("您输入了空点对!")
        #转换类型
        for i in range(len(point)):
            S.append(int(point[i]))
        #输出最近点对
        cpair1(S)
    #二维点对
    elif(model == '2'):
        #输入点数
        n=int(input("Please input how many points:
"))
        if(n==0):
            raise ValueError("您输入了0个点!")
        for i in range(n):
            words=f"please input the No.{i+1} point (like: x y) in x order:"
            point=input(words).split()
            p=Point1(int(point[0]),int(point[1]),i)
            S.append(p)
        #找到最近的一对点对
        best=cpair2(S)
        print(f"The closest points are ({best.a.x},{best.a.y}) and ({best.b.x},{best.b.y}).")
        print(f"And the distance is {best.dist**0.5}.")
    else:
        raise ValueError("没有这个选项!")

if __name__ == "__main__":
    #异常处理
    try:
        main()
    except Exception as e:
        print("您的输入不合法!出错信息如下:")
        print(e)

第三章 动态规划

动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解,但与分治法不同的是,适用于动态规划法求解的问题,经分解得到的子问题往往不是相互独立的。

动态规划算法的步骤:

①找出最优解的性质,并刻画其结构特征

②递归地定义最优值

③以自底向上的方式计算出最优值

④根据计算最优值时得到的信息,构造最优解

动态规划算法的两个基本要素:最优子结构与重叠子问题

最优子结构性质:问题的最优解包含子问题的最优解

重叠子问题:在用递归算法自顶向下求解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次

无后效性:一个问题被划分阶段后,阶段I中的状态只能由I+1中的状态通过状态转移方程得来,与其他状态没有关系,特别是与未发生的状态没有关系

动态规划算法有一个变形方法——备忘录方法,这种方法不同于动态规划算法“自底向上”的填充方向,而是“自顶向下”的递归方向,为每一个解过的子问题建立一个记录项(备忘录)以备需要时查看,也可以避免相同子问题的重复求解

3.1 矩阵连乘问题

m(i,j)是指从A[i]到A[j](1≤i≤j≤n)的最少数乘次数

矩阵可乘条件:A的列数等于B的行数,若A是一个p×q矩阵,B是一个q×r矩阵,则AB总共需要pqr次数乘。

PYTHON
"""
Copyright: Copyright (c) 2019
Author: Justlovesmile
Title: 矩阵连乘问题
"""

#计算最优值
def matrixChain(p,m,s):
    #m[i][j]表示A[i]到A[j]所需的最少数乘次数
    #s[i][j]表示A[i]到A[j]所需的最少数乘法对应的分隔位置
    n=len(p)-1
    for r in range(2,n+1):
        for i in range(1,n-r+2):
            #沿斜线方向递进
            j=r+i-1
            m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j]
            s[i][j]=i
            k=i+1
            #寻找i到j间最优分隔k
            while(k<j):
                t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]
                if(t<m[i][j]):
                    m[i][j]=t
                    s[i][j]=k
                k=k+1

#根据S递归输出
def traceback(s,i,j):
    if(i==j):
        print(f"A[{i}]",end="")
        return ;
    print("(",end="")
    traceback(s,i,s[i][j])
    traceback(s,s[i][j]+1,j)
    print(")",end="")



def main():
    p=[]
    y=0
    #输入矩阵个数
    n=input("Please iuput the number of matrix:").split()
    #异常处理
    if(len(n)==0):
        raise ValueError("您输入了空矩阵!")
    n=int(n[0])
    #输入每个矩阵的信息
    for i in range(n):
        s=input(f"Input No.{i+1} Matrix size,eg:5 5
").split()
        #判断是否能与前一项相乘
        if(len(p)>=1):
            if(y!=int(s[0])):
                raise ValueError("您输入的矩阵不能相乘!")
        x,y=int(s[0]),int(s[1])
        p.append(x)
    p.append(y)
    m=[]
    s=[]
    for i in range(n+1):
        m.append([0]*(n+1))
        s.append([0]*(n+1))
    matrixChain(p,m,s)  
    traceback(s,1,n)
    print("
Count times:",m[1][n])
    

    

if __name__ =="__main__":
    #异常处理
    try:
        main()
    except Exception as e:
        print("您的输入不合法!出错信息如下:")
        print(e)

3.3 最长公共子序列

建立递归关系:

PYTHON
"""
Copyright: Copyright (c) 2019
Author: Justlovesmile
Title: 最长公共子序列问题
"""

def IcsLength(x,y,b):
    m=len(x)
    n=len(y)
    #初始化
    c=[]
    for j in range(m+1):
        c.append([0]*(n+1))
    #逐个比较
    for i in range(1,m+1):
        for j in range(1,n+1):
            #如果相等那么此时的最长公共长度为去除该位置的最长公共长度+1
            if(x[i-1]==y[j-1]):
                c[i][j]=c[i-1][j-1]+1
                #记录c[i][j]的值是第一类子问题的解得到的
                b[i][j]=1
            #如果对应位置不相等,则比较两个序列去掉这个不等值后哪边的最长子序列会更长
            elif(c[i-1][j]>=c[i][j-1]):
                c[i][j]=c[i-1][j]
                b[i][j]=2
            else:
                c[i][j]=c[i][j-1]
                b[i][j]=3
    return c[m][n]

#根据b[i][j]输出最长子序列
def Ics(i,j,x,b):
    if(i==0 or j==0):
        return ;
    #如果是第一类子问题的解,则说明该位置是公共部分
    if(b[i][j]==1):
        Ics(i-1,j-1,x,b)
        print(x[i-1],end="")
    #如果是第二类子问题的解,则说明此时Zk≠Xm
    elif(b[i][j]==2):
        Ics(i-1,j,x,b)
    #Zk≠Yn
    else:
        Ics(i,j-1,x,b)

def main():
    #输入字符串
    A=input("Please input No.1 Ics:").split()
    B=input("Please input No.2 Ics:").split()
    b=[]
    for i in range(len(A)+1):
        b.append([0]*(len(B)+1))
    print("The longest length:",IcsLength(A,B,b))
    Ics(len(A),len(B),A,b)

if __name__=="__main__":
    #异常处理
    try:
        main()
    except Exception as e:
        print("您的输入不会合法!出错信息如下:")
        print(e)

3.4 凸多边形最优三角剖分

和矩阵连乘相似

PYTHON
"""
Copyright: Copyright (c) 2019
Author: Justlovesmile
Title: 凸多边形最优三角剖分问题
"""
from isConvex import isConvex

#计算最优值
def minWeightTriangulation(n,t,s,v):
    #t[i][j]是凸子多边形vi-1,vi,...,vj的最优三角剖分对应的权函数值
    for r in range(2,n+1):
        for i in range(1,n-r+2):
            j=r+i-1
            t[i][j]=t[i+1][j]+weight(i-1,i,j,v)
            s[i][j]=i
            k=i+1
            #遍历i到j的所有边
            while(k<j):
                u=t[i][k]+t[k+1][j]+weight(i-1,k,j,v)
                if(u<t[i][j]):
                    t[i][j]=u
                    s[i][j]=k
                k=k+1

#根据s输出划分结果
def traceback(s,i,j):
    if(i==j):
        print(f"B[{i}]",end="")
        return ;
    print("(",end="")
    traceback(s,i,s[i][j])
    traceback(s,s[i][j]+1,j)
    print(")",end="")

#根据距离计算权重
def weight(i,j,k,v):
    return dist(i,j,v)+dist(i,k,v)+dist(k,j,v)

#计算距离
def dist(i,j,v):
    return (v[i][0]-v[j][0])**2+(v[i][1]-v[j][1])**2

def main():
    v=[]
    #可选择手动输入和使用默认值
    ans=input("Do you want to use default v[]:(y / n )")
    if(ans=="y" or ans=="Y"):
        v=[[6,1],[13,1],[16,4],[13,7],[6,7],[3,4]]
        graph="""-----@######@-------
----#--------#------
---#----------#-----
--@------------@----
---#----------#-----
----#--------#------
-----@######@-------
"""
        print(graph)
        for i in v:
            print(f"({i[0]},{i[1]})",end=" ")
    
    elif(ans=="n" or ans=="N"):
        n=int(input("Please input the number of points:
"))
        if(n==0):
            raise ValueError("您输入了0!")
        for i in range(n):
            a=input(f"Input X and Y of No.{i+1} point:(eg:X Y)
").split()
            v.append([int(a[0]),int(a[1])])
        
    else:
        raise ValueError("对不起没有这个选项!")
    #判断是不是图多边形
    if(not isConvex(v)):
        raise ValueError("您输入的不是凸多边形!请确认是否按顺序输入!")
    t=[]
    s=[]
    n=len(v)
    #初始化
    for i in range(n):
        t.append([0]*(n))
        s.append([0]*(n))
    minWeightTriangulation(n-1,t,s,v)
    traceback(s,0,n-1)

if __name__=="__main__":
    #异常处理
    try:
        main()
    except Exception as e:
        print("您的输入不合法!出错信息如下:")
        print(e)

判断是否为凸多边形

PYTHON
#判断是否为凸多边形
'''
计算直线表达式
param vertex1: 前一个顶点
param vertex2: 后一个顶点
return (type, param): 返回直线的类别及其描述参数
'''
def kb(vertex1, vertex2):
    x1 = vertex1[0]
    y1 = vertex1[1]
    x2 = vertex2[0]
    y2 = vertex2[1]
    
    if x1==x2:
        return (0, x1)      # 0-垂直直线
    if y1==y2:              
        return (1, y1)      # 1-水平直线
    else:
        k = (y1-y2)/(x1-x2)
        b = y1 - k*x1
        return (2, k, b)    # 2-倾斜直线

'''
判断是否为凸多边形
param vertexes: 构成多边形的所有顶点坐标列表,如[[0,0], [50, 0], [0, 50]]
return convex: 布尔类型,为True说明该多边形为凸多边形,否则为凹多边形
'''
def isConvex(vertexes):
    # 默认为凸多边形
    convex = True   
    
    # 多边形至少包含三个顶点
    l = len(vertexes)
    if l<3:
        raise ValueError("多边形至少包含三个顶点!")
    
    # 对每两个点组成的直线做判断
    for i in range(l):
        pre = i
        nex = (i+1)%l
        
        # 得到直线
        line = kb(vertexes[pre], vertexes[nex])
        
        # 计算所有点和直线的距离(可能为正也可能为负)
        if line[0]==0:
            offset = [vertex[0]-vertexes[pre][0] for vertex in vertexes]
        elif line[0]==1:
            offset = [vertex[1]-vertexes[pre][1] for vertex in vertexes]
        else:
            k, b = line[1], line[2]
            offset = [k*vertex[0]+b-vertex[1] for vertex in vertexes]
        
        # 计算两两距离的乘积,如果出现负数则存在两个点位于直线两侧,因此为凹多边形
        for o in offset:
            for s in offset:
                if o*s<0:
                    convex = False
                    break
            if convex==False:
                break
                    
        if convex==False:
            break
            
    # 打印判断结果
    if convex==True:
        print("该多边形为凸多边形!")
    else:
        print("该多边形为凹多边形!")
    
    return convex

3.9 0-1背包问题

其中m(i,j)是指背包容量为j,可选择物品为i,i+1,···,n时0-1背包问题的最优值

PYTHON
"""
Copyright: Copyright (c) 2019
Author: Justlovesmile
Title: 0-1背包问题--动态规划
"""

#跳跃点法
def knapsack_Pro(n,v,w,C,p,x):
    #head指向每一阶段跳跃点集合的开始
    head=[0 for i in range(n+1)]
    p[0][0],p[0][1]=0,0
    left,right,pnext,head[1]=0,0,1,1
    for i in range(n):
        k=left
        for j in range(left,right+1):
            if(p[j][0]+w[i]>C):
                break 
            y=p[j][0]+w[i]
            m=p[j][1]+v[i]
            #重量小于此数的跳跃点直接加进来,不会被支配
            while(k<=right and p[k][0]<y):
                p[pnext][0]=p[k][0]
                p[pnext][1]=p[k][1]
                pnext+=1
                k+=1
            #两个if判断新产生的点能否加入p
            if(k<=right and p[k][0]==y):
                if(m<p[k][1]):
                    m=p[k][1]
                k+=1
            if(m>p[pnext-1][1]):
                p[pnext][0]=y
                p[pnext][1]=m
                pnext+=1
            #取出可以支配的点
            while(k<=right and p[k][1]<=p[pnext-1][1]):
                k+=1

        #上面break后
        while(k<=right):
            p[pnext][0]=p[k][0]
            p[pnext][1]=p[k][1]
            pnext+=1
            k+=1
        
        left=right+1
        right=pnext-1
        head[i+1]=pnext
    traceback_Pro(n,w,v,p,head,x)

def traceback_Pro(n,w,v,p,head,x):
    j=p[head[n]-1][0]
    m=p[head[n]-1][1]
    print("max value:",m,"max weight:",j)
    for i in range(n)[::-1]:
        for k in range(head[i],head[i+1]-1):
            if(p[k][0]+w[i]==j and p[k][1]+v[i]==m):
                x[i]=1
                j=p[k][0]
                m=p[k][1]
                break


def knapsack(v,w,C,m):
    #m[i][j]指背包容量为j,可选择物品为i,i+1,...,n时的0-1背包问题的最优值
    n=len(v)-1
    #只剩一个物品的情况
    for j in range(C):
        m[n][j] = v[n] if j>=min(w[n]-1,C) else 0
    #普通情况
    for i in range(1,n)[::-1]:
        for j in range(C):
            m[i][j] = max(m[i+1][j],m[i+1][j-w[i]]+v[i]) if j>w[i]-1 else m[i+1][j]
    #第一件物品
    if(n>0):
        m[0][C-1]=m[1][C-1]
        if C-1>=w[0]:
            m[0][C-1]=max(m[0][C-1],m[1][C-1-w[0]]+v[0])

def traceback(m,w,C,x):
    c=C-1
    for i in range(len(w)-1):
        #没选物品i则x[i]=0
        if (m[i][c]==m[i+1][c]):
            x[i]=0
        else:
            x[i]=1
            c -= w[i]
    #对于最后一个物品
    x[len(w)-1]=1 if m[len(w)-1][c]>0 else 0

#输出格式
def cout(x,v,w):
    total_v=0
    total_w=0
    print("Choose:")
    for i in range(len(v)):
        if x[i]==1:
            print(f"No.{i+1} item: value is {v[i]} , weight is {w[i]}")
            total_v +=v[i]
            total_w +=w[i]
    print(f"total value: {total_v}")
    print(f"total weight: {total_w}")

def main():
    v=[]    #物品的价值列表
    w=[]    #物品的重量列表
    #输入物品数量
    n=input("Please input the number of items:
")
    if(n=="" or n=="0"):
        raise ValueError("您输入了空值或0!")
    else:
        n=int(n)
    x=[0 for i in range(n+1)]
    #选择两种算法(课本上的)
    ans=input("Choose Knapsack or Knapsack_Pro?(1 or 2)
").split()[0]
    if ans=='1':
        m=[]    #m(i,j)指背包容量为j,可选择物品为i,i+1,...,n时的0-1背包问题的最优值
        for i in range(n):
            item=input(f"please input No.{i+1} item's value(v) and weight(w):(eg:v w)
").split()
            v.append(int(item[0]))
            w.append(int(item[1]))
        C=int(input("Please input the max weight of bag:
"))
        if(C<=0):
            raise ValueError("背包容量不能≤0")
        for i in range(n):
            m.append([0]*C)
        knapsack(v,w,C,m)
        traceback(m,w,C,x)
        cout(x,v,w)
    elif ans=='2':
        for i in range(n):
            item=input(f"please input No.{i+1} item's value(v) and weight(w):(eg:v w)
").split()
            v.append(float(item[0]))
            w.append(float(item[1]))
        #初始化
        p=[[0 for i in range(2)]for j in range(n*n)]
        C=float(input("Please input the max weight of bag:
"))
        if(C<=0):
            raise ValueError("背包容量不能小于等于0")
        if(n==1):
            if(w[0]<=C):
                x[0]=1
            else:
                x[0]=0
        else:
            knapsack_Pro(n,v,w,C,p,x)
        for i in range(n):
            if(x[i]==1):
                print("choose: value:",v[i],"weight:",w[i])
    else:
        raise ValueError(f"您输入了{ans}没有该选项!")

if __name__=="__main__":
    #异常处理
    try:
        main()
    except Exception as e:
        print("您的输入不合法!出错信息如下:")
        print(e)

3.10 最优二叉搜索树

二叉搜索树:存储于每个结点中的元素x大于其左子树中任一结点所存储的元素,小于其右子树中任一结点所存储的元素

第四章 贪心算法

贪心算法:总是做出在当前看来最好的选择,也就是说贪心算法并不从整体最优考虑它所作出的选择只是在某种意义上的局部最优选择。

使用贪心算法需满足:

贪心选择性:指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到

最优子结构性质:当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质

贪心算法适合的问题:有n个输入,其解就由这n个输入满足某些事先给定的约束条件的某个子集组成,而把满足约束条件的子集称为该问题的可行解。显然可行解一般来说是不唯一的。那些使目标函数取极值的可行解,成为最优解。

贪心算法是一种分级处理方法,它首先根据题意,选取一种度量标准,然后按这种度量标准对这n个的输入排序,并按序依次输入,如果不满足条件,则不把此输入加到解当中。

贪心算法设计求解的核心问题:选择能产生问题最优解的最优量度标准。

贪心算法正确性的证明:

①证明算法所求的问题具有优化子结构

②证明算法所求解的问题具有贪心选择性

③算法按照②种的贪心选择性进行局部最优选择

4.2 活动安排问题

为了选择最多的相容活动,每次选择fi最小的活动,使能够选择更多的活动

度量标准:按照结束时间的非减序排列

如果有序,则O(n),如果无序,O(nlogn)

"""
Copyright: Copyright (c) 2019
Author: Justlovesmile
Title: 活动安排问题
"""

#活动类,每个活动包括开始时间和结束时间
class activity():
    def __init__(self,ss,ff):
        self.s=ss
        self.f=ff
    

def greedySelector(arr,a):
    n=len(arr)-1
    a[0]=True
    j=0
    count=1
    #满足开始时间大于上一个活动的结束时间的加入(设为True)
    #O(n)
    for i in range(1,n+1):
        if(arr[i].s>=arr[j].f):
            a[i]=True
            j=i
            count+=1
        else:
            a[i]=False
    return count

def main():
    activities=[]
    #输入数据
    n=int(input("please input the number of activities:
"))
    #异常处理
    if(n==0):
        raise ValueError("您输入了0!")
    print("Use greedy selector , activities should be ordered by the end_time.")
    for i in range(n):
        item=input("please input the begin-time and end-time:(eg: 3 6)
").split()
        if(len(item)!=2):
            raise ValueError("您输入的数据个数不合法!")
        s=activity(float(item[0]),float(item[1])) 
        activities.append(s)
    #以结束时间非减序排序
    activities=sorted(activities,key=lambda x:x.f)
    #初始化选择集合a
    a=[False for i in range(n)]
    count=greedySelector(activities,a)
    print("Maximum number of activities:",count)
    print("Choose:",a)

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print("您的输入不合法!出错信息如下:")
        print(e)

作者: Justlovesmile

链接: https://blog.justlovesmile.top/posts/16050.html

来源: Justlovesmile's BLOG

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。