学习笔记---回溯算法与贪心算法

回溯和贪心


回溯


意义:编程解决问题时,常遇到需要例遍所有可能性来求解问题的情况。此时,回溯将是不错的选择。


代码示例:

#include 
#include 
#include 
/*这个程序用来测试回溯算法在解决问题中的应用*/

/*

八皇后问题:

经典的八皇后问题,即在一个8*8的棋盘上放8个皇后,
使得这8个皇后无法互相攻击( 任意2个皇后不能处于同一行,同一列或是对角线上),
输出所有可能的摆放情况。

*/
/*
将以上问题引申成在n*n的棋盘上摆放n个皇后的问题。
以下代码就使用了回溯法解决n皇后问题。
*/
int place(int*,int);//判断当前位置摆放皇后是否可行
void nQueens(int*,int);//n皇后问题算法核心
void printSolution(int*,int);//用于输出已求出的解决方案
int main()
{
    int n;
    int *x;//指针x作为动态数组使用
    scanf("%d",&n);
    //为指针x动态分配n+1个int的空间。用于代替数组模拟棋盘

    x=(int*)malloc(sizeof(int)*(n+1));
    nQueens(x,n);//将数组和皇后数量传入,开始求解
    return 0;
}
int place(int *x,int k)//形参x为模拟棋盘的数组,k为当前放置皇后的行数
{
    int i;
    //循环例遍数组中每一个已经确定位置的皇后
    for(i=1;i0)
    {
        x[k]++;//首先在第k行第1列摆放皇后
        while(x[k]<=n&&!place(x,k))//判断是否可行
            x[k]++;//否,则在下一列摆放
        if(x[k]<=n)//判断以上循环结束之后,列数是否超标
        {
            if(k==n)//判断是否摆放完最后一个皇后
                printSolution(x,n);//是,则输出此时的解
            else
            {
                k++;//否则,使行标加一
                x[k]=0;//将第k行清空
            }
        }
        else//如果列数超标,说明当前行无法摆放皇后。
            k--;//使k减一,下次循环时将重新摆放第k行的皇后
    }
}
void printSolution(int *x,int n)//用于输出结果的函数
{
    int i,j;
    /*使用双重循环输出平面图像*/
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=n;j++)
        {
            if(j==x[i])
                printf("Q ");
            else
                printf("* ");
        }
        printf("\n");
    }
    printf("\n");
}

结果:

学习笔记---回溯算法与贪心算法_第1张图片

解析:

1.对于place函数中两个判断条件的解析:

第一个判断条件很好理解:两个皇后不能在同一列上.

第二个判断条件是通过线的表达式推断的。将棋盘视为平面直角坐标系,每个皇后作为坐标系上一个特殊的点。则能以每个皇后的坐标画出两条斜率分别为1和-1的直线。要保证两个皇后的位置不冲突,只要保证两个皇后衍生出的所有直线上都不出现另一个皇后即可(例如:x[i]和x[k]两个皇后的坐标分别是:(i,x[i]),(k,x[k])。则如果两个皇后出现在同一直线上必有:y=x+(x[i]-i)y=x+(x[k]-k)是同一条直线。或y=-x+(x[i]+i)y=-x+(x[k]+k)是同一条直线。则可得:x[i]-x[k]=i-kx[i]-x[k]=k-i。即:|x[i]-x[k]|=|i-k|

2.以上代码中的x数组第0个元素不使用,数组下标n代表棋盘的第n行,其中的数组值m代表第n行第m列放置一个皇后这种设计保证每行有且只有一个皇后,规避了大量重复判断的同时也比二维数组更节省资源。

3.以上代码中nQueens函数使用了循环加回溯保证例遍每一种皇后摆放情况。如无法直接理解,可使用单步调试观察。


贪心算法


定义:通过局部最优达到全局最优


代码示例1:

#include 
#include 
/*这个程序用来测试贪心算法*/

/*
对于以下问题:
有3种硬币,面值分别为1元、5角和1角,各自的数量不限。
设计自动售货机时,需要为顾客找零钱,请设计程序进行计算,
要求给顾客的硬币枚数最少
*/

int main()
{
    int money[10]={100,50,10,0};//存储着以分为单位的各种货币
    int x;//x用于存储需要找零的金钱总数
    int i=0,n=0,m;//n用于存储零钱总枚数
    scanf("%d",&x);
    /*只要需要找零的金钱数还未归零,并且还存在面值更小的零钱,就继续循环*/
    while(x>0&&money[i]!=0)
    {
        m=x/money[i];//使m为当前货币可使用的最大量
        n+=m;//货币总枚数增加
        x-=m*money[i];//剩余需要找零的金钱减少
        i++;//i++,使用数组中存储的下一种零钱继续找零
    }
    if(x==0)//最终,如果成功找零
        printf("%d\n",n);//输出零钱枚数
    else
        printf("fail\n");//否则,输出失败

    return 0;
}
结果:

学习笔记---回溯算法与贪心算法_第2张图片
解析:

1.以上程序的核心是:首先用面值较大的零钱进行找零。直到无法找零,再用面值较小的找零。

2.贪心思路:不从整体最优考虑,而是保证所做的每一步选择都是局部最优的方案,最终将所有的局部最优糅合为整体最优


代码示例2:


#include 
#include 
/*
货船装箱问题

已知一批集装箱的重量,要将它们装入一个载重量为c的货船中,
在货船装载体积不限的前提下,怎样才能将最多的集装箱装上船?

*/

/*使用贪心法能解决此问题*/
/*
设货船重量c为13,共5个集装箱重量依次为5,7,6,3,2

只要优先将重量轻的装上船即可。
*/
#define N 50

int main()
{
    /*变量定义及初始化*/
    int w[N];//用于存储各个集装箱的重量
    int x[N];//用于记录已装入货船的箱子
    int t[N];//用于存储各个集装箱的编号
    int c;//最大载重量
    int n;//箱子总数
    int i,j,tmpw,tmpt;
    printf("输入货船的最大载重量:");
    scanf("%d",&c);
    printf("输入集装箱个数:");
    scanf("%d",&n);
    printf("输入每个集装箱的质量:\n");
    for(i=0;iw[j+1])
            {
                tmpw=w[j];
                w[j]=w[j+1];
                w[j+1]=tmpw;
                tmpt=t[j];
                t[j]=t[j+1];
                t[j+1]=tmpt;
            }

    /*从小到大将集装箱装入货船,直到无法再装*/
    for(i=0;i
结果:

学习笔记---回溯算法与贪心算法_第3张图片
解析:

同样是很容易理解的贪心思路:要装入的箱子数目最多,只要将重量小的箱子优先装入即可。


贪心算法的核心是:

每次抉择时,都“贪便宜”。无视这个选择对未来的抉择的影响,只选择当前最优的选项。这种算法在大多数情况下并不能使最终结果“最优”,但常常能得到“ 较优”的结果。在实际应用时,难点不在于贪心的实现,而在于判断题目是否可以用贪心来解。以及怎样的选择才是“当前最优”的。




你可能感兴趣的:(学习笔记,C语言,进阶)