软考:算法

19 阅读5分钟

分治算法求出的子问题是互相独立的;

贪心算法不追求最优解,只追求可行解,因此不具备最优子结构的特性;

动态规划算法追求最优子结构性质和重叠子问题性质;

回溯算法把问题解空间划分为图或者树,采用深度优先搜索策略遍历,遍历的过程中寻找可行解和最优解;

分支界限类似于回溯算法,它以广度优先遍历搜索解空间树。

1、回溯法

N皇后问题

image.png

1、按照行来摆放皇后,如果某一行摆放不下皇后时,那么就回溯,返回上一行重新摆放皇后的位置; 2、软考时只需要表示列,不需要表示行; 3、两个皇后处于斜线:两个皇后的行号和列号相差相同的值;

#include <stdio.h>
#define N 4;//皇后的个数
int q[N+1];//每一行存放皇后的列标
int check(int j){//检查第j个皇后的下标是否合法
    for(int i=1;i< j;i++){
        if(q[i]==q[j] || abs(i-j)==abs(q[i]-q[j]))
            return 0;
    }
    return 1;
}

//非递归
void queue(){
    for(int i=1;i<=N;i++){//初始时还没有放置
        q[i]=0;
    }
    int answer=0;
    int j=1;//第几个皇后
    while(j>=1){
        q[j]=q[j]+1;//皇后向后摆放一位
        while(q[j]<=N && check(j)==0){//判断第q[j]列位置是否合法
            q[j]=q[j]+1;//第q[j]列位置不合法,向后移动一位
        }
        if(q[j]<=N){//没有到达或者刚好到达边界
            if(j==N){//最后一个皇后选好位置了
                answer ++;
                for(int k=0;k<N;k++)printf("%d",q[k]);//输出结果
            }else{//还没有到最后一位皇后
                j++;
            } 
        }else{//超出列的最大值,说明这一行没有合法的位置
            q[j]=0;//将这一行归零
            j--;//回溯到上一行
        }

    }
}

//递归
int answer;
void queue(int j){
    int i;
    for(i=1;i<=N;i++){
        q[j]=i;//保证皇后可以取到一行的所有位置,一个一个遍历,挨个确定是否满足条件
    if(check(j)){//摆放位置合法
        if(j==N){//已经摆到了最后一个皇后
            answer++;
        }else{
            queue(j+1);//摆放下一个皇后
        }
    }
    }
}

2、分治法(归并排序,快速排序,分硬币)

把难以解决的大问题分解成规模较小的小问题 image.png

最大字段和问题

1、分解,将序列分解成两段S1+S2 有三种情况:

(1)最优解在S1中,S2=0;
(2)最优解在S2中,S1=0;
(3)最优解在S1+S2中,最终结果是S1+S2

2、合并,比较1中的三种情况的最大值为最终值

时间复杂度:O(nlogn)

int MaxSubSum(int *Array,int left,int right){
    int sum=0;
    if(left==right){//分解到单个整数,不继续分解,跳出递归的条件
        if(Array[left]<0)
            sum=0;
        else
            sum=Array[left];
    }else{//从中间划分子序列
        int center=(left+right)/2;
        int leftsum=MaxSubSum(Array,left,center);
        int rightsum=MaxSubSum(Array,center+1,right);
        
        //求左边序列的最小和
        int s1=0;
        int lefts=0;
        for(int i=center;i>=left;i++){
            lefts=lefts+Array[i];
            if(lefts>s1)s1=lefts;
        }
        
        //求右边序列的最小和
        int s2=0;
        int rights=0;
        for(int j=center+1;j<=right;j++){
            rights=rights+Array[j];
            if(rights>s2)s2=rights;
        }
        
        sum=s1+s2;
        if(leftsum>sum)sum=leftsum;
        if(rightsum>sum)sum=rightsum;
        return sum;
        
    }
}

3、动态规划(0-1背包问题,公共最长子序列)

求解最优性质的问题,最优值,最优解,存储所有子问题的解

0-1背包问题

时间复杂度:O(NW) 空间复杂度:O(NW) image.png

分别求子问题的解:W=0-4,N=0-5的子问题的最优解,列一个表格保存
dp[i][j]:[0,i]物品任取放入容量为j的背包中的最大价值
1、不放物品i(0):dp[i-1][j]
2、放物品i(1):前提:背包容量j大于等于物体i的重量
dp[i-1][j-weight[i]]+value[i]**------>1**
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])

初始化:dp[0][j] 和 dp[i][0] 都要初始化
       其他i,j下标为非0值的时候,dp[i][j]可以初始化成任意值
     
 
#define N 4 //物品数量
#define W 5 //背包容量

int v[]={0,2,4,5,6};//物品的价值
int w[]={0,1,2,3,4};//物品的重量

int dp[N+1][W+1]={};

for(int i=1;i<N;i++){
    for(int j=1;j<W;j++){
        if(j>=w[i]){//取第i个物体
            temp=dp[i-1][j-w[i]])+v[i];
        }
        dp[i][j]=max(dp[i-1][j],temp);
    }
}

return dp[N][W];//最大值,最优解

矩阵连乘

动态规划
时间复杂度:O(n3)
空间复杂度:O(n2)

image.png

分析A: M1*M2:计算次数=20*5*35,计算结果矩阵大小M12:20*35
        M12*M3:计算次数=20*35*4  计算结果矩阵大小M123:20*4
        M123*M4:计算次数=20*4*25  计算结果矩阵大小M1234:20*25
        最终的计算次数是20*5*35+20*35*4+20*4*25
        以此类推计算BCD,得到最小值为最优解
先把矩阵维数较大的相乘,消掉大维数的矩阵

公共最长子序列

时间复杂度是O(n2)

4、贪心算法

做出的选择是局部最优,不能保证获得全局最优,得到近似最优解;

最优子结构:可以使用动态规划和贪心算法,重叠子问题选择动态规划,贪心选择选择贪心法(全局的最优解可以通过局部最优解得到);

部分背包问题-最大单位重量价值先放进背包的原则

时间复杂度是O(nlogn) image.png image.png