算法设计与分析004

254 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

算法设计与分析练习题

(一) 0-1背包问题

1. 问题描述

题目:

给定N个物品,每个物品有一个重量W和一个价值V.你有一个能装M重量的背包.问怎么装使得所装价值最大.每个物品只有一个.

输入格式

输入的第一行包含两个整数n, m,分别表示物品的个数和背包能装重量。
以后两行分别为每个物品的价值,重量

输出格式

输出2行,第一行包含一个整数,表示最大价值
第二行为装入背包的物品序号

样例输入

5 8
v[]={2,1,4,3,5};
w[]={1,4,2,3,5};

样例输出

装入背包中物品总价值最大为:
11
装入的物品的序号为:
1 3 5

数据规模和约定

1<=N<=200,M<=5000.

解题思路:

根据动态规划解题步骤(问题抽象化、建立模型、寻找约束条件、判断是否满足最优性原理、找大问题与小问题的递推关系式、填表、寻找解组成)找出01背包问题的最优解以及解组成,然后编写代码实现。
动态规划的原理:
动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。

Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积,定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值,同时背包问题抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 个物品选或不选)。

1、建立模型,即求max(V1X1+V2X2+…+VnXn);

2、寻找约束条件,W1X1+W2X2+…+WnXn<capacity;

3、寻找递推关系式,面对当前商品有两种可能性:

包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。
其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i),但价值增加了v(i);

由此可以得出递推关系式:

j<w(i) V(i,j)=V(i-1,j)
j>=w(i) V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}

最优解回溯:
V(i,j)=V(i-1,j)时,说明没有选择第i 个商品,则回到V(i-1,j);
V(i,j)=V(i-1,j-w(i))+v(i)时,说明装了第i个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到V(i-1,j-w(i));
一直遍历到i=0结束为止,所有解的组成都会找到。

代码

#include<iostream>
#include <algorithm>
using namespace std;

int i,j;
int dp[30][100];
int item[100];

int value[100];
int weight[100];
int GetArray(int a[],int count)
{
	for(int i=1;i<=count;i++)
		cin>>a[i];
}

int GetMaxValue(int n,int m)
{
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			if(j<weight[i])
				dp[i][j] = dp[i-1][j];
			else
				dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
		}
	}
}

int main()
{
	int n,m;
	cin>>n>>m;
	GetArray(value,n);
	GetArray(weight,n);
	GetMaxValue(n,m);
	cout<<"装入背包中物品总价值最大为:"<<endl;
	cout<<dp[n][m]<<endl;
	int b = m;
    for(int i = n; i >= 1; i--){
        if(dp[i][b] > dp[i - 1][b]){
            item[i] = 1;
            b -= weight[i];
        }  
    }
    cout<<"装入的物品的序号为:"<<endl;
    for(int i = 1; i <= n; i++)
        //cout << item[i] << " ";
        if(item[i]==1)
        	cout<<i<<" ";
	cout<<endl;

	return 0;
}

(二)二维背包问题

1. 问题描述

题目

课后习题3-5:给定n种物品和一个背包。物品i的重量是wi,体积是bi,其价值是vi,背包的容量为C,容积为D。问:应该如何选择装入背包中的物品,使得装入背包中的物品的总价值最大?在选择装入背包额物品时,对每种物品i只有两种选择,即装入背包或不装入背包。不能将物品i装入背包多次,也不能只装入部分的物品i。试设计一个解此问题的动态规划算法,并分析算法的计算复杂性。

输入格式

输入的第一行包含三个整数n, C ,D,分别表示物品的个数,背包能装重量和背包的容积。
以后三行分别为每个物品的价值,重量,体积

输出格式

输出2行,第1行,包含一个整数,表示最大价值。
第二行,装入背包的物品序号

样例输入

5 8 8
v[]={2,1,4,3,5};
w[]={1,4,2,3,5};
b[]={2,3,5,2,4};

样例输出

装入背包中物品总价值最大为:
8
装入的物品的序号为:
4 5

解题思路:

二维0-1背包问题是在0-1背包问题上加上一维
即给定n种物品和一个背包。物品i的重量是Wi,体积为Bi,其价值为Vi,背包的容量为C,容积为D.问:应该如何选择转入背包的物品,
使得物品总价最大。

首先我们分析二维0-1背包的最优子结构在,其实非常简单只要在在0-1背包的基础上扩充m将其添上一维k即可:
m[i][j][k],表示第i件物品放入容量为j,容积为k的背包时的最大价值。
对于第i件物品只有两种状况,放入与不放入。
接下来我们可以m(i,j,k)的计算公式
即可以放入时(j >= w k >= b) m(i,j,k) = max( m[i+1][j][k], m[i+1, j-w, k-b) + Vi )
不可放入时 m(i,j,k) = m[i+1][j][k]
第n个背包
m[n][j][k] 放入时m = v
不放入时 m = 0

代码:

package Practise915;

import java.util.Scanner;
public class Main {
	public static void knapsack(int[] v,int[] w,int[] b,int c,int d,int[][][] m) {
		int n = v.length-1;  
		int jMax = Math.min(w[n]-1,c); 
		int kMax = Math.min(b[n], d); 
		for(int j=0;j<=c;j++) {
			for(int k=0;k<=d;k++) {
				if(w[n]<=j && b[n]<=k) {
					m[n][j][k]=v[n];
				}
				else {
					m[n][j][k]=0;
				}
			}	
		}
		for(int i=n-1;i>1;i--) {
			for(int j=0;j<=c;j++)
				for(int k=0;k<=d;k++) { 
					if (w[i]<=j && b[i]<=k)
						m[i][j][k] = Math.max(m[i+1][j][k], m[i+1][j-w[i]][k-b[i]]+v[i]);
					else
						m[i][j][k] = m[i+1][j][k];
				}
		}
		if(c>=w[1] && d>=b[1]){ 
			m[1][c][d] = Math.max(m[2][c][d], m[2][c-w[1]][d-b[1]]+v[1]); 
		}
		else {
			m[1][c][d]=m[2][c][d];
		}
	}
	
	public static void traceback(int[][][] m,int[] w,int c, int d,int[] x) { 
		int n = w.length-1; 
		for(int i=0;i<n;i++) { 
			if(m[i][c][d]==m[i+1][c][d]) {
				x[i]=0;} //未装入
			else { //装入
				x[i]=1; 
				c -= w[i]; 
				} 
			x[n]=(m[n][c][d]>0)?1:0; 
		} 
	} 	
	
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		int n = input.nextInt();
		int Weight = input.nextInt();
		int Volume = input.nextInt();
		int[] v = new int[n+1]; //价值 
		int[] w = new int[n+1]; //重量 
		int[] b = new int[n+1]; //体积 
		int[] x = new int[v.length]; 
		int[][][] m = new int[v.length][Weight+1][Volume+1]; 
		for (int i = 1;i <= n;i++) {
			v[i] = input.nextInt();
			
			
		}
		for(int i=1;i<=n;i++) {
			w[i] = input.nextInt();
		}
		for(int i=1;i<=n;i++) {
			b[i] = input.nextInt();
		}
		knapsack(v,w,b,Weight,Volume,m); 
		traceback(m,w,Weight,Volume,x); 
		System.out.println("装入背包中物品总价值最大为:" + m[1][Weight][Volume]); 
		System.out.println("装入物品的序号为:"); 
		for(int i=1;i<w.length;i++) { 
			if(x[i] == 1)
				System.out.print(i + " "); 
		} 
	} 
	
}

(三) 最长公共子序列

1. 问题描述

题目:

给定两个序列X={x1,x2,…,xm}和Y={y1,y2,…,yn},找出X和Y的最长公共子序列。

输入格式

输入两个整数n, m,分别表示两个序列中元素的个数;
依次输入两个序列中的所有元素值

输出格式

输出2行,第一行为最长公共子序列的长度。第二行为最长公共子序列的具体元素值

样例输入

8 9
1, 3, 4, 5, 6, 7, 7, 8
3, 5, 7, 4, 8, 6, 7, 8, 2

样例输出

5
3 4 6 7 8

解题思路

1.一个给定的序列的子序列,就是将给定序列中零个或多个元素去掉之后得到的结果。
给定串中任意个连续的字符组成的子序列称为该串的子串。
2.动态规划的基本思想就是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。
3.用c[i,j]表示Xi 和 Yj 的LCS的长度
递归公式为:
c[i][j]=0 if(i== 0||j==0)
c[i][j]=c[i-1][j-1]+1 if(i>0&&j>0&&x ==y)
c[i][j] = max{c[i-1][j],c[i][j-1]} if(i>0&&j>0&&x!=y)

代码

#include<iostream>
#include<cstdio>
#include<cstring> 

using namespace std;
 
int a[1001], b[1001];
int dp[1001][1001], len1, len2;
 
void lcs(int i, int j)
{
    for(i=1; i<=len1; i++)
    {
        for(j=1; j<=len2; j++)
        {
            if(a[i-1] == b[j-1])
                dp[i][j] = dp[i-1][j-1] + 1;
            else if(dp[i-1][j] > dp[i][j-1])
                dp[i][j] = dp[i-1][j];
            else
                dp[i][j] = dp[i][j-1];
        }
    }
}
 
void llcs()
{
    int i, j, z = 0;
    int c[1001];
    memset(c, 0, sizeof(c));
    i = len1, j = len2;
    while(i!=0 && j!=0)
    {
        if(a[i-1] == b[j-1])
        {
            c[z++] = a[--i];
            j--;
        }
        else if(dp[i-1][j] < dp[i][j-1])
            j--;
        else if(dp[i][j-1] <= dp[i-1][j])
            i--;
    }
    for(i=z-1; i>=0; i--)
        printf("%d ", c[i]);
    printf("\n");
 
}

void GetArray(int arr1[],int n)//获取输入 
{
	for(int i=0;i<n-1;i++)
		scanf("%d,",&arr1[i]);
	scanf("%d",&arr1[n-1]);
}

int main()
{
	int n,m;
	cin>>n>>m;
	GetArray(a,n);
	GetArray(b,m);
	len1=n;
	len2=m;
	lcs(len1,len2);
	cout<<dp[n][m]<<endl;
	llcs();
    return 0;
}

(四)电路布线

1. 问题描述

题目

在电路板的上、下两端分别有n个接线柱。根据电路设计,用导线(i,π(i))将上端接线柱与下端接线柱相连,要求找到导线的最大不相交子集

示例

输入: 下端接线柱取值 [8,7,4,2,5,1,9,3,10,6]
输出: 最大不相交连线分别为:
3 4
5 5
7 9
9 10
最大不相交连线数目为:4

解题思路:

1.分析题目要求,采用动态规划的算法。这个问题符合最优子结构性质:问题的最优解包含子问题的最优解。
2.重叠子问题性质:子问题之间不独立的同时(这是区分分治算法的关键),少量子问题被重复解决了。子问题之间有联系的同时有些计算重复了,我们可以在计算第一次的时候将结果记录下来,以后再用到的时候,直接取值,不用再去花时间计算。
3.递归公式为: 与i连线的对应点 为n(i)
当i=1的时候:
size[i,j]=0 j<n(i)
size[i,j]=1 j>=n(i)
当i>1的时候:
size[i,j]=size[i-1,j] j<n(i)
size[i,j]=max{size[i-1,j],size[i-1,n(i)-1]+1} j>=n(i)

代码:

#include <iostream> 
#include<stdio.h>
using namespace std;  
const int N = 10;
 
void MNS(int C[],int n,int **size);
void Traceback(int C[],int **size,int n,int Net[],int& m);
 
int main()
{
	int c[N+1];
	int **size = new int *[N+1];
 	c[0]=0;
 	cout<<"下端接线柱取值为:"<<endl; 
 	for(int i=1;i<N;i++)
 		scanf("%d,",&c[i]);
 	scanf("%d",&c[N]);
	for(int i=0; i<=N; i++)
		size[i] = new int[N+1];
	MNS(c,N,size);
	int Net[N],m;
	Traceback(c,size,N,Net,m);
	cout<<"最大不相交连线分别为:"<<endl;
	for(int i=m-1; i>=0; i--)
		cout<<Net[i]<<" "<<c[Net[i]]<<endl;
	cout<<"最大不相交连线数目为:"<<size[N][N]<<endl;
	return 0;
}
 
void MNS(int C[],int n,int **size)
{
	for(int j=0;j<C[1];j++)
		size[1][j]=0;
	for(int j=C[1]; j<=n; j++)
		size[1][j]=1;
	for(int i=2; i<n; i++)
	{
		for(int j=0; j<C[i]; j++)
			size[i][j]=size[i-1][j];//当i<c[i]的情形
		for(int j=C[i]; j<=n; j++)
			//当j>=c[i]时,考虑(i,c[i])是否属于MNS(i,j)的两种情况
			size[i][j]=max(size[i-1][j],size[i-1][C[i]-1]+1);
	}
	size[n][n]=max(size[n-1][n],size[n-1][C[n]-1]+1);
}
 
void Traceback(int C[],int **size,int n,int Net[],int& m)
{
	int j=n;
	m=0;
	for(int i=n;i>1;i--)
	{
		if(size[i][j]!=size[i-1][j])//此时,(i,c[i])是最大不相交子集的一条边
		{
			Net[m++]=i;
			j=C[i]-1;//更新扩展连线柱区间
		}
	}
	if(j>=C[1])//处理i=1的情形
		Net[m++]=1;
}

(五)基础练习 数列排序

给定一个长度为n的数列,将这个数列按从小到大的顺序排列。1<=n<=200输入描述:
第一行为一个整数n。
第二行包含n个整数,为待排序的数,每个整数的绝对值小于10000。

输入样例:

5
8 3 6 4 9

输出描述:

输出一行,按从小到大的顺序输出排序后的数列。

输出样例:

3 4 6 8 9

代码:

#include <iostream>
#include<algorithm>
using namespace std;
int main()
{
    int n;
    cin >> n;
    int array[n];
    for (int i = 0; i < n; i++)
    {
        cin >> array[i];
    }
    sort(array, array + n);
    for (int i = 0; i < n; i++)
    {
        cout << array[i] << " ";
    }

}

(六)算法训练 数的划分

题目:

​ 将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序)。

例如:n=7,k=3,下面三种分法被认为是相同的。
1,1,5; 1,5,1; 5,1,1;
问有多少种不同的分法。输入描述:
n,k

输入样例:

7 3

输出描述:

一个整数,即不同的分法

输出样例:

4 {四种分法为:1,1,5;1,2,4;1,3,3;2,2,3;}

代码:

#include<iostream>
#include<math.h>
#include<memory.h>
using namespace std;

int main()
{
    int n, k;
    cin >> n >> k;
    int dp[240][15];
    memset(dp,0, sizeof(dp));
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++)
            if (j == 1)
                dp[i][j] = 1;
            else
                dp[i][j] = dp[i - j][j] + dp[i - 1][j - 1];

    cout << dp[n][k] << endl;
    return 0;
}

(七)基础练习 矩阵乘法**

题目:

给定一个N阶矩阵A,输出A的M次幂(M是非负整数)
例如:
A =
1 2
3 4
A的2次幂
7 10
15 22输入描述:
第一行是一个正整数N、M(1<=N<=30, 0<=M<=5),表示矩阵A的阶数和要求的幂数
接下来N行,每行N个绝对值不超过10的非负整数,描述矩阵A的值

输入样例:

2 2
1 2
3 4

输出描述:

输出共N行,每行N个整数,表示A的M次幂所对应的矩阵。相邻的数之间用一个空格隔开
输出样例:
7 10
15 22

代码:

#include<iostream>
#include<cstring>
using namespace std;
int start[50][105];
int tmp[50][105];
int last[50][105];
void Display(int a[][105], int n)
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
			cout << a[i][j] << " ";
		cout << endl;
	}
}
void Getmatrix(int start[][105], int tmp[][105], int last[][105], int n, int m)
{
	for (int i = 1; i < m; i++)
	{
		for (int j = 0; j < n; j++)
		{
			for (int k = 0; k < n; k++)
			{
				int temp = 0;
				for (int x = 0; x < n; x++)
				{
					temp += start[j][x] * tmp[x][k];
					last[j][k] = temp;
				}
			}
		}
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				tmp[i][j] = last[i][j];
			}
		}
	}
}
int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			cin >> start[i][j];
		}
	}
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			tmp[i][j] = start[i][j];
		}
	}
	if (m == 0)
	{
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				if (i == j)
					last[i][j] = 1;
			}
		}
		Display(last, n);
	}
	else if (m == 1)
	{
		Display(start, n);
	}
	else
	{
		Getmatrix(start, tmp, last, n, m);
		Display(last, n);
	}
	return 0;
}