算法设计与分析实验报告c++&java实现(ACM面试题、字符串匹配算法、循环赛日程安排问题、分治法求解最大连续子序列和、动态规划法求解最大连续子序列和)

一、 实验目的

1.加深学生对算法设计方法的基本思想、基本步骤、基本方法的理解与掌握;
2.提高学生利用课堂所学知识解决实际问题的能力;
3.提高学生综合应用所学知识解决实际问题的能力。

二、实验任务

1、【ACM、面试题】求解按“最多排序”到“最小排序”的顺序排列问题。一个序列中的“未排序”的度量是相对于彼此顺序不一致的条目对的数量,例如,在字母序列“DAABEC”中,该度量为5,因为D大于右边是4个字母,E大于其右边的1个字母。该度量称为该序列的逆序数。序列“AACEDGG”只有一个逆序对(E和D),它几乎被排序好了,而序列“ZWQM”有6个逆序对,它是未排序的,恰好是反序。
你需要对若干个DNA序列(仅包含4个字母A、C、G和T的字符串)分类,注意是分类而不是按字母顺序排序,而是按照“最多排序”到“最小排序”的顺序排列,所有DNA序列的长度都相同。
输入:第一行包含两个整数:n(0<n≤50)表示字符串长度,m(0<m≤100)表示字符串个数。后面是m行,每行包含一个长度为n的字符串。
输出:按“最多排序”到“最小排序”的顺序输出所有字符串。若两个字符串的逆序对个数相同,按原始顺序输出它们。
2、字符串匹配算法(BF,KMP)
给定一个文本,在该文本中查找并定位任意给定字符串。
3、循环赛日程安排问题
设有n=2k个选手要进行网球循环赛,要求设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能赛一次。
4、采用分治法求解最大连续子序列和问题
给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。
例如:
序列(-2,11,-4,13,-5,-2)的最大子序列和为20
序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16。
规定一个序列最大连续子序列和至少是0(长度为0的子序列),如果小于0,其结果为0。
5、 采用动态规划法求解最大连续子序列和问题

三、实验设备及编程开发工具

实验设备:Win10 电脑
开发工具:Visual Studio 2019
编程语言:C/JAVA

四、实验过程设计(算法设计过程)

(一)、DNA序列分类

1、算法分析:
输入字符的时候可以利用string对象来处理。在计算逆序对时要按照逆序对从小到大的顺序输出,这里有个小技巧,多定义一个数组来存放原始顺序的逆序对数,然后对求取到的逆序对进行从小到大的排序,最后将备用存放逆序对的数组数据跟排好序的进行比较,然后对应原始下标输出对应的字符串即可。

2、代码实现:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
int n, m;
string a[55];
int t[110]={0},r[110];
 
int s(string  a)
{
	int sum = 0;
	for(int i = 0; i < a.length()-1; i++)
	  for(int j = i+1; j < a.length(); ++j)
	    if(a[i] > a[j])
	     sum++;
	return sum;
}
 
int main()
{
	
	int x = 0;
	
	cout << "请输入字符串长度和字符串个数:\n"; 
	scanf("%d%d",&n,&m);
	
	cout << "请输入所有的字符串:\n";
	for (int i = 0; i < m; ++i)
	{
		cin >> a[i];
		  
	}
	
	for(int i = 0; i < m; i++)
	{ 
	   t[i] = s(a[i]);
	   r[i] = t[i];
	}
	sort(t,t+m);
	
	cout << "=================\n";
	cout << "排序后的字符串顺序为:\n";
	for(int i = 0; i < m; i++)
	  for(int j = 0; j < m; j ++)
	  {
	  	if(t[i] == r[j])
	  	   cout << a[j] << endl;
	  }
	return 0;
	
}


DNA序列分类
1、实验结果

img

2、算法复杂度分析
时间复杂度:O(n^2)

二)、字符串匹配

1、算法分析:
首先,原字符串和子串左端对齐,比较第一个字符,发现不相等,子串向后移动,直到子串的第一个字符能和原字符串匹配。 当A匹配上之后,接着匹配后续的字符,直至原字符串和子串出现不相等的字符为止。参见kmp算法。

2、代码实现:

#include "vector"
#include "string"
#include <iostream>
#include "algorithm"
 
using namespace std;
 
//计算模式P的部分匹配值,保存在next数组中  
void MakeNext(const string &P, vector<int> &next)
{
	int q,k;//k记录所有前缀的对称值  
	int m = P.size();//模式字符串的长度  
	next[0] = 0;//首字符的对称值肯定为0  
	for (q = 1, k = 0; q < m; ++q)//计算每一个位置的对称值  
	{
		//k总是用来记录上一个前缀的最大对称值  
		while (k > 0 && P[q] != P[k])
			k = next[k - 1];//k将循环递减,值得注意的是next[k]<k总是成立  
		if (P[q] == P[k])
			k++;//增加k的唯一方法  
		next[q] = k;//获取最终值  
	}
}
 
 
void KmpMatch(const string &T, const string &P, vector<int> &next)
{
	int n, m;
	n = T.size();
	m = P.size();
	MakeNext(P, next);
	for (int i = 0, q = 0; i < n; ++i)
	{
		while (q > 0 && P[q] != T[i])
			q = next[q - 1];
		if (P[q] == T[i])
			q++;
		if (q == m)
		{
			cout << "模式文本的偏移为:" << (i - m + 1) << endl;
			q = next[q - 1];//寻找下一个匹配
		}
	}
}
 
int main()
{
	system("color 0A");
	vector<int> next(20,0);//保存待搜索字符串的部分匹配表(所有前缀函数的对称值)
	string T = "abcdefgikjhgdjahfsdhgfusgr";//文本
	string P = "sdhg";//待搜索字符串
	cout <<"文本字符串:"<< T << endl;
	cout <<"模式字符串:"<< P << endl;
	KmpMatch(T, P, next);
	return 0;
}


字符串匹配
1、实验结果

img

2、算法复杂度分析
时间复杂度:O(m+n)

(三)、循环赛日程安排

1、算法分析:

按照要求,可以将比赛表设计成一个n行n-1列的二维表,其中第i行第j列的元素表示和第i个选手在第j天比赛的选手号。采用分治策略,可将所有参加比赛的选手分成两部分, n = 2 k n=2^k n=2k个选手的比赛日程表就可以通过 n = 2 ( k − 1 ) n=2^{(k-1)} n=2k1个选手的的比赛日程表来决定。递归的执行这样的分割,直到只剩下两个选手,比赛日程表的就可以通过这样的分治策略逐步构建。

假设有 2 1 2^1 21个选手,其安排如下:

12
21

假设有 2 2 2^2 22个选手,其安排如下:

1234
2143
3412
4321

假设有 2 3 2^3 23个选手,其安排如下:

12345678
21446587
34127856
43218765
56781234
65872143
78563412
87654321

根据以上的规律,求解过程可以看作四个部分:

  1. 求左上角子表:左上角子表是前 2 ( k − 1 ) 2^{(k-1)} 2(k1)个选手的比赛前半程的比赛日程。

  2. 求左下角子表:左下角子表是剩余的 2 ( k − 1 ) 2^{(k-1)} 2(k1)个选手的比赛前半程比赛日程。这个子表和左上角子表的对应关系式,对应元素等于左上角子表对应元素加 2 ( k − 1 ) 2^{(k-1)} 2(k1)

  3. 求右上角子表:等于左下角子表的对应元素。

  4. 求右下角子表:等于左上角子表的对应元素。

2、代码实现:

#include<stdio.h>
#include<math.h>
#define N 50
void GameTable(int k,int array[][N]);
void print(int k,int array[][N]);         //输出二维数组 
main()
{
    int k;
    int array[N][N];
    printf("参赛选手的人数为n(n=2^k),请输入k 的值:");
    do
    {
         scanf("%d",&k);
        if(k>0)
        {
            GameTable(k,array);
            print(k,array);
        }
        else
          printf("您输入的数据有误,请重新输入"); 
    }while(k!=0);//排除输入错误k值

}
void GameTable(int k,int array[][N])//数组下标从1开始
{
    int i,j,s,t;
    int n=1;
    for(i=1;i<=k;i++)
        n*=2;                       //求总人数
    for(i=1;i<=n;i++)
        array[1][i]=i;                  //第一行排1-8
    int m=1;                          //用来控制每一次填表时i行j列的起始填充位置
    for(s=1;s<=k;s++)                 //s指对称赋值的总循环次数,即分成几大步进行制作日程表
    {
        n=n/2;
        for(t=1;t<=n;t++)              //t指明内部对称赋值的循环次数
            for(i=m+1;i<=2*m;i++)
                for(j=m+1;j<=2*m;j++)
                {
                    array[i][j+(t-1)*m*2]=array[i-m][j+(t-1)*m*2-m];       //右上角等于左上角的值
                    array[i][j+(t-1)*m*2-m]=array[i-m][j+(t-1)*m*2];       //左下角等于右上角的值
                }
        m*=2;
    }
    
}
void print(int k,int array[][N])
{
    int i,j;
    int num=pow(2,k);
    printf("%d人的循环赛日程表如下:\n",num);
    for(i=1;i<=num;i++)                           //输出二维数组 
    {
        for(j=1;j<=num;j++)
        {
            printf("%d\t",array[i][j]);
        }
         printf("\n");
    }
}


循坏日程赛安排
1、实验结果

img

2、算法复杂度分析
时间复杂度:O(2^k * 2^k)。

(四)、(分治)最大连续子序列和

1、算法分析:
1.将一个长度为n的序列,一分为二变为两个长度为n/2的子序列,继续将子序列们一分为二,直到每个子序列只含有1个整数。
2.此时问题已经足够小,“最大子序列和”有以下三种情况:左边序列的最大子序列和、右边序列的最大子序列和和处在中间位置上的最大子序列和,我们通过比较,得到三者中的最大值。
3.再将这些“小问题”合并,使用同样的比较方法逐步向上合并这些“左右序列”,直到得到整个序列的最大子序列和,解决问题。

2、代码实现:

#include<stdio.h>
#define MAXSIZE 100
 
int max3(int a,int b,int c)
{
	if(a>b) return a>c?a:c;
	else return b>c?b:c;
}
 
int MaxSubseqSum(int a[],int left,int right)
{
	int maxLeftSum,maxRightSum,maxMidSum;
	int maxLeftBorderSum,LeftBorderSum;
	int maxRightBorderSum,RightBorderSum;
	int mid;
	int i;
	if(left==right)	//递归出口,子序列只有一个元素时
		return a[left];
	mid=(left+right)/2;	//求中间位置
	maxLeftSum=MaxSubseqSum(a,left,mid);	//求左边序列的最大子序列和
	maxRightSum=MaxSubseqSum(a,mid+1,right);	//求右边序列的最大子序列和
	maxLeftBorderSum=0;
	LeftBorderSum=0;
	for(i=mid;i>=left;i--)	//从中间位置向左找靠边界的最大子序列
	{
		LeftBorderSum+=a[i];
		if(LeftBorderSum>maxLeftBorderSum)
			maxLeftBorderSum=LeftBorderSum;
	}
	maxRightBorderSum=0;
	RightBorderSum=0;
	for(i=mid+1;i<=right;i++)	//从中间位置向右找靠边界的最大子序列
	{
		RightBorderSum+=a[i];
		if(RightBorderSum>maxRightBorderSum)
			maxRightBorderSum=RightBorderSum;
	}
	maxMidSum=maxLeftBorderSum+maxRightBorderSum;	//得到处在中间位置上的最大子序列和
	return max3(maxLeftSum,maxRightSum,maxMidSum);
}
 
int MaxNum(int a[],int left,int right)
{
	int maxLeft,maxRight;
	int mid;
	if(left==right)
		return a[left];
	mid=(left+right)/2;
	maxLeft=MaxNum(a,left,mid);
	maxRight=MaxNum(a,mid+1,right);
	return maxLeft>maxRight?maxLeft:maxRight;
}
 
int main()
{
	int a[MAXSIZE];
	int count=0;
	int i,n;
	printf("序列长度:");
	scanf("%d",&n);
	printf("输入整数序列:");
	for(i=0;i<n;i++)
	{
		scanf("%d",&a[i]);
		if(a[i]<=0)
			count++;
	}
	if(count==n)	//判断是否含有正整数
		printf("最大子序列和:%d\n",MaxNum(a,0,n-1));
	else
		printf("最大子序列和:%d\n",MaxSubseqSum(a,0,n-1));
 
}


(分治)最大连续子序列和
1、实验结果

img

2、算法复杂度分析
时间复杂度:O(nlog(n))

(五)、(动态规划)最大连续子序列和

1、算法分析:

  1. 动态规划算法:求最大连续子序列
    1) k[j] 表示最大连续子序列最后一个元素,dk[j]表示最大连续子序列和
    2)最大连续子序列和为从 dk[i] ~ dk[j]
  2. 分两种情况:
    1)最大和为k[j]。它之前的序列和都小于k[j]
    2)最大和为dk[j-1]+k[j]。
    3.得到状态转移方程:
    dk[j] = max{dk[j-1]+k[j], k[j]} 边界为 dk[0] = j[0]
  3. 动态规划算法计算特点(步骤):
    从后往前算,每步计算结果都记录进表。
    计算结束后,再遍历记录表。

2、代码实现:

import java.util.Scanner;

public class MaxSubSequence {
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入子序列长度:");
        int N = sc.nextInt();
        System.out.println("请输入子序列:");
        int[] A = new int[N];
        for (int i = 0; i < A.length; i++) {
            A[i] = sc.nextInt();
        }

        int ThisSum, MaxSum, j;
        ThisSum = MaxSum = 0;
        for (j = 0; j < N; j++) {
            ThisSum += A[j];

            if (ThisSum > MaxSum)
                MaxSum = ThisSum;
            else if (ThisSum < 0)
                ThisSum = 0;
        }

        System.out.println("最大子序列和位:" + MaxSum);
    }
}

(动态规划)最大连续子序列和
1、实验结果

img

2、算法复杂度分析
时间复杂度:0(n)

五、实验小结(包括问题和解决方法、心得体会等)

分治法,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。本次实验增加了动手编码能力,对算法设计有了更进一步的认识,但是技术上的缺陷,编码能力上存在的短板,在今后的实验中还需要加大练习力度。

0 1背包问题是一例典型的组合优化的NP完全问题 问题可以描述为:给定一组共n个物品 每种物品都有自己的重量wi i 1 n和价值vi i 1 n 在限定的总重量(背包的容量C)内 如何选择才能使得选择物品的总价值之和最高 选择最优的物品集放置于给定背包中 最优集对应n元解向量 x1 …xn xi∈{0或1} 因此命名为0 1背包问题 0 1背包问题是许多问题的原型 但它又是一个NP完全问题实验主要研究和实现n 0< n< 200 和C C< 2000 C为整数 都较大的情形 随机产生n个物品的重量向量wi 1< wi< 100 wi为整数 和价值向量vi 1< vi< 100 vi为整数 0 1背包问题可以用许多方求解 有些算法可以得到问题的精确最优解 有些仅能获得一个近似最优解 本综合设计性实验要求用3种以上的方求解0 1背包问题 获得精确最优解或近似最优解皆可 并对所采用的多种算法从运行时间 寻找是否为最优解 能够求解问题规模等方面进行对比和分析 本课程讲述的所有算法思想都可以用来求解问题 甚至本课程未涉及的许多算法也非常适合于求解问题 学生可以先尝试先用本课程已介绍的算法实现和分析 学有余力或兴趣驱动下可以寻找一些智能算法的资料来试一试 涉及的方可以有:蛮力求解 递归求解 动态规划求解 贪心求解 回溯求解 广度优先的分支限界求解 优先队列的启发式分支限界 遗传算法 模拟退火算法 蚁群算法算法等 ">0 1背包问题是一例典型的组合优化的NP完全问题 问题可以描述为:给定一组共n个物品 每种物品都有自己的重量wi i 1 n和价值vi i 1 n 在限定的总重量(背包的容量C)内 如何选择才能使得选择物品的总价值之和最高 选择 [更多]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

温柔说给风

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值