算法分析与设计课程实验报告

写在开头:

算法真的学的头大,一眼望不到算法的尽头。yls的算法作业也真是暴多,这一学期光算法就写了50+篇报告,期中的时候看别人统计出来的时候真的破防了。但是这么多作业也不算白写,给我打的平时分我还是很满意滴。

声明:本人算法入门水平,佬们没必要浪费时间看这坨代码。

如果有任何违规的地方,请第一时间联系我,我会立即删除本文。

实验一:递归与分治

实验1.3 快速排序及第k小数

1.算法描述:

对于一个已经给出的数组,通过输入一个合法区间[l,r],以及一个数k,先找出[l,r]中第k小的数并输出,再使用快速排序对[l,r]这个区间进行排序,最后输出已经排序好的数值序列。

2.算法思路:

快速排序实际上就是通过一次次地选出一个个参考值,并根据这个参考值,将比他小的排在他左边,比他大的排在他右边,这样进行调整。我们可以先实现调整函数int arrange(int l,int r)以a[l]作为临时参照物,把在[l,r]中比a[l]小的放到它左边,比它大的放在他右边,并最终确定a[l]所在的位置。那么快速排序void quick_sort(int l,int r)就可以通过每一次得知a[l]的位置,将原序列分为三段,除了a[l]这个数不用处理以外,剩下的两段,一个都比他小,另一个都比他大,都要继续调整,即递归调用自己,直到全部因为l>=r而结束后,原序列变为有序序列。而通过arrange 返回的参数到左边界l的距离实际上就是参考数在序列中的序号,通过将这个序号与k进行对比,我们可以得出接下来我们要查找哪边。

3.代码实现: 

#include <iostream>
using namespace std;
int a[105]={0,9,2,7,4,5,6,3,8,1};
void swap(int i,int j)
{
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}
int arrange(int l,int r)
{
    int temp = a[l];
    while(l<r)
    {
        while(a[r]>=temp&&l<r)
            r--;
        if(l<r)
            swap(l,r);
        while(a[l]<=temp&&l<r)
            l++;
        if(l<r)
            swap(l,r);
    }
    return l;//返回参考点的下标
}
void quick_sort(int l,int r)
{
    if(l >= r)
        return;
    else
    {
        int mid = arrange(l, r);
        quick_sort(l,mid-1);
        quick_sort(mid+1,r);
    }
}
int quick_sort_mink(int l,int r,int k)
{
    int mid = arrange(l, r);
    if((mid-l+1) == k)
    {
        return a[mid];
        //若为第k小的,则返回
    }
    else if ((mid-l+1) > k)
    {
        return quick_sort_mink(l, mid-1, k);
        //若超过第k个,则查找l到mid-1,裁剪[mid,r],因为裁剪的是大的值,所以不改变k
    }
    else
    {
        return quick_sort_mink(mid+1, r, k-(mid-l+1));
        //若小于k,则将[l,mid]共(mid-l+1)个数全部裁剪,因而从查找原序列的第k个,变为查找新序列的第k-(mid-l+1)个。
    }
}
int main()
{
    int k,l,r;
    cout<<"请输入左边界l的值:";
    cin>>l;
    cout<<"请输入左边界r的值:";
    cin>>r;
    if(l>r)
    {
        cout<<"l不能比r大!!!"<<endl;
        return -1;
    }
    cout<<"请输入k的值:";
    cin>>k;
    if(r-l+1<k)
    {
        cout<<"总共就"<<r-l+1<<"个数,你让我找第"<<k<<"小的数?"<<endl;
        return -1;
    }
    cout<<"第"<<k<<"小的数是:"<<quick_sort_mink(l, r, k)<<endl;
    quick_sort(l, r);
    cout<<"排序后的结果为:";
    for(int i=l;i<=r;i++)
        cout<<a[i]<<" ";
    cout<<endl;
    return 0;
}

4.算法优化: 

 快速排序的最坏时间复杂度为O(n^{2}),比如对于{1,2,3,4}这样的序列进行快速排序,就会出现这样的最坏时间复杂度。对于最坏的情况,T(n) = T(n-1)+T(1)+n,最坏时间复杂度就是由这样的不对等分配导致的。而对于最好的情况,T(n) = 2T(n/2)+n,这样最好时间复杂度就是O(n\log n).所以如何选取一个合理的参照数字,选到的数字越靠近中间数越好,就是优化快排的关键。
1.从数组的头部,尾部和中间,这三个数里面选择既不是最大的也不是最小的数作为参照数。

2.使用随机数产生参照数的下标,随机数还是很能打的。

实验 1.5 棋盘覆盖问题

1.题干:

在一个2^{k}×2^{k}个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的

4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。

2.算法描述:

对于一组给出的k,x,y。输出一个除了(x,y)以外,其他方块都不重叠地被骨牌给覆盖了的边长为2^{k}的正方形棋盘情况图。

3.算法思路:

对于正方形棋盘,我们将其均分成四块,其中1块有特殊方块,另外3块没有特殊方块,为了能使递归进行下去,我们必须在另外3块中创造出特殊方块(或者说已经被占领的方块)。

创造情况如下图:

1.特殊点在左上角:

红色为新的特殊点;

2.特殊点在右上角: 

蓝色为新的特殊点; 

3.特殊点在左下角:

 橙黄色的为新的特殊点。

4.特殊点在右下角,则左上角区块的最右下角的方块,右上角区块的最左下角的方块,左下角区块的最右上角的方块,作为新的特殊点。

最后,若区块的尺寸缩小到单位长度1时,结束递归。

4.代码实现:

#include <iostream>
using namespace std;
int map[105][105]={0};
void draw(int x,int y,int lx,int ly,int l)//(x,y)为特殊点的坐标,(lx,ly)为正方形棋盘的左上角的坐标,l为棋盘的边长
{
    if(l == 1)
        return ;
    int halfx = lx+l/2-1,halfy = ly+l/2-1;
    if(x<=halfx)
    {
        if(y<=halfy)//若特殊点在左上角,则在最中间的2x2个方格中放入一个d类骨牌,并将放入的骨牌当作新的特殊点
        {
            map[halfx][halfy+1] = 4;//4代表此方块被d类骨牌占领
            map[halfx+1][halfy] = 4;
            map[halfx+1][halfy+1] = 4;
            draw(x, y, lx, ly, l/2);
            draw(halfx,halfy+1,lx,halfy+1,l/2);
            draw(halfx+1,halfy,halfx+1,ly,l/2);
            draw(halfx+1,halfy+1,halfx+1,halfy+1,l/2);
        }
        else//若特殊点在右上角,则在最中间的2x2个方格中放入一个c类骨牌,并将放入的骨牌当作新的特殊点
        {
            map[halfx][halfy] = 3;//3代表此方块被c类骨牌占领
            map[halfx+1][halfy] = 3;
            map[halfx+1][halfy+1] = 3;
            draw(halfx, halfy, lx, ly, l/2);
            draw(x,y,lx,halfy+1,l/2);
            draw(halfx+1,halfy,halfx+1,ly,l/2);
            draw(halfx+1,halfy+1,halfx+1,halfy+1,l/2);
        }
    }
    else
    {
        if(y<=halfy)//若特殊点在左下角,则在最中间的2x2个方格中放入一个b类骨牌,并将放入的骨牌当作新的特殊点
        {
            map[halfx][halfy] = 2;//2代表此方块被b类骨牌占领
            map[halfx][halfy+1] = 2;
            map[halfx+1][halfy+1] = 2;
            draw(halfx, halfy, lx, ly, l/2);
            draw(halfx,halfy+1,lx,halfy+1,l/2);
            draw(x,y,halfx+1,ly,l/2);
            draw(halfx+1,halfy+1,halfx+1,halfy+1,l/2);
        }
        else//若特殊点在右下角,则在最中间的2x2个方格中放入一个a类骨牌,并将放入的骨牌当作新的特殊点
        {
            map[halfx][halfy] = 1;//1代表此方块被a类骨牌占领
            map[halfx][halfy+1] = 1;
            map[halfx+1][halfy] = 1;
            draw(halfx, halfy, lx, ly, l/2);
            draw(halfx,halfy+1,lx,halfy+1,l/2);
            draw(halfx+1,halfy,halfx+1,ly,l/2);
            draw(x,y,halfx+1,halfy+1,l/2);
        }
    }
}
int quickPower(int a, int b)//是求a的b次方
{
    int ans = 1, base = a;//ans为答案,base为a^(2^n)
    while(b > 0)//b是一个变化的二进制数,如果还没有用完
    {
        if(b & 1)//&是位运算,b&1表示b在二进制下最后一位是不是1,如果是:
            ans *= base;//把ans乘上对应的a^(2^n)
        
        base *= base;//base自乘,由a^(2^n)变成a^(2^(n+1))
        b >>= 1;//位运算,b右移一位,如101变成10(把最右边的1移掉了),10010变成1001。现在b在二进制下最后一位是刚刚的倒数第二位。结合上面b & 1食用更佳
    }
    return ans;
}
int main()
{
    int k,x,y,l;
    cout<<"请输入k的值(1~6):";
    cin>>k;
    if(k<1||k>6)
    {
        cout<<"k的取值非法,请遵守提示!!!"<<endl;
        return -1;
    }
    cout<<"请输入特殊方格的x坐标(1<=x<=2^k):";
    cin>>x;
    cout<<"请输入特殊方格的y坐标(1<=y<=2^k):";
    cin>>y;
    if(x<1||y<1)
    {
        cout<<"横、纵坐标必须大于0哦。"<<endl;
        return -1;
    }
    l = quickPower(2, k);
    if(x>l||y>l)
    {
        cout<<"边长才为"<<l<<",你叫我上哪里去找("<<x<<","<<y<<")?"<<endl;
        return -1;
    }
    draw(x, y, 1, 1, l);
    for(int i=1;i<=l;i++)
    {
        for(int j=1;j<=l;j++)
        {
            cout<<map[i][j]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

5.算法优化:

我没想到有哪里可以优化的。。。

实验四:动态规划

实验4.2 计算矩阵连乘积

1.题干:

在科学计算中经常要计算矩阵的乘积。矩阵A和B可乘的条件是矩阵A的列数等于矩阵B的行数。若A是一个p×q的矩阵,B是一个q×r的矩阵,则其乘积C=AB是一个p×r的矩阵。由该公式知计算C=AB总共需要pqr次的数乘。其标准计算公式为:

现在的问题是,给定n个矩阵{A1,A2,…,An}。其中Ai与Ai+1是可乘的,i=1,2,…,n-1。要求计算出这n个矩阵的连乘积A1A2…An的最少数乘次数。

2.算法描述:

对于输入的矩阵个数n,以及各个矩阵的行数,列数。得到一个最小的乘积次数以及对应的方案。

3.算法思路:

采用动态规划,写出状态转移方程:

m[i][j]表示从第i个矩阵乘到第j个矩阵所需的最少乘法次数。

随后进行动态规划,注意因为m[i][j]的计算需要m[i][k]和m[k+1][j]所以必须先将i,j相差小的给计算出来,才能继续算i,j相差大的。

4.代码实现:

#include <iostream>
using namespace std;
int s[105][105];
void showhow(int l,int r)//展示分割矩阵
{
    if(l>=r)
        return;
    else
    {
        cout<<l<<"号矩阵到"<<r<<"号矩阵的分割矩阵为:"<<s[l][r]<<endl;
        showhow(l,s[l][r]);
        showhow(s[l][r]+1,r);
    }
}
int main()
{
    int n,p[105],i,j,r,k,cnt[105][105],tempcnt;
    cout<<"请输入矩阵的个数(1<n<101):";
    cin>>n;
    if(n<2||n>100)
    {
        cout<<"矩阵个数非法!!!"<<endl;
        return -1;
    }
    cout<<"请输入第1个矩阵的行数:";
    cin>>p[0];
    if(p[0]<1)
    {
        cout<<"行数非法!!!"<<endl;
        return -1;
    }
    for(i = 1;i<n;i++)
    {
        cout<<"请输入第"<<i<<"个矩阵的列数(同时也是第"<<i+1<<"个矩阵的行数):";
        cin>>p[i];
        if(p[i]<1)
        {
            cout<<"列数非法!!!"<<endl;
            return -1;
        }
    }
    cout<<"请输入第"<<i<<"个矩阵的列数:";
    cin>>p[n];
    if(p[n]<1)
    {
        cout<<"列数非法!!!"<<endl;
        return -1;
    }
    for(i = 1;i<=n;i++)
    {
        cnt[i][i]=0;
        s[i][i]=0;
    }
    i = 1;
    for(r=1;i+r<=n;r++)//i与j相差多少,并保证j不超过n
    {
        for(;i<n;i++)
        {
            j = i+r;
            cnt[i][j] = cnt[i+1][j]+p[i-1]*p[i]*p[j];//初始化分法
            s[i][j] = i;
            for(k=i+1;k<j;k++)
            {
                tempcnt = cnt[i][k]+cnt[k+1][j]+p[i-1]*p[k]*p[j];//查找是否有更好的分法
                if(tempcnt<cnt[i][j])
                {
                    cnt[i][j] = tempcnt;
                    s[i][j] = k;
                }
            }
        }
        i=1;
    }
    cout<<"最少乘积次数为:"<<cnt[1][n]<<endl;
    showhow(1, n);
    return 0;
}

5.算法优化:

没什么想法。。。

实验4.4 防卫导弹

1.题干:

一种新型的防卫导弹可截击多个攻击导弹。它可以向前飞行,也可以用很快的速度向下飞行,可以毫无损伤地截击进攻导弹,但不可以向后或向上飞行。但有一个缺点,尽管它发射时可以达到任意高度,但它只能截击比它上次截击导弹时所处高度低或者高度相同的导弹。现对这种新型防卫导弹进行测试,在每一次测试中,发射一系列的测试导弹(这些导弹发射的间隔时间固定,飞行速度相同),该防卫导弹所能获得的信息包括各进攻导弹的高度,以及它们发射次序。现要求编一程序,求在每次测试中,该防卫导弹最多能截击的进攻导弹数量,一个导弹能被截击应满足下列两个条件之一:

a)它是该次测试中第一个被防卫导弹截击的导弹;

b)它是在上一次被截击导弹的发射后发射,且高度不大于上一次被截击导弹的高度的导弹。

2.算法描述:

对于输入的导弹个数n,以及一系列导弹的高度h[n],输出能拦截的最大导弹个数。

3.算法思路:

本题为动态规划题,初始化dp[i-1] = 1。写出动态方程:若有比i-1号导弹更低的导弹,则dp[i-1] = max(dp[i-1],dp[j]+1)

dp[i]表示在拦截i号导弹的前提下,所能拦截的最大导弹个数,最终在所有情况中找出拦截导弹个数最多的情况。

4.代码实现:

#include <iostream>
using namespace std;
int maxcnt(int a[],int l,int r)
{
    if(l==r)
    {
        return a[l];
    }
    else
    {
        int mid = (l+r)/2;
        return max(maxcnt(a,l,mid),maxcnt(a,mid+1,r));
    }
}
int max(int a,int b)
{
    return a>=b?a:b;
}
int main()
{
    int n,h[105],dp[105],i,j;
    cout<<"请输入导弹的个数(小于101个):";
    cin>>n;
    if(n<1||n>100)
    {
        cout<<"非法导弹个数!!!"<<endl;
        return -1;
    }
    for(i=1;i<=n;i++)
    {
        cout<<"请输入第"<<i<<"个导弹的高度:";
        cin>>h[i];
        dp[i] = 1;
        if(h[i]<1)
        {
            cout<<"非法导弹高度!!!"<<endl;
            return -1;
        }
    }
    for(i= n;i>1;i--)
    {
        for(j=i;j<=n;j++)
        {
            if(h[i-1]>=h[j])//若第i-1个导弹的高度大于等于第j个的导弹的高度,则取两者能拦截的大者
            {
                dp[i-1] = max(dp[i-1],dp[j]+1);
            }
        }
    }
    cout<<"最多能拦截"<<maxcnt(dp, 1, n)<<"个导弹"<<endl;
    return 0;
}

5.算法优化:

没啥想法。。。

实验4.9 皇宫看守

1.题干:

太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;某些宫殿间可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。

请你编程计算帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

2.算法描述:

对于输入的节点个数n,以及各个结点的相关信息:此节点要花费的经费,子节点的个数,以及各个子节点的序号(保存在trees数组中),输出一个最小费用。

3.算法思路:

本实验依旧采用动态规划的方法。用dp[i]表示看守以i为根的皇宫所需要的最小经费。

对于这种只有根结点的树,他的dp = cost。

对于其他的树,则需要比较:1.根节点的花费+子代的子代的dp花费;2.子代的dp花费。最后取二者小的那一方dp[i] = min(trees[i].cost+sum,childcost(trees, i))

4.代码实现:

#include <iostream>
using namespace std;
class tree
{
public:
    int cost;
    int childnum;
    int childid[105];
    tree(int c,int cn)
    {
        this->cost = c;
        this->childnum = cn;
        for(int i=1;i<=cn;i++)
        {
            cin>>childid[i];
        }
    }
    tree(){}
};
int childcost(tree a[],int id)//求出编号为id的所有孩子的最小经费之和
{
    int i,sum=0;
    for(i=1;i<=a[id].childnum;i++)
    {
        sum+=a[a[id].childid[i]].cost;
    }
    return sum;
}
int max(int a,int b)
{
    return a>b?a:b;
}
int maxone(int a[],int l,int r)//二分法求数组最大值
{
    if(l == r)
        return a[l];
    else
    {
        int mid = (l+r)/2;
        return max(maxone(a,l,mid),maxone(a, mid+1, r));
    }
}
int min(int a,int b)
{
    return a<=b?a:b;
}
int main()
{
    int n,i,tempc,tempcn,num,dp[105];//dp[i]表示看守以i为根的树所需要的最小经费
    tree trees[105];
    cout<<"请输入结点的个数(小于101):";
    cin>>n;
    for(i=1;i<=n;i++)
    {
        cout<<"请输入"<<i<<"号结点的相关信息:";
        cin>>tempc>>tempcn;
        trees[i] = tree(tempc,tempcn);
    }
    for(num=0;num<n-1;num++)
    {
        for(i = 1;i<=n;i++)
        {
            if(trees[i].childnum == num)
            {
                if(num == 0)
                {
                    dp[i] = trees[i].cost;
                }
                else
                {
                    int sum = 0;
                    for(int j = 1;j<=trees[i].childnum;j++)
                    {
                        sum+= childcost(trees, trees[i].childid[j]);
                    }
                    dp[i] = min(trees[i].cost+sum,childcost(trees, i));//要么自己的耗费+孩子的孩子的耗费之和,要么孩子的耗费之和,二者取小
                }
            }
        }
    }
    cout<<maxone(dp,1,n)<<endl;//在dp数组中开销最大的肯定是根结点。
    return 0;
}

5.算法优化:

我不到怎么优化啊。。。

实验五:贪心算法和随机算法

实验5.1 背包问题

1.题干:

有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。

物品

A

B

C

D

E

F

G

重量

35

30

60

50

40

10

25

价值

10

40

30

50

35

40

30

2.算法描述:

对于给出的背包容量m,物品个数n,以及各个物品的重量w和总价值v,输出背包能承载的最大总价值,以及放入物品的种类和多少。

3.算法思路:

采用贪心算法,对所有物品按照单位重量的价值进行排序,优先选单位重量的价值高者的全部,最后再取剩下的单位重量的价值高者的部分。

4.代码实现:

#include <iostream>
using namespace std;
void swap(int a[],int i1,int i2)
{
    int temp = a[i1];
    a[i1] = a[i2];
    a[i2] = temp;
    return;
}
void swap(double a[],int i1,int i2)
{
    double temp = a[i1];
    a[i1] = a[i2];
    a[i2] = temp;
    return;
}
int main()
{
    int n,index[105],w[105],v[105],m,i,j;
    double sum = 0,v_per_w[105];
    cout<<"请输入背包的容量:";
    cin>>m;
    cout<<"请输入物品的个数:";
    cin>>n;
    for(i = 1;i<=n;i++)
    {
        index[i] = i;
        cout<<"请输入"<<i<<"号物品的重量:";
        cin>>w[i];
        cout<<"请输入"<<i<<"号物品的总价值:";
        cin>>v[i];
        v_per_w[i] = (1.0*v[i])/w[i];
    }

    for(i=1;i<n;i++)
    {
        int max = i;
        for(j=i+1;j<=n;j++)
        {
            if(v_per_w[j]>v_per_w[max])
                max = j;
        }
        if(max != i)
        {
            swap(index,i,max);
            swap(w,i,max);
            swap(v,i,max);
            swap(v_per_w,i,max);
        }
        cout<<v_per_w[i]<<endl;
    }
    i=1;
    while(m>w[i])
    {
        sum+=v[i];
        m-=w[i];
        i++;
    }
    sum+= (v_per_w[i]*m);
    double percent = (m*1.0)/w[i];
    cout<<"总价值最大为:"<<sum<<endl;
    cout<<"方案为:"<<endl;
    for(int j=1;j<i;j++)
    {
        cout<<index[j]<<"号物品取全部"<<endl;
    }
    cout<<index[i]<<"号物品取"<<percent<<endl;
    return 0;
}

5.算法优化:

这玩意真没有优化的必要了吧。。。

实验5.2 照亮的山景

1.题干:

在一片山的上空,高度为T处有N个处于不同位置的灯泡,如图。如果山的边界上某一点于某灯i的连线不经过山的其它点,我们称灯i可以照亮该点。开尽量少的灯,使得整个山景都被照亮。山被表示成有m个转折点的折线。

提示:照亮整个山景相当于照亮每一个转折点。

2.算法描述:

对于输入的灯泡高度t,灯泡个数n,每个灯泡的横坐标l_x,折点个数m,每个折点的坐标(xi,yi),输出最少的开灯组合。

3.算法思路:

通过计算灯泡到折点的斜率来确定哪些点能被这个灯泡照到:对于x大于灯泡的横坐标l_x的点,后面能照到的折点和灯泡构成的直线的斜率为非减序列;而对于x小于灯泡横坐标l_x的点,前面能照到的折点和灯泡构成的直线的斜率为非递增序列。

1.

2.

3.

4.

5.

 6.

7.

确定好每个灯能照到哪些点后,这个问题就变成了,如何取子集,使这些子集的交集为全集,且子集个数最少。这好像是一个NPC问题,目前没有多项式复杂度的解法,所以我采用模拟退火算法这种启发式算法来求解。效果也还可以。 

4.代码实现:

#include <iostream>
using namespace std;
int cnt[105],details[105][105],a[105]={0};//cnt[k]表示k号灯泡能照到的折点的个数,details[k][]是具体哪个折点能被k号灯泡照到
double check(int get[],int n,int m)//对于一种亮灯泡的方案get[],计算这种方案能照亮的折点的百分比
{
    int i,j,count=0;
    for(i=1;i<=m;i++)
    {
        a[i] = 0;
    }
    for(i=1;i<=n;i++)
    {
        if(get[i] == 1)
        {
            for(j=1;j<=cnt[i];j++)
            {
                a[details[i][j]] = 1;
            }
        }
    }
    for(i=1;i<=m;i++)
    {
        if(a[i] == 1)
        {
            count++;
        }
    }
    double percent = (double)((1.0*count)/m);
    return percent;
}
int main()
{
    int t,n,m,l_x[105],x[105],y[105],l,r,i;
    cout<<"请输入灯泡的高度T以及灯泡的个数N:"<<endl;
    cin>>t>>n;
    cout<<"请输入"<<n<<"个灯泡的横坐标:"<<endl;
    for(i=1;i<=n;i++)
    {
        cin>>l_x[i];
    }
    cout<<"请输入折点的个数:"<<endl;
    cin>>m;
    cout<<"请输入"<<m<<"个折点的坐标(请按横坐标依次增大的顺序输入):"<<endl;
    for(i=1;i<=m;i++)
    {
        cin>>x[i]>>y[i];
    }
    for(int k=1;k<=n;k++)
    {
        r=1;
        while(l_x[k]>x[r])//找到第一个横坐标大于等于这个灯泡的点
            r++;
        if(l_x[k] == x[r])//如果这个点正好在灯泡下方,此时斜率为∞,分母为0会报错,所以要特殊处理
        {
            cnt[k]++;
            details[k][cnt[k]] = r;//记录这个点
            l = r-1;//设置左边的起点
            r = r+1;//设置右边的起点
        }
        else//这个点不在灯泡正下方
        {
            l = r-1;//设置左边的起点
            r = r;//设置右边的起点
        }
        if(r<=m)//如果右边还有点
        {
            double k_board = (1.0*(t-y[r]))/(l_x[k]-x[r]);//计算标记点和灯泡组成的直线的斜率
            cnt[k]++;
            details[k][cnt[k]] = r;
            for(i=r+1;i<=m;i++)
            {
                double temp_k = (1.0*(t-y[i]))/(l_x[k]-x[i]);
                if(temp_k>=k_board)//若后面的斜率变大了,那么就更新屏障的斜率
                {
                    cnt[k]++;
                    details[k][cnt[k]] = i;
                    k_board = temp_k;
                }
            }
        }
        if(l>0)//如果左边还有点
        {
            double k_board = (1.0*(t-y[l]))/(l_x[k]-x[l]);
            cnt[k]++;
            details[k][cnt[k]] = l;
            for(i=l-1;i>0;i--)
            {
                double temp_k = (1.0*(t-y[i]))/(l_x[k]-x[i]);
                if(temp_k<=k_board)//若前面的斜率变小了,那么就更新屏障的斜率
                {
                    cnt[k]++;
                    details[k][cnt[k]] = i;
                    k_board = temp_k;
                }
            }
        }
    }
    //模拟退火部分
    int bestget[105]={0},tempget[105]={0},nowget[105]={0},bestsize=0,tempsize=0,nowsize=0;
    //best/temp/nowget分别为最佳,暂时,目前的亮灯方案。best/temp/nowsize分别为最佳,暂时,目前的亮灯个数
    double temperature = 200,a = 0.99,bestmax=0,tempmax=0,nowmax=0;//best/temp/nowget分别为最佳,暂时,目前的照到的折点的百分比。
    while(temperature > 0.1)
    {
        for(i=1;i<=100;i++)
        {
            int random_index = (int)(rand()%n)+1;
            nowget[random_index] = (nowget[random_index]+1)%2;
            nowmax = check(nowget, n, m);
            nowsize = 0;
            for(int j=1;j<=m;j++)
            {
                if(nowget[j])
                    nowsize++;
            }
            if(nowmax>=tempmax||(tempmax - nowmax)/temperature>(1.0*rand())/RAND_MAX)
            {
                if((nowmax == tempmax) && (nowsize > tempsize))
                    continue;
                tempmax = nowmax;
                tempsize = nowsize;
                for(int j=1;j<=n;j++)
                {
                    tempget[j] = nowget[j];
                }
            }
            
        }
        if(tempmax>=bestmax)
        {
            if((tempmax == bestmax) && (tempsize > bestsize))
                continue;
            bestmax = tempmax;
            bestsize = tempsize;
            for(int j=1;j<=n;j++)
            {
                bestget[j] = tempget[j];
            }
        }
        temperature*=a;
    }
    for(i=1;i<=n;i++)
    {
        cout<<bestget[i]<<" ";
    }
    cout<<endl;
    return 0;
}

5.算法优化:

模拟退火部分的参数应该可以调整优化,但是我不是很懂参数设置。

实验5.3 搬桌子问题

1.题干:

某教学大楼一层有n个教室,从左到右依次编号为1、2、…、n。现在要把一些课桌从某些教室搬到另外一些教室,每张桌子都是从编号较小的教室搬到编号较大的教室,每一趟,都是从左到右走,搬完一张课桌后,可以继续从当前位置或往右走搬另一张桌子。输入数据:先输入n、m,然后紧接着m行输入这m张要搬课桌的起始教室和目标教室。输出数据:最少需要跑几趟。

2.算法描述:

对于输入的教室个数n,桌子个数m,以及各个桌子的起始教室start和目标教室end,给出全部搬完的最少的趟数。

3.算法思路:

采用贪心算法,优先搬移起始教室最近的桌子(或者目标教室最近的教室好像也可以?)即可。

4.代码实现:

#include <iostream>
using namespace std;
void swap(int a[],int i1,int i2)
{
    int temp = a[i1];
    a[i1] = a[i2];
    a[i2] = temp;
    return ;
}
int main()
{
    int n,m,start[105],end[105],used[105]={0},i,j,cnt=0,num=0,temp;
    cout<<"请输入教室的个数n和桌子的个数m:";
    cin>>n>>m;
    for(i=1;i<=m;i++)
    {
        cout<<"请输入第"<<i<<"个桌子的起始教室和目标教室:";
        cin>>start[i]>>end[i];
    }
    for(i=1;i<m;i++)
    {
        int min = i;
        for(j=i+1;j<=m;j++)
        {
            if(start[j]<start[min])
                min = j;
        }
        if(min != j)
        {
            swap(start,i,min);
            swap(end,i,min);
        }
    }
    while(num<m)
    {
        temp = 0;
        for(i=1;i<=n;i++)
        {
            if(used[i] == 0&&start[i]>=temp)
            {
                temp = end[i];
                used[i] = 1;
                num++;
            }
        }
        cnt++;
    }
    cout<<cnt<<endl;
    return 0;
}

5.算法优化:

贪心好像也没什么好优化的。。。

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值