0
点赞
收藏
分享

微信扫一扫

【算法设计与分析】期末抱佛脚复习

菜菜捞捞 2022-01-06 阅读 52
算法

写在前面:
是抱佛脚复习,不全。
知识和代码没有标出处的都来自书上:《计算机算法设计与分析》王晓东

第1章 算法概述

算法的四条性质

  1. 输入:有0个或多个输入
  2. 输出:至少产生一个输出
  3. 确定性:组成算法的每条指令是无歧义的
  4. 有限性:每条指令的执行次数和时间是有限的

算法复杂性依赖的三个要素

算法时间复杂度:最好、最坏、平均
最具有实际价值的是 最坏情况下的 时间复杂度。
在这里插入图片描述
关于时间复杂度的基本要点

1.时间复杂度反映的是随着问题规模的变大,计算所需的时间的增长速度,与系数的多少关系不大
2.算法的渐近时间复杂度,简称时间复杂度,很多时候为了便于理解,直接把时间复杂度等同于O()是可以的。
3.常见的时间复杂度,及其增长速度比较
O(1)<O(log n)<O(n)<O(nlog n)<O(n2)<O(n3)< O(2n)<O(n!)<O(nn)

大O运算规则:
(1) O(f)+O(g)=O(max(f, g))
(2) O(f)+O(g)=O(f+g)
(3) O(f)O(g)=O(fg)
(4) 如果g(N)=O(f(N)), 则O(f)+O(g)=O(f)
(5) O(Cf(N))=O(f(N)), 其中C是一个正常数
(6) f=O(f)
来自这里

P和NP问题

什么是P类问题?
多项式时间算法,即对规模为n的输入,算法在最坏情况下的时间复杂度为 O(nk)
所有在多项式时间内能够求解的判定问题构成P类问题

什么是NP类问题?
P类问题是确定性计算模型下的易解问题类,而NP类问题是非确定性计算模型下的易验证问题类。(N即non-…)

简而言之,NP类问题,没法在多项式时间内找到解,但是可以在多项式时间内验证解。

P类问题属于NP类问题。

什么是NP完全问题?
NP完全问题,即NPC问题(C是complete)

什么是NP难度问题?

引用来自这里

即,P类是在多项式时间内求解NP是在多项式时间内内验证NP完全是:是NP类同时可以约化到它;NP难问题是:所有NP可以约化到它,但不一定是NP问题
在这里插入图片描述

第2章 递归和分治策略

斐波那契数列

int Fibon1(int n){
    if (n == 1 || n == 2){
        return 1;
    } else{
        return Fibon1(n - 1) + Fibon1(n - 2);
    }

汉诺塔

void hanoi(int n,int a,int b,int c)
{
	if(n>0)
	{
		hanoi(n-1,a,c,b);
		move(a,b);//将a上的放到b
		hanoi(n-1,c,b,a);
	}
 } 

分治法的三步及代码
分治法在每一层递归上都有三个步骤:

分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
合并:将各个子问题的解合并为原问题的解。

它的一般的算法设计模式如下:

Divide-and-Conquer(P)
1.  if |P|≤n0
2.    then return(ADHOC(P))//规模较小直接解决
3.  将P分解为较小的子问题 P1 ,P2 ,...,Pk
4.  for i←1 to k
5.    do yi ← Divide-and-Conquer(Pi)    △ 递归解决Pi//无法直接解决 所以要递归地解决
6.  T ← MERGE(y1,y2,...,yk)             △ 合并子问题//合并
7.  return(T)

其中|P|表示问题P的规模;n0为一阈值,表示当问题P的规模不超过n0时,问题已容易直接解出,不必再继续分解。ADHOC§是该分治法中的基本子算法,用于直接解小规模的问题P。因此,当P的规模不超过n0时,直接用算法ADHOC§求解。算法MERGE(y1,y2,…,yk)是该分治法中的合并子算法,用于将P的子问题P1 ,P2 ,…,Pk的相应的解y1,y2,…,yk合并为P的解。

来自这里

求时间复杂度的主定理
if(logb(a)!=d),T(n)=nmax(logb(a),d) ;
else T(n)=ndlogn;(即再乘一个logn)

其中b是除数,作为log的底。比较logba与n的系数d的大小,取大的。
如果一样大就取d,然后再乘个logn
在这里插入图片描述
二分搜索

int BinarySearch(int n)//在0到n-1之间搜索 
{
	int l=0,r=n-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(x==a[mid]) return mid;//找到了
		else if(x<a[mid]) r=mid-1;
		else if(x>a[mid]) l=mid+1;
	}
	return -1;//能运行到这里说明上面没有找到过 
}

每执行一次while算法,数组大小都会减小一半,故最多执行logn词。
最坏情况下的时间复杂性为 O(logn)

归并排序
时间复杂度O(nlogn)

void MergeSort(int l,int r)
{
	if(l<r)//至少有两个元素,不能重合 
	{
		int mid=(l+r)/2;
		MergeSort(l,mid);//分解 
		MergeSort(mid+1,r);
		Merge(l,mid,r);//合并 
		Copy();//复制数组 
	}
 } 

总代码模板:

void merge_sort(int q[],int l,int r)
{
	if(l>=r) return;
	
	int mid=(l+r)/2;
	
	merge_sort(q,l,mid);
	merge_sort(q,mid+1,r);
	
	int k=0,i=l,j=mid+1;//k是 结果数组里的个数
	while(i<=mid&&j<=r)
	{
		if(q[i]<=q[j]) temp[k++]=q[i++];
		else temp[k++]=q[j++];
	 } 
	
	while(i<=mid) temp[k++]=q[i++];
	while(j<=r) temp[k++]=q[j++];
	
	for(i=l,j=0;i<=r;i++,j++) q[i]=temp[j];
}

快速排序
有中枢元素。
平均O(nlogn),最坏O(n2)(刚好逆序的时候)

void QuickSort(int l,int r)
{
	if(l<r)
	{
		int mid=partition(l,r);//划分,其实就是核心的排序——找一个中枢,让左边的都小,右边的都大 
		QuickSort(l,mid);//递归对左半段排序 
		QuickSort(mid+1,r);
	}
 } 
 
int partition(int l,int r)
{
	int i=l,j=r+1;//因为j要--j 先算后用,所以j要先+1
	int x=a[l];
	while(1)
	{
		while(a[++i]<x&&i<r);//注意左指针要小于右指针 
		while(a[--j]>x);
		if(i>=j) break;
		swap(a[i],a[j]);//但凡走到这一步,说明a[i]>=x,a[j]<=x 该交换了 
	} 
	a[l]=a[j];
	a[j]=x;//交换a[l]和a[j],把a[l]放在中间——它本来就是中枢元素 
	return j;//返回划分点的下标 
}

一些更好理解的其他模板:

void quick_sort(int q[],int l,int r)
{
	if(l>=r) return;//i j 相遇
	int x=q[l+r>>1],i=l-1,j=r+1; //确定x,中间数,ij开始要在范围外——这样一进去就能比较 
								//x在最左或最右的可能出现最坏情况 
	while(i<j)
	{
		do i++;while(q[i]<x);
		do j--;while(q[j]>x);
		if(i<j) swap(q[i],q[j]);
	} 
	
	quick_sort(q,l,j);//左右递归 可以写i,因为ij左右对称 
	quick_sort(q,j+1,r);
}

中位数问题
思想:将两个有序的数组合成一个有序的数组解决问题。
题目:给定两递增数组a1和a2,求两数组元素集合的中位数。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100000+10;
int n;
int a[N],b[N];
int c[2*N]; 
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>a[i];
	}
	for(int i=0;i<n;i++)
	{
		cin>>b[i];
	}
	int k=0,i=0,j=0;
	while(i<n&&j<n)
	{
		if(a[i]<=b[j]) c[k++]=a[i++];
		else c[k++]=b[j++];
	}
	while(i<n) c[k++]=a[i++];
	while(j<n) c[k++]=b[j++];
	
	cout<<c[(k-1)/2];
	
	return 0;
}

第3章 动态规划

动态规划的步骤

  1. 分析最优解结构
  2. 建立递归关系
  3. 计算最优值

动态规划的基本要素

  1. 最优子结构
  2. 重叠子问题

动态规划法的变形——备忘录方法。

动态规划与分治法的区别
动态规划算法与分治法类似,其基本思想是将待求解问题分解成若干子问题,先求解子问题,再结合这些子问题的解得到原问题的解。与分治法不同的是,适合用动态规划法求解的问题经分解得到的子问题往往不是互相独立的。 若用分治法来解这类问题,则分解得到的子问题数目太多,以致最后解决原问题需要耗费指数级时间。
来自:宝哥的期末复习

矩阵连乘问题
可以看这里

最大子段和

int besti,bestj;//用来存答案ij下标
int n;//数字个数 数组是a[n] 
int MaxSum()
{
	int ans=0;//全为负数就为0 所以初始化为0 
	for(int i=1;i<=n;i++)
	{
		int temp=0;
		for(int j=i;j<=n;j++)
		{
			temp+=a[i];
			if(temp>ans)//比已经存的最大值要大 
			{
				ans=temp;
				besti=i;
				bestj=j;
			}
		}
	}
}

大概这样,可以求出所有的子端和。
在这里插入图片描述
01背包问题
一个AcWing上面优秀的解析

第4章 贪心算法

注意:局部最优未必总体最优。

会场安排问题
代码可以看看这里

贪心算法的两个基本要素

  1. 贪心选择性质
  2. 最优子结构性质

贪心与背包和01背包
背包问题可以用贪心求解,01背包却不行。
因为它无法保证背包最终能装满,部分闲置的背包空间使单位背包空间价值降低了。

哈夫曼编码
是使用字符在文件中出现的频率表来建立一个用0,1串表示各字符的最优表示方式。
出现频率高的字符——较的编码。
出现频率低的字符——较的编码。
是文件的最优编码方案。

在这里插入图片描述
单元最短路径
一个模板bfs。

代码也许之后补充。

第5章 回溯法

回溯法有 “通用解题法” 之称。用它可以系统地搜索问题的所有解。回溯法是一个既带有系统性又带有跳跃性的搜索算法。
在包含问题的所有解的解空间树中,按照 深度优先搜索 的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。 (其实回溯法就是对隐式图的深度优先搜索算法) 。若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。

回溯法的三个步骤

  1. 针对所给问题,定义问题的解空间
  2. 确定易于搜索的解空间结构
  3. 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索

01背包问题
回溯法的背包问题

第6章 分支限界法

其他

同学的复习:点这里

举报

相关推荐

0 条评论