今天来讲贪心算法吧。
贪心法就是根据人类的本性命名的。每次选择时都选择那个当前最好的结果就行了,也就是我们常说的局部最优。但是大多数情况下局部最优并不一定是全局最优,所以贪心算法并不一定可以得到全局最优解。相反的,如果你可以确定一个系统的局部最优一定可以推出全局最优的话,那么毫无疑问,直接使用贪心算法,因为贪心算法的效率比动规快太多了。
下面就来看题吧。
**
会场安排问题
**
假设要在足够多的会场里安排一批活动,并希望使用尽量少的会场。要求:输入各个活动的开始时间和结束时间,输出要找的最小会场数。
这道题其实很简单,关键是如何贪心,贪心的办法也很简单,就是按照结束的时间进行贪心,每次选取开始时间晚于前面活动结束时间的并且结束时间最早的活动,为什么是结束时间而不是开始时间或活动时长?因为结束的越早,剩余的时间就越多,其他活动可以加入的可能性就越大。
那么下面就可以开始贪了,假设只需要一个会场,然后将尽量多的活动安排进这个会场里,如果活动没有安排完,那么就得新建一个会场,并将剩余的活动尽可能多的安排在新的会场里,如果活动还没有安排完,那么就又得新建一个会场…………
下面放出我的代码:
#include <stdio.h>
#include <stdlib.h>
#define swap(i,j) i=i+j;j=i-j;i=i-j;
static int plan(int *, int *, int);
static int sort(int *, int *, int);
int main()
{
// int n;
// printf("please input the number of n : \n");
// scanf("%d",&n);
// int * s = (int *)malloc(n * sizeof(int));
// int * e = (int *)malloc(n * sizeof(int));
// for (int i = 0; i < n; i++)
// {
// scanf("%d %d",&s[i],&e[i]);
// }
int a[] = {1,12,25,27,36};
int b[] = {23,28,35,80,50};
int n = 5;
int * s = a;
int * e = b;
printf("%d",plan(s,e,n));
return 0;
}
int plan(s,e,n)
int * s, * e, n;
{
int m = 0, count = 0;
sort(s,e,n);
while (m != n)
{
int temp = 0;
for (int i = 0; i < n; i++)
{
if (s[i] >= temp)
{
temp = e[i];
s[i] = -1;
e[i] = -1;
m++;
}
}
count++;
}
return count;
}
int sort(s,e,n)
int * s, * e, n;
{
for (int i = 0; i < n; i++)
{
for (int j = i; j < n; j++)
{
if (e[i] > e[j])
{
swap(e[i],e[j]);
swap(s[i],s[j]);
}
}
}
return 0;
}
下面再看一道题:
**
出现次数超过一半的数
**
已知数组中有一个数的出现次数超过数组长度的一半,要求找出这个数。
乍一看,直接想到的方法就是统计了,将数组一次遍历,统计出各个数字的出现次数,然后找出出现次数超过一半的数字就好了。
这次讲得是贪心算法,当然不会用上面的办法。既然是贪心,就得有贪的办法。发现题目中说这个数是存在的,所以不需要考虑不存在的情况,而且这个数的出现次数超过数组长度的一半,注意:是超过而不是不小于。既然是超过,那么就满足这个数的出现次数一定比其他所有数的总数大。
那么可以确定贪心方案:
每次删除两个不一样的数,就有两种可能:
①:这两个数都不是我们要找的数;
②:这两个数只有一个不是我们要找的数;
但是,这两种可能都会不会改变问题的本质,原本超过数组长度一半的数,在我们删除两个不一样的数后,依旧是超过数组长度一半的数。
当数组中剩下的所有的数都相同时,这个数就是我们要求的数了。
代码如下:
#include <stdio.h>
#include <stdlib.h>
static int half(int *, int);
int main()
{
int a[] = {1,2,1,3,3,1,1,3,1,1,2};
// int a[] = {1,1,1,1,1,2,2,2,2};
int len = sizeof(a) / sizeof(a[0]);
// int len;
// printf("please input the number of n : \n");
// scanf("%d",&len);
// int * a = (int *)malloc(len * sizeof(int));
// for (int i = 0; i < len; i++)
// scanf("%d",&a[i]);
printf("%4d",half(a,len));
}
int half(int * a, int n)
{
while(1)
{
int tag = 0;
for (int i = 1; i < n; i++)
{
if (a[0] != a[i])
{
if (n == 3)
return a[3-i];
a[0] = a[n-2];
a[i] = a[n-1];
n = n-2;
i = 1;
tag = 1;
}
for (int i = 0; i < n; i++)
printf("%4d",a[i]);
printf("\n");
}
if (tag == 0)
return a[0];
}
}
很简单吧,贪心算法就是这样,只要有思路,做出来是很容易的。但是,就是我前面说的,局部最优不一定能推出全局最优,所以使用贪心算法经常会导致得不到最优解。判断一个问题是否可以用贪心法求得最优解,经常需要使用数学归纳法对其进行证明,如果能够证明局部最优可以退出全局最优,那么就可以使用贪心法求最优解了。
说个事情,有QQ好友问我要代码的C语言版,我说明一下:
我博客上目前写的的所有代码都是C语言的,不要因为我写了for (int i = 0; i < n; i++)这样的循环就觉得代码是C++(大多数人都以为在C语言里,for循环的迭代体i必须事先定义好,不可以在使用for时定义),其实从C99起就已经支持这种写法了。VC编译器是什么情况我并不知道,但是使用GCC编译器的各位,你们的版本肯定是高于C99的,为什么还是编译不通过呢?原因是GCC的默认编译标准是C89,想要使用C99标准这样编译:
gcc filename.c -o filename -std=c99
我是算法吹,以后会给大家带来更多精彩的算法。