算法设计与分析复习

考试题型

  1. 算法应用:共40分,2个小题,每小题20分
  2. 分析题:共20分,2个小题,每小题10分
  3. 算法设计:共20分,1个小题,每小题20分
  4. 综合题:共20分,1个小题,每小题20分

先看看作业

作业1(蛮力法解题)

(1)掌握蛮力法算法思想和实现思路。

蛮力法算法思想又称为枚举法、穷举法、暴力法,是指采用遍历技术整个解空间,即采用一定的策略将待求解问题的所有元素依次处理一次,从而找出问题的解。
实现思路:根据问题中的条件将可能的情况列举出来,逐一尝试从中找出满足问题条件的解。
设计思路:1、找出枚举范围2、找出约束条件

看的别人的博客和书上的定义:算法分析学习笔记二 蛮力法

(2)求解幂集问题,给定一个包含n个元素的集合,实现求解幂集的函数。

#include<iostream>
using namespace std;
// 将b表示的二进制加1  
// b数组下表从小到大对应从低到高位 
void inc(int b[], int n) {
	for (int i = 0; i < n; i++) {
		if (b[i])	b[i] = 0;
		else {
			b[i] = 1;
			break;
		}
	}
}

void PSet(int a[], int b[], int n) {
	int pw = (int)pow(2, n);//pow() 函数用来求 x 的 y 次幂(次方),其原型为:double pow(double x, double y);
	printf("1到%d的幂集为:\n", n);

	for (int i = 0; i < pw; i++) {//幂集总数有2的n次方个,枚举打印a数组,b[i]==1就打印a[i]
		cout << "{";
		for (int i = 0; i < n; i++) {
			if (b[i])
				cout<<a[i];
		}
		cout << "}"<<endl;
		inc(b, n);
	}
	cout << endl;
}
int main()
{
	int n;
	cout << "请输入集合大小:" << endl;
	cin >> n;
	cout << "请输入集合的内容:" << endl;

	int a[10], b[10];
	for (int i = 0; i < n; i++) {
		cin >> a[i];
		b[i] = 0;
	}
	PSet(a, b, n);
	return 0;
}

大脑已经不思考了,白嫖真香,将大佬的代码蛮力法求解幂集问题改了一下,不想自己写了。

(3)求解全排列问题,给定n个互不相同的元素,实现求解全排列的函数。

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

void DFS(int a[], int k, int m) {//a数组存放排列,k
	if (k == m) {
		// 因为每一次交换就可以将问题缩减一级,所以m次交换就可以将问题缩减为0级,0个数的全排列问题,问题最终得到解决,当然缩减到1级也是可以的(就是没有了最后自身与自身的交换而已)
		for (int i = 0; i < m; i++) {
			cout << a[i] << " ";
		}
		cout << endl;
	}
	else {
		for (int j = k; j < m; j++) {//遍历数组,分别将每一位元素与当前数组首位元素交换
			swap(a[k], a[j]);//通过交换来实现全排列
			DFS(a, k + 1, m);
			swap(a[k], a[j]);//进行下一次交换时,是在原数组的基础上进行的,所以必须把首位元素交换回来
		}
	}
}
int main() {
	int a[10];
	int n;
	cout << "请输入排列大小"<<endl;
	cin >> n;
	cout << "请输入不重复的集合进行排列:";
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	DFS(a, 0, n);
}

这题用递归做的,将集合中的元素从头开始交换,也是蛮力法,形式上好看一点。

作业2(求最近点对问题,用的分治法)

#include <iostream>
#include <math.h>
using namespace std;
const int n = 8;
//定义结构体表示集合S中点的坐标
struct point {
	int x, y;
};

double Closest(point S[], int low, int high);		//实现求最近对距离
double Distance(point a, point b);					//求两点之间距离
int Partition(point r[], int first, int end);		//划分
void QuickSort(point r[], int first, int end);		//快速排序

//实现求最近对距离
double Closest(point S[], int low, int high) {
	double d1, d2, d3, d;
	int mid, i, j, index;
	point P[n];                   //存放点集合 P1和P2

	//如果只有两个点,返回这两个点间的距离
	if (high - low == 1) {
		cout<<"减到两个点的点对("<<S[low].x<<","<<S[low].y<<")" << " 和 ("<<S[high].x << "," << S[high].y << ")" <<endl;
		return Distance(S[low], S[high]);
	}

	//如果递归到有三个点,求最近点对距离
	if (high - low == 2) {
		d1 = Distance(S[low], S[low + 1]);
		d2 = Distance(S[low + 1], S[high]);
		d3 = Distance(S[low], S[high]);
		if ((d1 < d2) && (d1 < d3)) { 
			cout << "三个点对最短的为(" << S[low].x << "," << S[low].y << ")" << " 和 (" << S[low+1].x << "," << S[low+1].y << ")" << endl;
			return d1; }
		else if (d2 < d3) { 
			cout << "三个点对最短的为(" << S[low+1].x << "," << S[low+1].y << ")" << " 和 (" << S[high].x << "," << S[high].y << ")" << endl;
			return d2; }
		else { 
			cout << "三个点对最短的为(" << S[low].x << "," << S[low].y << ")" << " 和 (" << S[high].x << "," << S[high].y << ")" << endl;
			return d3; }
	}

	mid = (low + high) / 2;				//计算中间点
	d1 = Closest(S, low, mid);			//递归求解在左部的子问题
	d2 = Closest(S, mid + 1, high);		//递归求解在右部的子问题

	if (d1 <= d2) d = d1;				//求解卡在中间的子问题
	else d = d2;
	index = 0;
	for (i = mid; (i >= low) && (S[mid].x - S[i].x < d); i--)			//建立点集合P1
		P[index++] = S[i];
	for (i = mid + 1; (i <= high) && (S[i].x - S[mid].x < d); i++)		//建立点集合P2
		P[index++] = S[i];

	//对集合P1、P2按y坐标升序排列
	QuickSort(P, 0, index - 1);

	//依次处理集合P1和P2中的点
	for (i = 0; i < index; i++) {
		for (j = i + 1; j < index; j++) {
			if (P[j].y - P[i].y >= d)		//超出y坐标的范围,点P[i]处理完毕
				break;
			else {
				d3 = Distance(P[i], P[j]);
				if (d3 < d)
					d = d3;
			}
		}
	}
	return d;
}

//求两点之间距离
double Distance(point a, point b) {
	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

//区域划分
int Partition(point r[], int first, int end) {				//划分
	int i = first, j = end;									//初始化待划分区间
	while (i < j) {
		while (i < j && r[i].y <= r[j].y) j--;				//右侧扫描
		if (i < j) {
			point temp = r[i]; r[i] = r[j]; r[j] = temp;	//将较小记录交换到前面
			i++;
		}
		while (i < j && r[i].y <= r[j].y) i++;				//左侧扫描
		if (i < j) {
			point temp = r[i]; 
			r[i] = r[j];
			r[j] = temp;    //将较大记录交换到后面
			j--;
		}
	}
	return i;												// 返回轴值记录的位置
}

//快速排序
void QuickSort(point r[], int first, int end) { 
	int pivot;
	if (first < end) {
		pivot = Partition(r, first, end);		//划分,pivot是轴值在序列中的位置
		QuickSort(r, first, pivot - 1);			//求解子问题1,对左侧子序列进行快速排序
		QuickSort(r, pivot + 1, end);			//求解子问题2,对右侧子序列进行快速排序
	}
}


int main()
{
	//输入按x坐标升序排列的n个点的集合,这里直接初始化一些
	point S[n] = { {1,4},{1,8},{2,1},{3,2},{4,4},{5,1},{6,6},{7,2} };

	double minDist = Closest(S, 0, n - 1);

	//输出最近点对的距离
	cout << "最近点对距离为:" << minDist << endl;

}

算法思路参考 【算法设计与分析】分治法与最近点对问题

作业3(最长公共子序列问题)

1、求解最长公共子序列问题。要求要有结果展示。
这个也是书上有的经典问题,使用动态规划的思想。
状态转换方程:
{ d p [ i ] [ j ] = 0 , i = 0 或 j = 0 ( 边 界 条 件 ) d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 , a [ i − 1 ] = b [ j − 1 ] ( 当 前 下 标 的 元 素 相 等 ) d p [ i ] [ j ] = M A X ( d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j ] ) , a [ i − 1 ] ! = b [ j − 1 ] ( 当 前 元 素 不 等 就 有 两 种 递 推 方 案 , 取 其 中 最 大 的 放 入 d p ) 最 后 d p [ m ] [ n ] 就 为 最 终 结 果 , 然 后 可 用 回 溯 算 法 在 d p 中 构 造 最 长 子 序 列 \begin{cases} dp[i][j]=0 ,&i=0或j=0(边界条件)\\ dp[i][j]=dp[i-1][j-1]+1,&a[i-1]=b[j-1](当前下标的元素相等)\\ dp[i][j]=MAX(dp[i][j-1],dp[i-1][j]),&a[i-1]!=b[j-1](当前元素不等就有两种递推方案,取其中最大的放入dp) \end{cases}\\ 最后dp[m][n]就为最终结果,然后可用回溯算法在dp中构造最长子序列 dp[i][j]=0,dp[i][j]=dp[i1][j1]+1,dp[i][j]=MAX(dp[i][j1],dp[i1][j]),i=0j=0()a[i1]=b[j1]()a[i1]!=b[j1](dp)dp[m][n]dp
https://blog.csdn.net/weixin_44279771/article/details/105989975

作业4(n皇后问题,递归求解)

n皇后问题书上有原题,这是一个非常经典的问题,要记住状态转换方程;
递归模型为:

queen(i,n)==n个皇后为放置完毕,输出一个解;
queen(i,n)==在第i行找到一个合适的位置(i,j),放置一个皇后;
其它情况:queen(i+1,n)


//这里写一下皇后问题的核心代码,判断函数和输出函数没有写
void queen(int i,int n){
	if(i>n)
		dispasolution(n);//所有皇后放置结束,调用输出函数
	else{
		for(int j=1;i<=n;j++)//在第i行上试探每一列j
			if(place(i,j)){//调用位置比较函数,测试(i,j)位置能否放置皇后
			q[i]=j;//找到就记录一下
			queen(i+1,n);
			}			
		}
}

按照大纲复习

重点章节:

第二章 递归算法设计
第三章 分治法
第四章 蛮力法
第七章 贪心法
第八章 动态规划

可能出现的内容:

递归算法:递归算法是执行过程和结果

算法设计应用递归算法
使用递归算法就是将原问题不断分解为规模缩小的子问题,然后递归调用方法来表示问题的解。
求解步骤:

  1. 明确函数要做什么
  2. 明确递归的结束条件
  3. 找出函数的等价关系式
    最好写出递归方程。

递推法和倒推法:猴子吃桃等简单问题及类似问题的灵活运用

悟空第一天摘下桃子若干,当即吃掉一半,还不过瘾,又多吃一个,第二天又将剩下的桃子吃掉一半多一个,以后每天吃掉前一天剩下的一半多一个,到第n天准备吃的时候只剩下一个桃子。聪明的你,请帮悟空算一下,他第一天开始吃的时候桃子一共有多少个呢?

#include<stdio.h>
 
int main()
{
    int a,i,n;
    scanf("%d",&n);
    a=1;//倒推法
    for(i=n-1;i>=1;i=i-1)
    {//从n-1开始到第1天
        a=2*a+2;
    }
    printf("%d",a);
    return 0;
}

贪心法

贪心法是寻找最优解的常用方法,将求解过程分成若干个步骤,每个步骤都应用贪心原则,选取当前状态下最好/最优的选择。

可能出现的经典问题:找零钱、
活动安排问题、
多机调度问题、
田忌赛马问题、
程序存储问题、
汽车加油问题、
流水作业调度问题
以及类似问题等

找零钱:从大到小排序,先选最大的
活动安排问题:安排尽可能多的活动就是要按结束时间从小到大排序,先挑小的兼容
多机调度问题:让最长处理时间的作业优先分配给最先空闲的机器
田忌赛马问题:下面有详解
程序存储问题:先存最小的
汽车加油问题:能到下一站就不加油,不让就在这一站加满
流水作业调度问题:分为两组,第一组为第一生产线时间小于第二生产线的作业,按第一生产线的时间从小到大排序,小的先执行;第二组为第一生产线时间大于第二生产线的作业,按第二生成线的时间从大到小排序,大的先执行

求解思路:都是贪心法取局部最优解,例如田忌赛马的问题,就是递减排序,从当前最大开始能匹配就用,不能就使用当前最小的匹配

分治法

应用分治法求解的时候有一个非常明显的特征:这个问题的规模可以分解为多个规模较小的子问题。这个看起来也是动态规划的要求,但是动态规划算法是用来求解最优解的问题,分治法是用于求解一个较大规模的解。

分治法一般可以用递归算法实现,分治法是一种求解问题的策略,而递归是一种实现求解算法的技术。

可能出现的经典问题:
求第n小元素值、
求中位数、
同时求最大最小值、
棋盘覆盖问题、
Strassen矩阵乘法(详解矩阵乘法中的Strassen算法)等

在这里插入图片描述

求解第k小元素:类似于快速排序的思路,递归出口为a[i]==k-1,如果k-1<i,就在左区间找,如果k-1>i,就在右区间找。
求中位数:两个序列的中位数比较,然后分别减半找另外一边,当只有两个元素时小的就是中位数。
同时求最大最小值:不断二分,只剩两个的时候比较大小并记录一个临时值,最后和全局值比较更新
棋盘覆盖问题:棋盘问题解析(这个问题放到现在应该不难理解了)

求解思路:运用递归算法的技术,确定递归出口和递归条件。

动态规划

可能出现的经典问题:
多段图问题:分阶段选出最短路径、
资源分配问题:先只分配一个厂,然后在其基础上加其他厂、
矩阵连乘问题https://blog.csdn.net/qq_19782019/article/details/94356886
最大子段和、
最长递增子序列、
编辑距离、
最长公共子串等

最大子段和的状态转换方程: { d p [ 0 ] = 0 , 边 界 条 件 d p [ j ] = M A X { d p [ j − 1 ] + a j , a j } , 1 < = j < = n \begin{cases} dp[0]=0,&边界条件\\ dp[j]=MAX\{dp[j-1]+a_j , a_j\},&1<=j<=n \end{cases} {dp[0]=0,dp[j]=MAX{dp[j1]+aj,aj},1<=j<=n

编辑距离问题的状态转换方程:
{ d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] , 当 a [ i − 1 ] = b [ j − 1 ] 时 d p [ i ] [ j ] = M I N { d p [ i − 1 ] [ j − 1 ] + 1 , d p [ i ] [ j − 1 ] + 1 , d p [ i − 1 ] [ j ] + 1 } , 当 a [ i − 1 ] ! = b [ j − 1 ] 时 , 当 字 符 不 相 等 的 时 候 , 就 有 三 种 步 进 方 式 \begin{cases} dp[i][j]=dp[i-1][j-1],&当a[i-1]=b[j-1]时\\ dp[i][j]=MIN \{dp[i-1][j-1]+1,dp[i][j-1]+1,dp[i-1][j]+1\},&当a[i-1]!=b[j-1]时,当字符不相等的时候,就有三种步进方式 \end{cases} {dp[i][j]=dp[i1][j1],dp[i][j]=MIN{dp[i1][j1]+1,dp[i][j1]+1,dp[i1][j]+1},a[i1]=b[j1]a[i1]!=b[j1]

求解思路:需要用到上个阶段结果的问题就使用dp[][]数组存放;不需要用到上个阶段结果的问题就使用len[]数组存放(例如最长递增子序列问题)

图搜索算法

可能出现的经典问题:
排列树与子集树上的回溯、
算法的剪枝与限界分支算法、
简单问题的分支与限界求解
0-1背包问题的多种方法求解

图搜索问题应该会和分支限界算法一起考,在广度优先遍历的基础上加上剪枝和限界函数。

概率算法

可能出现的经典问题:
蒙特卡罗算法求解数值计算问题(频率在测试实例充分多时近似为概率)、
舍伍德策略对算法性能的影响(可以用来消除算法的时间复杂性与输入实例间的联系)

蒙特卡罗算法最经典的就是用于求解PI的近似值,通过随机密度求解区域分配,进而算出PI的近似值。

舍伍德概率算法是在一次计算之前根据随机数将待计算序列中随机确定一个元素作为基准,并将它与第一个元素交换,则计算之后的子序列更加趋近于期望,从而使算法的行为不受待计算序列的不同输入实例的影响。(在确定性算法中引入随机性,使计算时间复杂性对所有实例而言相对均匀)

分析题部分

试题形式包括计算推导、算法模型中结论和定理的分析与证明
可能考的内容:
算法的性能指标:
算法性能定理与结论的论证、
部分算法模型结论的证明
递归算法:一般难度非递归与递归算法的时间空间性能的推算。

算法设计部分-蛮力法

考一些编码量不大的枚举问题求解、不太复杂的递归算法设计。

蛮力法就是找出全部的解空间,然后顺序遍历整个解空间寻找可行解,也可以用一个全局变量记录,找最优解。

demo:求解与k无关的数
#include<iostream>
#include<string>

using namespace std;

bool compare(int k,int n) {
	if (n%k == 0) {//能整除
		return false;
	}
	for (int i = 10; i <= n;i=i*10) {//十进制中有k
		int m = n%i;
		if (m==k) {
			cout << "不能整除,但是位置上有k的数为" << n<<endl;
			return false;
		}
	}
	return true;
}
void Poof(int k, int n, int sum) {
	for (int i = 1; i <= n; i++) {
		if (compare(k, i)) {
			sum++;
			cout << "第" << sum << "个与k无关的数为" << i << endl;
		}
	}
	cout << "直到" << n << "和k无关的数的总和为" << sum << endl;
}
int main() {
	int k;//一个个位数
	cout << "请输入k";
	cin >> k;
	int n = 100;
	int sum = 0;
	Poof(k, n, sum);
	return 0;
}

往年习题

2020年算法应用题

demo1(动态规划,轮流拿牌问题)

1.桌子上摆放着144 张麻将牌,两个人轮流取牌,每人每次最少取走1张,最多取走4 张,谁拿到最后一张麻将牌谁输,设先取牌者为A,后取牌者为B,如果双方均完全理智,没有任何失误∶
(1)请详细说明先取牌者获胜的策略;(5分)
(2)将问题推广为任意的 n张麻将牌,两个人轮流取牌,每人每次最少取走 1 张,最多取走k 张,请详细说明此时先拿牌者取胜的策略。(5分)

(1)从桌子上只剩1张麻将牌开始倒推剩7张麻将牌即可知:只要在取牌让剩余牌数还剩余6张时,然后B再取,A永远取5-nB 张就能将最后一张牌留给B。
(2)先计算m=n MOD(k+1),如果m!=1,先取m-1张(取牌让剩余牌正好位k+2张),接下来永远取(k+1)-nB 张就能将最后一张牌留给B,先取者必胜
(如果m为1,则先取者无论取多少张,B就可以用同样的策略,永远取(k+1)-nA 来进行,则先取者必败)

demo2(贪心法,田忌赛马问题)

2.田忌赛马是中国历史上有名的博弈故事。如果田忌和齐威王比赛的马由3匹变成了n 匹(n>3),齐威王的策略不变,仍然让他的马按从优到劣的顺序出赛,田忌则可以按任意顺序选择他的马出赛。赢一局,田忌可以得到100 两银子,输一局,田忌就要输掉100 两银子。已知齐威王和田忌的所有马的奔跑速度都不相同,并且已对两人的马按速度分别从快到慢排好序,请设计一个算法,帮助田忌赢得最多的银子。
(1)描述求解该问题的算法策略和解题步骤;
(2)现已知齐威王和田忌各有11 匹马,两队马的速度如下表,请简要说明算法执行的全过程。

马的编号1234567891011
齐威王6891215202930343536
田忌57101416171823253233

(3)算法执行后,田忌的盈利是多少?

(1)采用贪心的算法策略;
面对齐威王顺序出场的各匹马,如果田忌当前最快的马的速度更快就派最快的马出场;否则派最慢的马出场迎战齐威王当前最快的马
(2)算法执行过程如下:
33<36,田忌派速度为5的马出战齐威王速度36的马,败;
33<35,田忌派速度为7的马出战齐威王速度35的马,败;
33<34,田忌派速度为10的马出战齐威王速度34的马,败;
33>30,田忌派速度为33的马出战齐威王速度30的马,胜;
32>29,田忌派速度为32的马出战齐威王速度29的马,胜;
25>20,田忌派速度为25的马出战齐威王速度20的马,胜;
23>15,田忌派速度为23的马出战齐威王速度15的马,胜;
18>12,田忌派速度为18的马出战齐威王速度12的马,胜;
17>9,田忌派速度为17的马出战齐威王速度9的马,胜;
16>8,田忌派速度为16的马出战齐威王速度36的马,胜;
14<6,田忌派速度为14的马出战齐威王速度36的马,胜;
最终结果为胜8局,负3局。
(3)田忌的盈利为100*(8-3)=500两银子。

练习题(动态规划,最长递增子串问题)

3.对于给定的一个序列{a1,a2,a3,… an},存在一子序列{ai1, ai2,a3,…,ain},满足ai<an2<a13,…<am,且1≤i1<i2< …<in≤n。在上升子序列中最长的称为最长上升子序列。
例如∶对于序列{1,7,3,5,9,4,8},子序列{1,7},{3,4,8}等为其上升子序列,子序列{1,3.5.8}为最长上升子序列,长度是4。
对于给定的序列,要求以尽可能快的方式求出最长上升子序列及其长度。
(1)描述求解该问题的算法策略和解题步骤;
(2)对序列{8,5,2,3,7,4,1,9,6,8,9},求最长上升子序列及长度。

练习题(分治法,求中位数)

4.一个长度为n 的递增有序序列a【0…n-1】,处于中间位置的元素称为a 的中位数,两个有序序列的中位数是包含两者所有元素的有序序列的中位数。为简化问题,含有n个元素的有序序列a【s…t】,仅考虑下标为m= ⌊ s + t ‾ 2 ⌋ \lfloor{ \mathop{\underline {s+t}}\limits_{2} }\rfloor 2s+t处的元素为中位数。例如∶
序列a=(11,13,15,17,19),中位数是15;序列b=(2,4,6,8,20),中位数是6;a、b 两个有序序列合并起来的中位数为11。
(1)设计一个效率较高的算法求给定的两个等长有序序列的中位数,说明求解该问题的算法策略和解题步骤;
(2)对于如下两个有序序列,写出求解中位数的全过程。(4分)
a=(346142778 96),b=(1928476163 73 85)

练习题(分支限界法,0/1背包问题)

5.有n个重量分别为{w;w2.…,wn}的物品,它们的价值分别为{v1,v2,…,v},给定一个容量为W的背包,从这n个物品中选取一些物品放入该背包,每个物品要么选中要么不选中,要求选中的物品能够放入背包,装入的重量和恰好为W,而且具有最大的价值。
(1)设计用回溯法求解该问题的左剪枝条件与右剪枝的限界条件;
(2)设 W=7,对如下4 件物品,画出用回溯法进行剪枝和限界时,求解该问题的完整解空间树,并说明最优解的解向量。

编号1234
重量5423
价值1215810

练习题答案

题1

(1)利用动态规划的思想,用递推法从头开始递推,
状态转换方程为
{ l e n [ i ] = 0 , 0 < = i < = n − 1 l e n [ i ] = M A X ( l e n [ i ] , l e n [ j ] + 1 ) \begin{cases} len[i]=0 ,&0<=i<=n-1\\ len[i]=MAX(len[i],len[j]+1) \end{cases} {len[i]=0,len[i]=MAX(len[i],len[j]+1)0<=i<=n1
初态:len[i]=1,0<=i<=n-1,len表示序列长度
递推式:len[i]=max(len[i],len[j]+1)
用辅助数组存放前驱坐标方便结果输出

(2)对序列{8,5,2,3,7,4,1,9,6,8,9}的计算过程如下:

序列下标012345678910
元素值85237419689
序列长度11123314456
前驱下标-1-1-1233-14589

因此最长上升子序列为(2,3,4,6,8,9),长度为6.

题2

(1) 采用分治法的二分法求解,算法为:
由题n=1,a,b中唯一元素的较小者为中位数
否则,分别求出a,b的中位数ma 和mb
1、如果ma =mb ,则就为中位数,算法结束
2、如果 ma <mb ,则舍弃a的前半部分和b的后半部分;
否则ma >mb ,舍弃a的后半部分和b的前半部分
(2)求解过程:
画表表达更加清晰:

数组ab
初始状态3,4,6,14,27,78,9619,28,47,61,63,73,85
第一次二分后14,27,78,9619,28,47,61
第二次二分后78,9619,28
第三次二分后7828

最后即得中位数为28.

题3

(1)设Tw 表示已经装入背包的物品总重量,Tv 背包中物品的总价值;
左剪枝条件:
对于第i层的结点,如果Tw +w[i]已超过W,就不能再选择w[i]
右限界条件:
设剩余物品重量Rw=w[i]+w[i+1]+…+w[n]
当不选择物品i时:Rw-w[i]=w[i+1]+…+w[n]
如果Tw +Rw -w[i]<W,即使选择后面的所有物品,重量也不会达到W,减去右孩子。

(2)回溯法加剪枝和限界的完整解空间树:
在这里插入图片描述
画解空间树可知:可行解2个,最优解是25,解向量为{0,1,0,1}。


2019算法应用题

题1(分析题)

设下列算法中的n为充分大是正整数:
s u m = 0 ; f o r ( i = 1 ; i < = n ; i + + ) { f o r ( i = 3 ∗ i ; i < = n ; j + + ) s u m = s u m + 1 } sum=0;\\ for(i=1;i<=n;i++)\{\\ for(i=3*i;i<=n;j++)\\ sum=sum+1 \\ \} sum=0;for(i=1;i<=n;i++){for(i=3i;i<=n;j++)sum=sum+1}
(1)详细计算说明算法中的子语句执行的次数多少次?
(2) O O O Θ \Theta Θ Ω \Omega Ω哪个符号更合适表示该算法的时间复杂度?
(3)算法的时间复杂度是多少?

解:
(1)在这里插入图片描述
(2)用 Θ \Theta Θ 更合适;因为该语句的执行次数与输入数据的分布无关,只与输入数据的大小n有关。
(3)算法的时间复杂度为 Θ ( n 2 ) \Theta(n^2) Θ(n2),因为当n充分大时, 0.1 n 2 < f ( n ) < 0.2 n 2 0.1n^2<f(n)<0.2n^2 0.1n2<f(n)<0.2n2,即时间复杂度 n 2 n^2 n2既是频度的上界,也是频度的下界。

题2(分析题)

在这里插入图片描述
解:(1)全局变量x-输出3125,因为当递归到达出口后,回退时永远是x最后输入的值:1 * 5 * 5 *5 * 5 * 5;
(2)局部变量x-输出1680,因为当递归到达出口后,回退时会还原x在进入该层递归时输入的值:1 * 5 * 3 * 8 * 2 * 7;
(3)可以直接转换;因为该算法是尾递归,直接将继续递归的条件变为循环条件,从递归出口向递归入口倒推。

题3(贪心法,田忌赛马问题的类似问题)

在这里插入图片描述
和田忌赛马问题一致,利用贪心法求解,先排序,然后递推即可。

题4(动态规划,设备分配问题)

在这里插入图片描述
这题有多个阶段,应该使用动态规划的策略;
该问题有局部最优性特点,而且下一阶段的决策不影响上以阶段的决策。
状态转换方程:
dp数组:

01234567
000000000
A0124881113
B0246781315
C03579111316

状态转换方程:
设原数组名为sum[3][7]
{ d p [ i ] [ j ] = 0 , i = 0 ∣ ∣ j = 0 d p [ i ] [ j ] = s u m [ i ] [ j ] , s u m [ i ] [ j ] > d p [ i − 1 ] [ j ] d p [ i ] [ j ] = m a x ( d p [ i ] [ m ] + d p [ i − 1 ] [ j − m ] ) , m 初 始 化 为 1 , m − > j \begin{cases} dp[i][j]=0 , & i=0||j=0 \\ dp[i][j]=sum[i][j], & sum[i][j]>dp[i-1][j] \\ dp[i][j]=max(dp[i][m]+dp[i-1][j-m]),&m初始化为1,m->j \end{cases} dp[i][j]=0,dp[i][j]=sum[i][j],dp[i][j]=max(dp[i][m]+dp[i1][jm]),i=0j=0sum[i][j]>dp[i1][j]m1,m>j


矩阵连乘问题

问题描述;
给你一系列的矩阵A1,A2,A3,…,An和一系列的整数P0,P1,P2…,Pn,每个矩阵 Ai 的规模为Pi-1 * Pi。
现在,请你计算这些矩阵连乘所需要的最少的计算次数是多少?

解:

  1. 找出最优子结构,在本问题中,即找出如何划分“括号”的方法。
  2. 找出最优子结构的递推式。
    定义 m [ i , j ] m[i,j] m[i,j]的值为计算Ai 所需要的最小的计算次数,
    m [ i , j ] = { 0 , i = j m i n i < = k < j ( m [ i , k ] + m [ k + 1 , j ] + p i − 1 p k p j ) , i < j m[i,j]=\begin{cases} 0,&i=j\\ min_{i<=k<j}(m[i,k]+m[k+1,j]+p_{i-1}p_kp_j),&i<j \end{cases} m[i,j]={0,mini<=k<j(m[i,k]+m[k+1,j]+pi1pkpj),i=ji<j
    Ai…k的乘积结果为一个规模为Pi-1 * Pk的矩阵,
    而Ak+1…Aj的乘积结果为一个规模为Pk * Pj的矩阵,所以,求Ai…j的最少乘积次数则为【Ai…k所需要的最少乘积次数】+【Ai+1…j所需要的最少乘积次数】+【中间生成的两个临时矩阵所需要的乘积次数】。
    将K的可能值都代进去试一遍,K的可能值只有j-i种,找出m[i,j]最小的那一个情况,此时的K值就是我们需要的,并且最小乘积次数也找到了,就是m[i,j]次。
  • 对于这类的具体问题可以采用自底向下的方式去计算最优值,将这个连乘问题拆分成两个矩阵相乘的问题,然后找出最佳的乘法分配;
  • 计算 m [ i ] [ j ] m[i][j] m[i][j]时都需要先将m[i][k]和m[k+1][j]求出来,递归到最下层只有两个矩阵相乘的时候,递增序列就求出来了。
  • 2
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlbertOS

还会有大爷会打钱?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值