初级算法合集【1】

把这个学期学习的算法整理了一遍。

一、动态规划

1、最长公共子序列

#include <iostream>
#include <cstdio>
#include <string>
#define MAX 10001
using namespace std;

int x[MAX];
int y[MAX];
int c[MAX][MAX];

int max(int a, int b)
{
	if (a > b) return a;
	else return b;
}

int main() 
{

	int i;
	int j;
	int n;
	int m;
	cin >> n;
	cin >> m;
	for (i = 1; i <= n; i++)
		cin >> x[i];
	for (i = 1; i <= m; i++)
		cin >> y[i];
	
	for (i = 0; i <= n; i++)
	{
		for (j = 0; j <= n; j++)
		{
			if (i == 0 || j == 0)
			{
				c[i][j] = 0;
			}
			else if (x[i] == y[j])
			{
				c[i][j] = c[i - 1][j - 1] + 1;
			}
			else 
			{
				c[i][j] = max(c[i][j - 1], c[i - 1][j]);
			}
		}
	}
	cout << c[n][n];
}

2、最大子段和

b[1]=max{a[1]};
b[2]=max{a[1]+a[2],
a[2]};
b[3]=max{a[1]+a[2]+a[3],
a[2]+a[3],
a[3]};
b[4]=max{a[1]+a[2]+a[3]+a[4],
a[2]+a[3]+a[4],
a[3]+a[4],
a[4]};
则最大子段和为:MAX { b[1] , b[2], b[3,]……., b[n] }
b[j] = max{b[j-1]+a[j], a[j]}, 1≤j ≤n
b[j]=a[j] // b[j-1]<0
b[j]=b[j-1]+a[j] // b[j-1]>=0

int  MaxSum(int *a,  int n)
{    int  sum=0,b=0;
      for( j=1; j<=n;  j++)
        {   if ( b>0)   b+=a[j];  else   b=a[j];
              if(b>sum) sum= b;    }
     return sum;
}

3、找零钱问题

问题描述
现有种不同面值的货币,每种面值的货币可以使用任意张。顾客结账时,你需要找给顾客元零钱,你可以给出多少种方法。例如,有1、2、3元三种面值的货币,你需要找零3元,那么共有3种方法:
1 1 1
1 2
3
输入零钱数组 penny[n]
找零目标 aim
寻找有多少种方法来组成 aim (每种零钱可以使用无数张)

分析
设 dp[i][j] 为 前 i 种货币凑成目标 j 的总方法数
dp[i][j]=dp[i-1][j]+dp[i-1][j-penny[i]]+…dp[i-1][j-kpenny[i]]
k代表使用 penny[i] 的个数
其中 k 是保证j-k
penny[i]>=0 的最大值

可以继续推下去

设 x =j-penny[i]
则dp[i][x]=dp[i-1][x]+dp[i-1][x-penny[i]]+…dp[i-1][x-kpenny[i]]
所以 dp[i-1][j-penny[i]]+…dp[i-1][j-k
penny[i]]=dp[i][x]=dp[i][j-penny[i]]

dp[i][j]=dp[i-1][j]+dp[i][j-penny[i]]
边界条件为 j>=penny[i]

int core (int penny[], int n, int aim)
{
        for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= aim; j++) {
            if (j < penny[i]) 
            {
                dp[i][j] = dp[i - 1][j];
            } 
            else 
            {
                dp[i][j] = dp[i - 1][j] + dp[i][j - penny[i]];
            }
        }
    }
    return dp[n][aim];
}

4、走方格(最小路径和)

有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
给定一个矩阵map及它的行数n和列数m,请返回最小路径和。

设dp[n][m]为走到n*m位置的路径长度,那么显而易见dp[n][m] = min(dp[n-1][m],dp[n][m-1]);

int core(int map[][MAX], int n, int m)
{
      for(int i=1;i<n;i++){  
            for(int j=1;j<m;j++){  
             dp[i][j] = min(dp[i][j-1]+map[i][j],dp[i-1][j]+map[i][j]);     
            }  
        }  
        return dp[n-1][m-1];  
}  

5、最长上升子序列问题

给定一个数字序列a,求该序列中最长上升子序列的长度。例如a={1,4,2,5,3},其最长上升子序列为{1,2,3},因此最长上升子序列的长度为3

假设序列a长度为n,构建一个长度为n的一维数组dp[n],dp[i]代表了以a[i]结尾的上升子序列的长度。很显然,dp[i] = 1 + max{dp[j] | a [j] < a[i] 且 j < i } ,最长上升子序列的长度为max{dp[i]}

int core(int map[][MAX], int n, int m)
{
        dp[1] = 1;
        for (int i = 1; i <= n; i++) 
        {
            int max = 0;
            for (int j = 1; j < i; j++) 
            {
                if (a[i] > a[j]) 
                {
                    max = Math.max(max, dp[j]);
                }
            }
            dp[i] = max + 1;
        }
        
        int result = 0;
        for (int i = 1; i <= n; i++) 
        {
            if (result < dp[i]) 
            {
                result = dp[i];
            }
        }
        return result;
}  

5、背包问题

1、01背包

一个背包有一定的承重c,有n件物品,每件都有自己的价值,记录在数组v中,也都有自己的重量,记录在数组w中,每件物品只能选择要装入背包还是不装入背包,要求在不超过背包承重的前提下,选出物品的总价值最大。

设dp[i][j] 为前 i 个物品用 j 的重量来装时可以放下的最大价值

dp[i][j]=max{dp[i-1][j-w[i]]+v[i],dp[i-1][j]}
前面的是放第i个物品,后面的是不放。

int core (int c, int n, int w[], int v[])
{
        for (int i = 1; i <= n; i++) 
        {
            dp[i][0] = 0;
        }
        for (int i = 1; i <= n; i++) 
        {
            for (int j = 1; j <= c; j++) 
            {
                if (j >= w[i]) //能放下
                {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
                } 
                else 
                {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[n][c];
}

空间优化
dp[i][j] 每次变化都与 dp[i-1][x] 有关
所以 得到新的状态转移方程
dp[j]=max(dp[j],dp[j-w[i]]+v[i])
其实数组每次写入一行数据都是根据上一行的数据进行写入,不需要用二维数组,可以用一维数组,每次根据自身进行更新即可,重点是[i-1][j - weight[i]]这个数据的意思是我要在表中上一行找比j小的数据,那换成一维的话,我找的是自身(只有一行,没有上一行)j左边的数据,这个数据必须保证是上一次(i-1)写入的,肯定不能从左向右写入数据,那样的话[i-1][j - weight[i]] 对应的就是这一次(i)写入的,上一次数据没有利用就丢失了,所以j循环应该从M到1,从右向左向一维数组写入数据。


	for(int i = 1; i <= n; i++)
	{
		for (int j = c; j >= 1; j--)
		{
			if (j >= w[i])
			{
				dp[j] = Math.max(dp[j], dp[j -w[i]] + v[i]);
			}
		}

2、完全背包

   for(int i=1; i<=n; i++)
        for(int j=w[i]; j<=c; j++)//注意此处,与0-1背包不同,这里为顺序,0-1背包为逆序
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

二、贪心

1、活动安排问题

设有n个活动的集合E={1,2,…, n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。
每个活动 i 都有一个要求使用该资源的起始时间 si 和一个结束时间 fi ,且si < fi 。如果选择了活动i,则它在半开时间区间[si, fi)内占用资源。若区间[si, fi)与区间[sj, fj)不相交,则称活动 i与活动 j是相容的。也就是说,当si ≥ fj或sj ≥ fi时,活动 i与活动 j相容。
活动安排问题是,在所给的活动集合中选出最大的相容活动子集合。

void GreedySelector(int n, Type s[], Type f[], bool A[])
{ // s数组存储活动起始时间,f数组存储活动结束时间,且各活动按结束时间已按非减序排列;A数组存储被选中的活动。
       A[1]=true;  // 选择活动1(结束最早的活动)
       int j=1; // j记录最近一次被选中的活动
       for (int i=2;i<=n;i++) {
          if (s[i]>=f[j]) { A[i]=true; j=i; }
          else A[i]=false;
          } // 每次总是选择具有最早完成时间的相容活动加入集合A中
}

三、回溯法

1、装载问题

有n个集装箱要装上2艘载重量分别为C1和C2的轮船。其中集装箱i的重量为Wi,且(W1+W2+….+Wn<=C1+C2)。
装载问题是,是否有一个合理装载方案,可将这n个集装箱都装上这2个轮船,若有,请给出解决方案。

#define num 100
int n,c1,c2,w[num];// n个集装箱,A,B货轮载重量分别为C1,C2,W[i],第i个集装箱的重量
int cw,bw,rw;//cw,当前集装箱货物重量;bw,最优载重重量,rw,剩余集装箱重量;
int x[num],bx[num];//x[],A货轮的当前结果;bx[],A货轮的最优结果;
void BackTrack(int i) 
{
    //处理完了前n个集装箱;
    if(i>n){
        if(cw>bw){//cw,目前A中装了cw重量的集装箱;
        //更新最优解;
            bw=cw;
            for(int i=1;i<=n;i++) bx[i]=x[i];
        }    
        return;
    }
    //rw表示处理完第i个之后(选或不选),还剩下rw-w[i]重量的集装箱未处理;
    rw-=w[i];

    if(cw+w[i]<=c1){//cw,第i个货箱之前的重量 + 第i个货箱小于A的最大重量C1;
        cw+=w[i];//加上
        x[i]=1;//标记i被选
        BackTrack(i+1);
        cw-=w[i];//减去重量
        x[i]=0;//撤销标记;
    }
    //不选择第i个物品的话;
    //if cw:表示[1:i)的数据  rw:表示(i,n]的数据 ,不包括第i个的数据
    //如果不包括第i的数据的和(cw+rw) 大于  目前最优解bw,则可以递归下去;
    //如果cw+rw<=bw 那么就算把剩下的全部装入也不可能得到更好的答案,所以省去。
    if(cw+rw > bw){
        x[i]=0;
        BackTrack(i+1);
    }
    rw+=w[i];
    return ;
}
int main()
{
    //读入数据;
    scanf("%d%d%d",&n,&c1,&c2);
    for(int i=1;i<=n;i++) 
    {
        scanf("%d",&w[i]);
        rw+=w[i];//rw表示目前最优集装箱的剩余重量;
    }

    //递归回溯
    BackTrack(1);

    //bw表示A货轮装下的货物重量;剩余的重量 > B可以放下的最多,则不可;
    if(rw-bw>c2){
        printf("没有装载方案\n");
    }else{
        printf("货轮A:\n");
        for(int i=1;i<=n;i++) if(bx[i]) printf("%d ",i);
        printf("\n货轮B:\n");
        for(int i=1;i<=n;i++) if(0==bx[i]) printf("%d ",i);
    }
    return 0;   
}

2、批处理作业调度问题

给定 n 个作业的集合 j = {j1, j2, …, jn}。每一个作业 j[i] 都必须先由机器1处理,然后由机器2处理。作业 j[i] 需要机器 j 的处理时间为 t[j][i] ,其中i = 1, 2, …, n, j = 1, 2。对于一个确定的作业调度,设F[j][i]是作业 i 在机器 j 上的完成处理的时间。所有作业在机器2上完成处理的时间之和 称为该作业调度的完成时间之和。 批处理作业调度问题要求对于给定的 n 个作业,制定最佳作业调度方案,使其完成时间和达到最小。

int x[100],bestx[100],m[100][100];//m[j][i]表示在第i台机器上作业j的处理时间  
  //数组bestx记录相应的当前最佳作业调度。
int f1=0,f2,cf=0,bestf=10000,n;  //bestf记录当前最小完成时间和
  
  
void swap(int *x,int t,int j)
{
	int temp = x[t];
	x[t] = x[j];
	x[j] = temp;
} 
  
void Backtrack(int t)  
{  
    int tempf,j,i;  
    if(t>n) //到达叶子结点,搜索到最底部  
    {  
            for( i=1; i<=n; i++)  
                bestx[i]=x[i];  
            bestf=cf;  
 
    }  
    else    //非叶子结点  
    {  
        for(j=t; j<=n; j++)  
        {  
            f1+=m[x[j]][1];  //记录作业在第一台机器上的完成处理时间
            tempf=f2;//保存上一个作业在机器2的完成处理时间  
            f2=(f1>f2?f1:f2)+m[x[j]][2];//机器2开始作业的时间为机器1完成该作业的时间与机器2完成上一个作业的时间中较大的那一个。
            cf+=f2;               //cf记录当前在机器2上的完成时间和  
            if(cf<bestf)  //贪心???
            {  
                swap(x,t,j);  //交换两个作业的位置  
                Backtrack(t+1);  
                swap(x,t,j);  
            }  
            f1-=m[x[j]][1];  
            cf-=f2;  
           f2=tempf;  
        }  
    }  
} 
 
 
int main()  
{  
    int i,j;  
  
    printf("请输入作业数量\n");  
    scanf("%d",&n);   
    printf("请输入在各机器上的处理时间\n");  
    for(i=1; i<=2; i++)  
        for(j=1; j<=n; j++)  
			scanf("%d",&m[j][i]);  
    for(i=1; i<=n; i++)  
        x[i]=i;  //记录当前调度
    Backtrack(1); 
	 
    printf("调度作业顺序\n");  
    for(i=1; i<=n; i++) 
		 printf("%d\t",bestx[i]);  
	printf("\n"); 
    printf("处理时间:\n");  
    printf("%d\n",bestf);  
    return 0;  
}  

3、图的m着色问题

给定无向连通图G和m种不同的颜色。用这些颜色为图G的各顶点着色,每个顶点着一种颜色。是否有一种着色法使G中每条边的2个顶点着不同颜色。这个问题是图的m可着色判定问题。
若一个图最少需要m种颜色才能使图中每条边连接的2个顶点着不同颜色,则称这个数m为该图的色数。求一个图的色数m的问题称为图的m可着色优化问题。

 int  m;            //给定的颜色数
 int a[n][n];     //n个顶点图的邻接矩阵
 int x[n];         //存放着色方案
 int flag=0;     //是否M可着色
 bool Ok(int k)
{  // 着色方案是否可行
   for (int j=1;j<=n;j++)
   if ((a[k][j]==1)&&(x[k]==x[j])) return false;
   return true;
}
void  backtrack(t)    //判断图是否M可着色
{   if (t>n)   //有着色方案
    	{   flag=1;    return;   }    
    for (i=1; i<=m; i++)   //对M种颜色进行遍历
        {   x[t]=i;
             if ( ok(t))  backtrack(t+1) ;
        }
         return;
}

4、N皇后问题

在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。求N皇后问题的一种放法,或求N皇后问题的所有放法。

x[i]:i表示第i行皇后
x[i]表示第i行上皇后放第几列
不同行:数组x的下标保证不重复
不同列:x[i]!=x[j] (i<=I,j<=n;i!=j)
不同对角线:abs(x[i]-x[j])!=abs(i-j)

bool Place(int k)
{
  for (int j=1;j<k-1;j++)  //填到第K行时,就与前1~(K-1)行都进行比较
  if ((abs(k-j)==abs(x[j]-x[k]))||(x[j]==x[k])) return false;
  return true;
} 

void Backtrack(int t)
{
  if (t>n) sum++;
    else
      for (int i=1;i<=n;i++) {  //每层均有n种放法
        x[t]=i;  //放置本层皇后
        if (Place(t)) Backtrack(t+1);
      }
 }

5、最大子团问题

给定无向图G=(V,E)。如果UV,且对任意u,vU有(u,v)E,则称U是G的完全子图。G的完全子图U是G的团当且仅当U不包含在G的更大的完全子图中。G的最大团是指G中所含顶点数最多的团。
首先设最大团为一个空团,往其中加入一个顶点,然后依次考虑每个顶点,查看该顶点加入团之后仍然构成一个团。如果可以,考虑将该顶点加入团或者舍弃两种情况;如果不行, 直接舍弃,然后递归判断下一顶点。对于无连接或者直接舍弃两种情况,在递归前,可采用剪枝策略来避免无效搜索。

void Backtrack(int i) { // 计算最大团
  if (i > n) {   // 到达叶结点
      for (int j = 1; j <= n; j++) bestx[j] = x[j];
      bestn = cn;   return;}  //当前团的顶点数cn
   int OK = 1;
   for (int j = 1; j < i; j++)  // 检查顶点 i 与当前团的连接
   if (x[j] && a[i][j] == 0) { // i与j不相连
      OK = 0;  break; }
   if (OK) { // 进入左子树
      x[i] = 1;  cn++;
      Backtrack(i+1);
      x[i] = 0; cn--;}
   if (cn + n - i > bestn) { // 进入右子树
      x[i] = 0;
      Backtrack(i+1);}
}

四、分支限界法

1、单源最短路径

在有向图G中,每一边都有一个非负边权。要求图G的从源顶点s到目标顶点t之间的最短路径。

#include <iostream>
#include<queue>
using namespace std;
 
typedef struct ArcCell{
    int adj;//保存权值
    int info;//存储最短路径长度
}ArcCell,AdjMaxtrix[100][100];
 
typedef struct{
    int data;
    int length;
}VerType;
 
typedef struct{
    VerType vexs[100];//顶点向量
    AdjMaxtrix arcs;
    int vexnum;//顶点数
    int arcnum;//弧数
}Graph;
 
Graph G;
queue<int> q;
 
void CreateGraph()
{
    int m,n,t;
    printf("输入顶点数和弧数:");
    scanf("%d%d",&G.vexnum,&G.arcnum);
    printf("输入顶点:");
    for(int i=1;i<=G.vexnum;i++)
    {
        scanf("%d",&G.vexs[i].data);
        G.vexs[i].length=10000;
    }
 
    for(int i=1;i<=G.vexnum;i++)
        for(int j=1;j<=G.vexnum;j++)
        {
            G.arcs[i][j].adj=0;
        }
 
    printf("输入弧及权重:\n");
    for(int i=1;i<=G.arcnum;i++)
        {
            scanf("%d%d%d",&m,&n,&t);
            G.arcs[m][n].adj=1;
            G.arcs[m][n].info=t;
        }
 
}
 
int NextAdj(int v,int w)
{
    for(int i=w+1;i<=G.vexnum;i++)
        if(G.arcs[v][i].adj)
            return i;
    return 0;//not found;
}
 
void ShortestPaths(int v)
{
    int k=0;//从首个节点开始访问
    int t;
    G.vexs[v].length=0;
    q.push(G.vexs[v].data);
    while(!q.empty())
    {
        t=q.front();
        k=NextAdj(t,k);
        while(k!=0)
        {
            if(G.vexs[t].length+G.arcs[t][k].info<=G.vexs[k].length)//减枝操作
            {
                G.vexs[k].length=G.vexs[t].length+G.arcs[t][k].info;
                q.push(G.vexs[k].data);
            }
            k=NextAdj(t,k);
        }
        q.pop();
    }
}
 
int main()
{
    CreateGraph();
    ShortestPaths(1);
    for(int i=1;i<=G.vexnum;i++)
        printf("%d\n%d\n",G.vexs[i].data,G.vexs[i].length);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值