动态规划【实战篇】

一、最长公共子序列:

1.算法核心:

(1)最优子结构:

        a.若x_{m}=y_{n},则z_{k}=x_{m}=y_{n},且Z_{k-1}X_{m-1}Y_{n-1}的最长公共子序列;

        b.若x_{m}\neq y_{n},当z_{k}\neq x_{m}时,ZX_{m-1}Y_{n}的最长公共子序列,

                                当z_{k}=y_{n} 时,ZX_{m}Y_{n-1}的最长公共子序列。

(2)递归关系:c[i][j]  记录序列 Xi 与 Yi 的最长公共子序列

c[i][j]=\begin{cases} 0 & \text{ if } i=0 ,j=0 \\ c[i-1][j-1]+1& \text{ if } i,j>0;x_{i}= y_{i} \\ max \begin{Bmatrix} c[i][j-1] &,& c[i-1][j] \end{Bmatrix} & \text{ if } i,j>0;x_{i}\neq y_{i} \end{cases}

2.代码实现:

#include<iostream> 
#include<string.h>
using namespace std;

int LCSlength(string A,string B,int m,int n,int **b)
{
	int **LCS=new int *[m+1];
	/*
	1、初始化LCS[i][j]; 
	2、空串与任意串的公共子序列长度为 0 (c[i][0]和c[0][j]) 
	*/
	for(int i=0;i<=m;i++)
	{
		LCS[i]=new int [n+1];
		for(int j=0;j<=n;j++)
		{
			LCS[i][j]=0;
		}		
	}
	
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(A[i]==B[j])						//c[i][j]=c[i-1][j-1]+1
			{
				LCS[i][j]=LCS[i-1][j-1]+1; 
				b[i][j]=1;						//c[i][j]的结果由第 1个子问题得到 
			}				
			else								//c[i][j]=max{c[i][j-1],c[i-1][j]}
			{
				if(LCS[i][j-1]>LCS[i-1][j])
				{
					LCS[i][j]=LCS[i][j-1];
					b[i][j]=2;//c[i][j]的结果由第 2个子问题得到 
				}										
				else
				{
					LCS[i][j]=LCS[i-1][j];
					b[i][j]=3;					//c[i][j]的结果由第 3个子问题得到 
				} 					
			}
		}
	}
	return LCS[m][n];
} 
 
void LCS(int **b,string A,int i,int j)
{
	if(i==0||j==0)
		return;
	if(b[i][j]==1)
	{
		LCS(b,A,i-1,j-1);
		cout<<A[i]<<' ';
	}	
	else if(b[i][j]==2)
	{
		LCS(b,A,i,j-1);
	}		
	else
	{
		LCS(b,A,i-1,j);
	}		
}
int main()
{	
	string A = "BCBACABBACDX";
	string B = "BCXYDADJL";		// A 和 B的LCS为BCAD 
	int m = A.length();
	int n = B.length();
	int length;
	int **b=new int *[m+1];
	for(int i=0;i<=m;i++)
	{
		b[i]=new int [n+1];
		for(int j=0;j<=n;j++)
		{
			b[i][j]=0;
		}		
	}
	A = ' ' + A;
	B = ' ' + B;
 	
 	cout<<"序列A:"<<A<<endl;
 	cout<<"序列B:"<<B<<endl;

	length=LCSlength(A,B,m,n,b);
	
	cout<<"A和B的最长公共子序列长度为:"<<length<<endl;
	cout<<"A和B的最长公共子序列为:";
	
	LCS(b,A,m,n);
	
	return 0; 
}
/*
序列A: BCBACABBACDX
序列B: BCXYDADJL
A和B的最长公共子序列长度为:4
A和B的最长公共子序列为:B C A D
*/ 

二、0-1背包问题:

1.算法核心:

(1)对其子问题:

max \sum_{k=i}^{j} v_{k}x_{k}

\left\{\begin{matrix} \sum_{k=i}^{j}w_{k}x_{k}\leqslant C & \\ x_{k}\in \begin{Bmatrix} 0 ,1 \end{Bmatrix} ,i\leq k\leq j & \end{matrix}\right.

        其最优解为m(i,j),背包重量为 j,可选择的物品为i,i+1,... ,n

(2)递归式:

m(i,j)=\left\{\begin{matrix} m(i+1,j)&,0\leq j< w[i] \\ max(m(i+1,j),m(i+1,j-w[i]+v[i]))&,j \geq w[i] \end{matrix}\right.

m(n,j)=\left\{\begin{matrix} v[n] &,j\geq w[n] \\ 0 & ,0\leq j < w[n] \end{matrix}\right.

2.代码实现:

#include <iostream>
#include<ctime>
using namespace std;
#include<string.h>
#define NUM 50
#define CAP 1500
int v[NUM];//价值 
int w[NUM];//重量 
int p[NUM][CAP];//记录矩阵 
void knapsack(int c, int n) //容量 c 可选物品 n
{ 
	//初始化m[n][j] 
	int jMax=min(w[n]-1,c); 
	for( int j=0; j<=jMax; j++) //0<=j<w[n]
		p[n][j]=0; 
	for( int j=w[n]; j<=c; j++) //j>=w[n]
		p[n][j]=v[n];
		
	//逐次把剩下的物品添入
	for( int i=n-1; i>1; i--) 
	{ 
		jMax=min(w[i]-1,c);
		for( int j=0; j<=jMax; j++) //0<=j<w[i]
			p[i][j]=p[i+1][j]; 
		for(int j=w[i]; j<=c; j++) //j>=w[i]
			p[i][j]=max(p[i+1][j], p[i+1][j-w[i]]+v[i]); 
	} 
	/*添加最后一份物品
	1.若0<=c<w[1]:p[1][c]=p[2][c]
	2.若c>=w[1]: 取m(2,c)与m(2,c-w[1])+v[1]二者中的最大值 
	*/ 
	p[1][c]=p[2][c]; 
	if (c>=w[1]) 
		p[1][c]=max(p[1][c], p[2][c-w[1]]+v[1]); 
} 

void traceback( int c, int n, int x[ ]) 
{ 
	for(int i=1; i<n; i++) 
	{ 
		if (p[i][c]==p[i+1][c]) //未放入物品 i 
		{
			x[i]=0; 
		} 
		else //放入了物品 i 
		{ 
			x[i]=1; c-=w[i]; 
		} 
	}
	x[n]=(p[n][c])? 1:0; //最初考虑的那个物品是否为0 若为0则说明未放入且其W>C 
} 

int main () 
{
	int x[NUM];
	int W;
	int n;
	printf("输入背包总容量M:"); 
	while (scanf("%d", &W) && W) 
	{
		printf("输入要装入的物品n:");
		scanf("%d", &n);
		printf("依次输入物品的重量w及价值v:\n");
		for (int i=1; i<=n; i++)
			scanf("%d%d", &w[i], &v[i]);
		memset (p, 0, sizeof(p));//数组初始化 
		knapsack(W, n);//进行画表
		printf("最优值为:%d\n", p[1][W]);
		traceback(W, n, x);//根据表和价值情况 获取物品选择路线
		printf("最优解为:\n");
		for (int i=1; i<=n; i++)
			if (x[i]) printf("%d ", i);
		printf("\n");
	}
	return 0;
}
/*输入背包总容量M:10
输入要装入的物品n:5
依次输入物品的重量w及价值v:
2 6
2 3
6 5
5 4
4 6
最优值为:15
最优解为:
1 2 5
*/ 

三、流水作业调度:

1.算法核心:

根据Johnson法则,我们可以对作业进行排序和分类:

(1)尽可能早的安排a[i]< b[i] 的作业,并按a[i] 增序排列 ;

(2)对于a[i]\geq b[i] 的作业,按b[i] 降序排列;

2.代码实现:

#include<stdio.h>
#include<string.h>
#include<algorithm> 
#include<iostream>
using namespace std;
#define N 100
struct work
{
	int key;//记录值 
	int index;//记录序号
	int judge;//记录b[i]是否大于a[i] ,大的话为1 
}d[N];

int main()
{
	int i,j,k,n,a[N],b[N],c[N];
	cout<<"请输入作业数:"<<endl;
	cin>>n;
	cout<<"请输入在第一台机器上加工的时间:"<<endl;
	for(i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	cout<<"请输入在第二台机器上加工的时间:"<<endl;
	for(i=1;i<=n;i++)
	{
		cin>>b[i]; 
	}
	
	for(i=1;i<=n;i++)//对结构数组赋初值,d[i]储存两个机器加工中较小的值 
	{
		if(a[i]>b[i])//记录第 2 类数据
		{
			d[i].key=b[i];
			d[i].judge=2;
		} 
		else		//记录第 1 类数据 
		{
			d[i].key=a[i];
			d[i].judge=1;
		}
		d[i].index=i;
	}
	
	for(i=1;i<=n;i++)//对结构数组赋初值,d[i]储存两个机器加工中较小的值 
	{
		if(a[i]>b[i])//记录第 2 类数据
		{
			d[i].key=b[i];
			d[i].judge=2;
		} 
		else//记录第 1 类数据 
		{
			d[i].key=a[i];
			d[i].judge=1;
		}
		d[i].index=i;
	}
	
	for(i=1;i<n;i++)//对值进行增序排序
	{
		for(j=1;j<=n-i;j++)
		{
			if(d[j].key>d[j+1].key)//将较大值交换到靠后的位置 
			{
				int index,key,judge;
				index=d[j].index;
				key=d[j].key;
				judge=d[j].judge;
				
				d[j].index=d[j+1].index;
				d[j].key=d[j+1].key;
				d[j].judge=d[j+1].judge;
				
				d[j+1].index=index;
				d[j+1].key=key;
				d[j+1].judge=judge;
			}
		}
	}
	
	for(i=1,k=1,j=n;i<=n;i++)//c[i]即为最终的作业执行顺序 
	{
		if(d[i].judge==1) 
			c[k++]=d[i].index;//将a[i]<b[i]的排前面  
		else 
			c[j--]=d[i].index;//正好将b[i]按降序排序 
	}
	i=a[c[1]];			//初始化,M1最优调度序列下的作业结束时间  
	j=i+b[c[1]];		//初始化,M2最优调度序列下的作业结束时间 
	
	for(k=2;k<=n;k++)	//从第二个开始 
	{
		i+=a[c[k]];		//本作业在M1上的结束时间
		if(j>i)			//此时上一个作业在 M2上还没加工完,那么作业堆积
		{
			j=j+b[c[k]];//本作业在 M2上的结束时间就是上一个作业在 M2上的结束时间加上本作业在M2上的运行时间
		}
		else			 
		{ 
			j=i+b[c[k]];//M2正好空着,那就在M1上的结束时间加上M2上的运行时间
		} 
	}
	printf("最短时间为%d\n作业顺序为:",j);
	for(i=1;i<=n;i++)
	{
		printf("%d ",c[i]);
	}
	return 0;
} 
/*
请输入作业数:
6
请输入在第一台机器上加工的时间:
30 120 50 20 90 110
请输入在第二台机器上加工的时间:
80 100 90 60 30 10
最短时间为430
作业顺序为:4 1 3 2 5 6
*/

四、最优二叉搜索树:

1.算法核心:

(1)w[i,j] 概率权重矩阵:

w[i,j]=\left\{\begin{matrix} q_{i-1}&,j=i-1\\ w[i,j-1]+p_{j}+q_{j}&,i\leq j \end{matrix}\right.

(2)e[i,j] 最优二叉搜索树的搜索代价矩阵:

e[i,j]=\left\{\begin{matrix} q_{i-1}&,j=i-1\\ min_{i\leq r\leq j}(e[i,r-1]+e[r+1,j]+w[i,j])&,i\leq j \end{matrix}\right.

(3)root[i,j] 记录使得e[i,j] 取得最小值时的 r 值

2.代码实现:

#include <iostream>
  
using namespace std;
const int MaxVal = 9999;
const int n = 5;

//搜索到根节点和虚拟键的概率
double p[n + 1] = {-1,0.15,0.1,0.05,0.1,0.2};
double q[n + 1] = {0.05,0.1,0.05,0.05,0.05,0.1};

int root[n + 1][n + 1];//记录根节点 
double w[n + 2][n + 2];//子树概率总和 
double e[n + 2][n + 2];//子树期望代价 

void optimalBST(double *p,double *q,int n)
{
 	//初始化只包括虚拟键的子树 
    for (int i = 1;i <= n + 1;++i)
    {
        w[i][i - 1] = q[i - 1];
        e[i][i - 1] = q[i - 1];
    }
 
    //由下到上,由左到右逐步计算 
    for (int len = 1;len <= n;++len)
    {
        for (int i = 1;i <= n - len + 1;++i)
        {
            int j = i + len - 1;
            e[i][j] = MaxVal;
            w[i][j] = w[i][j - 1] + p[j] + q[j];//更新w[i][j]的值  
            
            for (int k = i;k <= j;++k)//求最小的 temp值及其对应的根 
            {
                double temp = e[i][k - 1] + e[k + 1][j] + w[i][j];
                if (temp < e[i][j])
                {
                    e[i][j] = temp;
                    root[i][j] = k;
                   // cout << root[i][j] << " ";
                }
            }
        }
    }
    cout << "各子树的根:" << endl;
    for (int i = 1;i <= n;++i)
    {
        for (int j = 1;j <= n;++j)
        {
            cout << root[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

//输出最优二叉查找树所有子树的根
void printRoot()
{
    
}
 
//打印最优二叉查找树的结构
//打印出[i,j]子树,它是根r的左子树和右子树
void printOptimalBST(int i,int j,int r)
{
    int rootChild = root[i][j];//子树根节点
    if (rootChild == root[1][n])
    {
        //输出整棵树的根
        cout << "k" << rootChild << "是根" << endl;
        printOptimalBST(i,rootChild - 1,rootChild);
        printOptimalBST(rootChild + 1,j,rootChild);
        return;
    }
  
    if (j < i - 1)
    {
        return;
	}
    else if (j == i - 1)//遇到虚拟键
    {
        if (j < r)
        {
            cout << "d" << j << "是" << "k" << r << "的左孩子" << endl;
        }
        else
            cout << "d" << j << "是" << "k" << r << "的右孩子" << endl;
        return;
    }
    else//遇到内部结点
    {
        if (rootChild < r)
        {
            cout << "k" << rootChild << "是" << "k" << r << "的左孩子" << endl;
        }
        else
            cout << "k" << rootChild << "是" << "k" << r << "的右孩子" << endl;
    }
 
    printOptimalBST(i,rootChild - 1,rootChild);
    printOptimalBST(rootChild + 1,j,rootChild);
}
 
int main()
{
    optimalBST(p,q,n);
    //printRoot();
    cout << "最优二叉树结构:" << endl;
    printOptimalBST(1,n,-1);
}
/*
各子树的根:
1 1 2 2 2
0 2 2 2 4
0 0 3 4 5
0 0 0 4 5
0 0 0 0 5

最优二叉树结构:
k2是根
k1是k2的左孩子
d0是k1的左孩子
d1是k1的右孩子
k5是k2的右孩子
k4是k5的左孩子
k3是k4的左孩子
d2是k3的左孩子
d3是k3的右孩子
d4是k4的右孩子
d5是k5的右孩子
*/ 

猜你喜欢

转载自blog.csdn.net/aimat2020/article/details/124734895