0

计算机算法设计与分析复习

 2 years ago
source link: https://1905060202.github.io/2022/01/04/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%AE%97%E6%B3%95%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%88%86%E6%9E%90%E5%A4%8D%E4%B9%A0/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
胡小宁的博客

计算机算法设计与分析复习

发表于2022-01-04|更新于2022-01-08|计算机基础
阅读量:38

由于本课程是第一次面向本科专业开设,我身为第一届应考生,承担的考试压力要大许多。一是因为没有历年的真题供参考,另一方面是自己也并没有把太多心思放在这门课程的学习上。好在老师给出了复习提纲和样卷。那么我们就按照复习提纲和样卷来做一个全面的复习。

2021考题预测

1.插入算法

2.Master定理

3.DFS/BFS

4.回溯方法解决子集树/排列树的搜索问题

5.分支界限方法解决旅行商问题(较难暂时放弃)

6.Las解决0/1背包问题

7.动态规划方法解决多段图问题

8.贪心算法求解装载问题

9.归并排序/快速排序/二分排序

10.回溯方法解决最大团问题/子集和问题

2021期末预测题目全解

必须背下来的算法:

void DFS(Matrix<bool> &G,int v,vector<bool> visited,Func Visit){
int n = G.Rows();//求出顶点数
Visit(v);//访问根
Visited(v)=1;//已访问

for(int w =0;w<n;++w){
if(not Visited[w] and G(v,w)){
DFS(G,w,Visited,Visit);
}
}

}
void BFS(Matrix <bool> &G,int v,vector<bool> Visited,Func Visit){
int n = G.Rows();
queue <int> Q;
//根节点
Visit(v);
Visited[v]=1;
Q.push(v);

while(not empty(Q)){
v = Q.front();
Q.pop();
for(int w=0;w<n;++w){
if(not Visited[w] and G(v,w)==1){
Visit(w);
Visited[w]=1;
push(w);
}
}
}
}

回溯方法解决子集树搜索问题

#include <dummy.h>
void B_SubSet(int n){
vector<int> X(n);//解向量
function <void(int)> SubSet = [&](int){
if(t>=n)
Print X;
else{
if(legal(t)){
X[t]=1;
SubSet(t+1);
}
if(bound(t)){
X[t]=0;
SubSet(t+1);
}
}
}
}

回溯方法解决排列数搜索问题

#include <dummy.h>
void B_Perm(int n){
vector <int> X(n);
iota(X);//解向量需要初始化为自然排列
function<void(int)>Perm=[&](int t){
if(t>=n-1)
cout<<X<<endl;
else{
for(int i =t;i<n;++i){
swap(X[t],X[i]);
if(legal(t) and bound(t))
Perm(t+1);
swap(X[t],X[i]);
}
}
}
}

Las解决0/1背包问题

#include <LasVegas.h>
bool Knap(vector<double> &V,vector<double> &W,double c,double t,vector<bool> &X){
//参数说明:效益数组V。重量数组W 背包容量c
static mt19937g;
int n = min(size(V),size(W));
double fv = 0,fw = 0;
for(int i =0;i<n;++i){
X[i]=g()%2;
fv+=V[i]*X[i];
fw+=W[i]*X[i];
if(fw>c)
return false;
}
return fv>=t;
}

贪心算法求解装载问题

auto Load(vector<double> &W,double M){
int n =size(W);
sort(W);
vector<bool>X(n,0);
double rc = M;
for(int i=0;i<n and W[i]<=rc;++i){
X[i] = 1;
rc- = W[i];
return X;
}
}

回溯方法解决最大团问题

Clique(t,cn)//用 cn 记录当前团顶点数
{
if(t>=n and cn >fn){ // 答案 ( 更优 )
fn = cn;
BX = X;
}else if(t<n){
if(Connected(G,X,t))// 可行 ( 当前成团 )
{
X[t]=1;
Clique(t+1,cn+1);
}
if(cn+n-t>fn){// 界限 ( 可能有更大的团 )
X[t]=0;
Clique(t+1,cn);
}
}
}

回溯方法解决子集和问题

SetSum(t,s,r){
if(not Y.empty())
return; // 已经记录答案 , 不再继续寻找
// 已经找到答案
if(s==M)// s 为已选整数的和 ( 和 ), r 为待处理的所有整数的和 ( 余额 )
print X;
else if(t<n){
if(s+W[t]<=M){ // 可行 , t+1 号数 , 和增加 , 余额减少
X[t]=1;
SetSum(t+1,s+W[t],r-W[t+1]);
}
if(s+r-W[t]>=M){// 界限 , t+1 号数 , 和不变 , 余额减少
X[t] = 0;
SetSum(t+1,s,r-w[t+1]);
}
}
}
#include <algorithm.h>
template<class T>
//合并两个有序组
void Merge(T X[],int low,int m,int up){
T W[up-low];//一个缓冲区
int i = low,j = m,k=0;//左指针,右指针,结果游标k
for(;i<m and j<up;k++)
{
if(X[i]<X[j])
{
W[k] = X[i];
i++;
}else
{
W[k] = X[j];
j++;
}
}
for(;i<m;i++){//剩余的左侧
W[k] = X[i];
k++;
}
for(;j<up;j++){//剩余的右侧
W[k] = X[j];
j++;
}

void MergeSort(T X[],int low,int up){//归并排序
if(up-low<=1)
return;
int m =(low+up)/2;//计算分割点
MergeSort(X,low,m);//左排序
Mergesort(X,m,up);//右排序
Merge(X,low,m,up);//合并
}
#include <algorithm.h>
//以一个数为基准,将序列中的其他数往它两边“扔“,即是分治思想
//首先要写一个划分程序
//非常漂亮的算法,省去了左右指针
template<class T> // 划分 , 待划分区间为 [low, up), 左侧存放小元素
int Partition(T X[], int low, int up){
//我们从尾部开始划分,不用移动元素
int key = up-1;
int m = low;//划分位置
for(int i =low;i<up;i++){
if(X[i]<X[key]){//小元素调整到左侧
swap(X[i],X[m]);
m++;//左侧元素增加,划分位置后移
}
}
swap(X[key],X[m]);//将划分元素调整到划分位置
return m;//返回划分位置
}

void QuickSort(T X[],int low,int up){//快排主程序
if(up-low<=1)
return;
//要标志划分位置m
int m =Partition(X,low,up);
QuickSort(X,low,m);//左排序
QuickSort(X,m+1,up);//右排序
}

#include <algorithm.h>
template<class T1,class T2>
int search(T x[],int n,const T2 &v){
//分治之思想,把元素丢到两边然后重复
int low = 0;
int up = n;
while(low < up){
int m =(low+up)/2;
if(X[v]<X[m]){
up = m;
}
if(X[v]>X[m])
low = m+1;
if(X[v]==X[m])
return m;
}
return -1;
}

template <class T,class T2>
int insert(T X[],int m,const T2 &V){
int p;
for(p = m-1;p>0 and X[p]>v;--p)
if(X[p]==v)
return m;
for(int i=m-1;i>=p;--i){
X[i+1]=X[i];
X[p+1] = v;
return m+1;
}
}

二叉树的遍历

二叉树的C++表示

#include <algorithm.h>
template<class T>
struct BtNode//二叉树结点
{
T data;
BtNode *left = 0,*right = 0;//左子树,右子树
};

二叉树的先根次序遍历

template<class T,class Func>//先根遍历
void PreOrder(BtNode<T> *x ,Func Visit)
{
if(x==0)
return;
Visit(x)//访问根
PreOrder(x->left,Visit);//遍历左子树
PreOrder(x->right,Visit);//遍历右子树
}

中根遍历与后根遍历只是调换了顺序,故不再赘述。

二叉树的按层次遍历

template<class T,class Func>
void LevelOrder(BtNode<T> *x,Func Visit)
{
queue<BtNode<T> *> Q;
if(x!=0){
Visit(x);
Q.push(x);//访问根并将根加入队列
}
while(not empty(Q)){
x = Q.front();
Q.pop();
//访问x的左右儿子并将左右儿子加入队列
auto left = x->left;
auto right = x->right;
if(left!=0)
{
Visit(left)
Q.push(left);
}
if(right!=0){
Visit(right)
Q.push(right);
}
}
}

一般树的遍历

树的C++表示

#include <algorithm.h>
template<class T>//树节点类
struct TreeNode{
T data;
TreeNode *first = 0,next = 0;//第一棵子树,下一棵树(兄弟)
}

树的先根遍历

template<class T,class Func>
void PreOrder(TreeNode<T> *x,Func Visit){
if(x==0)
return;
Visit(x);//访问根
PreOrder(x->first,Visit);//遍历第一棵子树
for(x=x->first;x!=0;x=x->next){
PreOrder(x->next,Visit);//遍历其余子树
}
}

树的中根遍历只是与先根遍历的顺序不同,不再赘述。

树的后根遍历

template<class T,class Func>
void PostOrder(TreeNode<T> *x,Func Visit){
if(x==0)
return;
auto t = x->first;
PostOrder(t,Visit);
for(;t!=0;t=t->next){
PostOrder(t>next,Visit);
Visit(x);
}
}

树的按层次遍历

template<class T,class Func>
void LevelOrder(TreeNode<T> *x,Func Visit){
queue<TreeNode<T> *> Q;
if(x!=0){
Visit(x);
Q.push(x);//访问根并将根加入队列
}
while(not empty(Q)){
x = Q.front();
Q.pop();
//访问x的所有儿子并将所有儿子加入队列
for(x=x->first;x!=0;x=x->next){
Visit(x);
Q.push(x);
}
}
}

连通图的两种遍历方法

宽度优先搜索BFS

#include <algorithm.h>
template<class Fun>
void BFS(Matrix<bool> &G,int v,vector<bool> &Visited,Fun Visit){
//参数:邻接矩阵,开始顶点,访问标记(已经初始化为0),访问函数
int n =G.Rows();//获取矩阵的行数,即顶点数
queue<int> Q;//创建空队列
Visit(v);
Visited[v] = 1;
Q.push(v);//访问v并加入队列
while(not empty(Q)){
v = Q.front();
Q.pop();//取出队首元素v
for(int w=0;w<n;++w){//遍历所有顶点,康康有无有与自己相连的边
if(not Visited[w] and G(v,w)==1){
//w与v相邻且未访问
Visit(w);
Visited[w]=1;
Q.push(w);//访问w,入队
}
}
}
}

深度优先搜索DFS

#include <algorithm.h>
template<class Fun>//搜索一个连通分支
void DFS(Matrix<bool> &G,int v,vector<bool> &Visited,Fun Visit){
//参数:邻接矩阵,开始顶点,访问标记,访问函数
int n =G.Rows();
Visit(v);
Visited[v]=1;
for(int w =0;w<n;++w){
if(not Visited[w] and G(v,w)==1){
//w与v相邻且未访问
DFS(G,w,Visited,Visit);//访问w
}
}
}
#include <algorithm.h>
template<class T1,class T2>
int search(T x[],int n,const T2 &v){
//在升序数组X中搜索v所在的位置
int low = 0;
int up = n;
while(low<up){
int m = (low+up)/2;
if(v==X[m])
reuturn m;
else if(v<X[m])
up=m;//左侧继续搜索
else
low = m+1;//右侧继续搜索
}
return -1;//-1表示未找到v
}
#include <algorithm.h>
template<class T>
//合并两个有序组
void Merge(T X[],int low,int m,int up){
T W[up-low];//缓冲区
int i=low ,j=m,k=0;//左侧游标i,右侧游标j,结果游标k;
for(;i<m and j<up;++k){//当两侧都未取尽时
if(X[i]<X[j])//左侧元素较小
{
W[k] = X[i];
++i;
}else{//右侧元素较小
W[k]=X[j];
++j;
}
for(;i<m;++k){//左侧剩余部分添加到W
W[k]=X[i];
++i;
}
for(;j<up;++k){//右侧剩余部分添加到W
W[k]=X[j];
++j;
}
}
}
//这个算法是有错的,请自行鉴别

void MergeSort(T X[],int low,int up){
if(up-low<=1)
return;//最多一个元素
int m = (low+up)/2;//分割点
MergeSort(X,low,m);//左侧排序
MergeSort(X,m,up);//右侧排序
Merge(X,low,m,up);//合并
}

#pragma once
#include <algorithm.h>

template<class T> // 划分 , 待划分区间为 [low, up), 左侧存放小元素
int Partition(T X[], int low, int up)
{
int key = up - 1, m = low; // 划分元素选用尾部元素 , 用 m 记录划分位置
for(int i = low; i < key; ++i) // 小元素调整到左侧 ( 划分元素不参与比较 )
if(X[i] < X[key])
swap(X[i], X[m]), ++m; // 左侧元素增加 , 划分位置后移
swap(X[key], X[m]); // 将划分元素调整到划分位置
return m; // 返回划分位置
} /

void QuickSort(T X[],int low,int up){
if(low>=up)
return;//没有元素
int m =Partition(X,up,low);//划分位置
QuickSort(X,low,m);//左侧排序
QuickSort(X,m+1,up);//右侧排序
}
#include <algorithm.h>
auto Load(vector<double> &W,double M){
//参数:重量数组,货船容量
int n = size(W);//货箱个数
sort(W);//重量数组W升序排列
vector<bool> X(n,0);//解向量初始化为0
double rc = M;//背包剩余容量
for(int i=0;i<n and W[i]<=rc;++i){
X[i] = 1;
rc- = W[i];
//装入货箱i
}
return X;
}
auto Knap(vector<double> &V,vector<double> &W,double M){
//参数:价值数组,重量数组,背包容量
int n = min(size(V),size(W));//物品数
Sort(V,W);//将物品按单价降序排列;
vector<double> X(n,0);//解向量初始化为零
double rc = M;//背包剩余容量
int t;//当前物品
for(t = 0;t<n and W[t]<=rc;++t){
X[t] = 1;
rc - =W[t];//可完整装包的物品完整装包
}
if(t<n){
X[t]=rc/W[t];//不能完整装包的物品部分装包
return X;
}
}

活动安排问题


#include <algorithm.h>

auto Action(vector<double> &S,vector<double> &F){
//参数:开始时间数组,结束时间数组
int n = min(size(S),sieze(F));
Sort(S,F);//将活动按照结束时间升序排列
vector<bool> X(n,0);//解向量初始化为零
X[0] = 1;//将活动0加入解中
for(int i =1,j=0;i<n;++i){
if(S[i]>=F[j]){
X[i] = 1;
j = i;//将活动i加入解中
}
}
return X;
}

最小生成树问题

bool Prim(const Matrix<double> &G,int v,vector<int> &prev){
int n = G.Rows();//G是加权图,顶点数为n
prev.assign(n,v);//prev用于保存各顶点的父亲,初始根为v
vector<bool> S(n,false);//S标志顶点是否已选,初始为0
S[v] = true;//选取v
for(int i =0;i<n-1;++i){
//寻找已选项点到未选项点权值最小的顶点
double min = inf;//min初始为无穷大
for(int w=0;w<n;++w){
if(not S[w] and G(prev[w],w)<min)
{
min = G(prev[w],w);
v=w;
}
}
if(isinf(min))
return false;//不连通
S[v] = true;//选取v
for(int w = 0;w<n;++w){//更新未选顶点父亲
if(not S[w] and G(v,w)<G(prev[w],w))
prev[w] = v;
}
}
return true;//连通
}

动态规划方法

矩阵连乘问题的动态规划方法

auto MatrixChain(int r[],int n){
//动态规划方法
Matrix<int> c(n,n,0);//c(i,j)初始为0
Matrix<int> kay(n,n);//用于构造最优次序
for(int i =n-2;i>=0;--i){
//计算c(i,j)和kay(i,j).其中j>i
for(int j = i+1;j<n;++j){
//从k = i开始计算最小项,并更新kay
c(i,j) = (int)inf;
for(int k =i;k<j;++k){
int t = c(i,k)+c(k+1,j)+r[i]*r[k+1]*r[j+1];
if(t<c(i,j)){
c(i,j) = t;
kay(i,j) = k;//找到更小项
}
}
}
}
}

多段图问题

auto MultiGraph(Matrix<double> &G){
//邻接矩阵G,顶点按段序统一编号
int n = G.Rows();
t = n-1;//顶点数,汇点
vector<double> C(n,0);//C[j]是j到汇点的最小成本,初始为0
vector<int> Next(n);//Next[j]是j到汇点的最短路径中j的后继顶点
for(int j =t-1;j>=0;--j){
//从j的后一个阶段中选择顶点r,使G(j,r)+C[r]最小
int r = j+1;
for(int i =r;i<n;++i){
if(G(i,j)+C[i]<G(j,r)+C[r])
r = i;
C[j] = G(j,r)+C[r];
Next[j] = r;
}
}
vector<int> X(m);//X[i]是最短路径中第i阶段选取的顶点
X[0] = 0;
for(int i = 0;i<m-1;++i){
X[i+1] = Next[X[i]];
}
return X;
}

子集和问题

SetSum(t,s,r){
if(s==M)
print X;
else if(t<n){
if(s+W[t]<=M){
X[t] = 1;
SetSum(t+1,s+W[t],r-W[t+1]);
}
if(s+r>=M){
X[t] = 0;
SetSum(t+1,s,r-W[t+1]);
}
}
}

旅行商问题

#include <algorithm.h>
auto TSP(Matrix<double>&G){
int n = G.Rows();//顶点个数
vector<int> X(n),BX;//到当前结点的路经,最优路径
iota(X);//解向量X需初始化为自然排列
double BC = inf;//最优耗费
function<void(int,double)> TSP=[&](int t,double C){
auto LC = C+G(X[t-1],X[t])+G(X[t],0);//当前回路耗费
if(t>=n-1 and LC<BC){
BC = LC;
BX = X;//答案,更优
}else if(t<n-1){
for(int i = t;i<n;++i){
swap(X[t],X[i]);
auto CC = C +G(X[t-1],X[t]);//当前路经耗费
if(CC<BC){
TSP(t+1,CC);//可行(当前路径可能更优)
}
swap(X[t],X[i]);
}
}
}
}

最大团问题

function Clique(t,cn)
{
if(t>=n and cn>fn)
{
fn = cn;
BX = X;
}else if(t<n){
if(Connected(G,X,t)){
X[t] = 1;
Clique(t+1,cn+1);
}
if(cn+n-t>fn){
X[t]=0;
Clique(t+1,cn);
}
}
}

0/1背包问题

#include <LasVegas.h>
bool Knap(vector<double> &V,vector<double> &W,double c,double t,vector<bool> &X){
//参数:效益数组V 重量数组W 背包容量c
static mt19937g;//随机数产生器
int n =min(size(V),size(W));
double fv = 0;//最后效益
double fw = 0;//最后重量
for(int i = 0;i<n;++i){//随即决定物品i的选取
X[i] = g()%2;
fv+=V[i]*X[i];
fw+=W[i]*X[i];
if(fw>c)
return false;
}
return fv>=t;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK