分治算法的主要思想就是将原问题分解为多个相同结构的子问题,通过分别解决子问题的方式,最终解决原问题。分治算法主要解决可以进行线性模块划分的问题,主要步骤就是三个:分解原问题,解决子问题,合并子问题的解。下面以最大最小值问题和二分查找问题为例简单介绍。
最大最小值问题:已知数列{a(n)},求解其最大值和最小值。以下是最大最小值问题的C语言程序。
#include
#include
#include
#define ANUM 15
template<typename T>
void maxmin(T *array,int left,int right,T *max,T*min) {
int mid;
T lmax, lmin, rmax, rmin;
if (left == right) {
*max = array[left];
*min = array[right];
return;
}
mid = (left + right) / 2;
maxmin(array, left, mid, &lmax, &lmin);
maxmin(array, mid+1, right, &rmax, &rmin);
if (lmax > rmax) {
*max = lmax;
}
else {
*max = rmax;
}
if (lmin < rmin) {
*min = lmin;
}
else {
*min = rmin;
}
return;
}
以上程序编译运行后结果如下:
长度为15的数组:
26 10 32 39 35 56 78 10 25 21 77 30 37 10 29
数列a[15]的最大值为78,最小值为10
二分查找问题:已知数列{a(n)},求解其是否含有某值val。以下二分查找问题的C语言程序。
int main()
{
//声明并初始化变量max, min, a[ANUM];
unsigned int max, min, a[ANUM];
srand((unsigned int)time(NULL));
for (int i = 0; i < ANUM; i++) {
a[i] = rand() % 100;
}
printf("长度为%d的数组:\n", ANUM);
for (int i = 0; i < ANUM; i++) {
printf("%d ", a[i]);
}
printf("\n");
maxmin(a, 0, ANUM - 1, &max, &min);
printf("数列a[%d]的最大值为%d,最小值为%d\n", ANUM, max, min);
return 0;
}
以上程序编译运行后结果如下:
请输入要求解汉诺塔数列的阶数:64
第64项汉诺塔数列f=18446744073709551615
#include
#include
#include
#define ANUM 15
#define VALUE 6
template<typename T>
int bisearch(T *array, int left, int right, T *value) {
if (left >right) {
return -1;
}
int mid = (left + right) / 2;
if (*value == array[mid]) {
return 1;
}
else if (*value < array[mid]) {
bisearch(array, left, mid-1, value);
}
else {
bisearch(array, mid+1, right, value);
}
}
int comp(const void*a, const void*b) {
return *(int *)a - *(int *)b;
}
int main()
{
//声明并初始化变量max, min, a[ANUM];
unsigned int a[ANUM],val=VALUE;
srand((unsigned int)time(NULL));
for (int i = 0; i < ANUM; i++) {
a[i] = rand() % 100;
}
printf("长度为%d的原始数组排列:\n", ANUM);
for (int i = 0; i < ANUM; i++) {
printf("%d ", a[i]);
}
printf("\n");
printf("长度为%d的增序数组排列:\n", ANUM);
qsort(a, ANUM, sizeof(int), comp);
for (int i = 0; i < ANUM; i++) {
printf("%d ", a[i]);
}
printf("\n");
int r=bisearch(a, 0, ANUM - 1, &val);
if (r > 0) {
printf("数列a[%d]中有value=%d\n", ANUM, val);
}
else {
printf("数列a[%d]中没有value=%d\n", ANUM, val);
}
return 0;
}
以上程序编译运行后结果如下:
长度为15的原始数组排列:
44 17 72 78 77 69 36 18 42 16 22 25 68 36 23
长度为15的增序数组排列:
16 17 18 22 23 25 36 36 42 44 68 69 72 77 78
数列a[15]中没有value=6
贪心算法(又称贪婪算法)是指在求解问题时,总是做出在当前看来是最好的选择。也就是说贪心算法不考虑整体最优,所做出的仅是某种局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题能产生整体最优解或者是整体最优解的近似解。
活动安排问题:设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si
#include
#include
#include
#define ANUM 15
//#define VALUE 6
//template
struct Act{
int start;
int end;
};
int comp(const void* a, const void* b) {
return ((Act *)a)->end - ((Act *)b)->end;
}
int greedy_activity_selector(struct Act act[])
{
int num = 0, i = 0;
for (int j = 1; j < ANUM; j++)
{
if (act[j].start >= act[i].end)
{
i = j;
num++;
}
}
return num;
}
int main()
{
//声明并初始化变量max, min, a[ANUM];
//unsigned int a[ANUM],val=VALUE;
Act act[ANUM] = {0};
srand((unsigned int)time(NULL));
for (int i = 0; i < ANUM; i++) {
act[i].start = rand() % 24;
act[i].end = rand() % 24;
}
printf("长度为%d的原始结构体数组排列:\n", ANUM);
for (int i = 0; i < ANUM; i++) {
printf("[%d,%d] ", act[i].start, act[i].end);
}
printf("\n");
printf("长度为%d的增序结构体数组排列:\n", ANUM);
qsort(act, ANUM, sizeof(Act), comp);
for (int i = 0; i < ANUM; i++) {
printf("[%d,%d] ", act[i].start, act[i].end);
}
printf("\n");
int r= greedy_activity_selector(act);
printf("结构体数列act[%d]最多可以安排%d场活动\n", ANUM, r);
return 0;
}
以上程序编译运行后结果如下:
长度为15的原始结构体数组排列:
[12,0] [1,5] [22,10] [0,4] [22,14] [3,4] [0,23] [20,13] [14,12] [5,4] [1,13] [10,21] [17,20] [22,20] [7,7]
长度为15的增序结构体数组排列:
[12,0] [5,4] [0,4] [3,4] [1,5] [7,7] [22,10] [14,12] [20,13] [1,13] [22,14] [17,20] [22,20] [10,21] [0,23]
结构体数列act[15]最多可以安排8场活动
钱币找零问题:这个问题在我们日常生活中非常普遍。假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0、c1、c2、c3、c4、c5、c6张。现在要用这些钱来支付K元,至少需要多少张纸币?以下是钱币找零问题贪心算法的C语言程序。
#include
#include
#include
#define ANUM 7
#define min(a,b) (((a) < (b)) ? (a) : (b))
int Count[ANUM] = { 7,0,5,3,0,2,8 };
int Value[ANUM] = { 1,2,5,10,20,50,100 };
int solve(int money)
{
int num = 0;
for (int i = ANUM - 1; i >= 0; i--)
{
int c = min(money / Value[i], Count[i]);
money = money - c * Value[i];
num += c;
}
if (money > 0) num = -1;
return num;
}
int main()
{
int money;
scanf_s("%d", &money);
/*cin >> money;*/
int res = solve(money);
if (res != -1)
//cout << res << endl;
printf("At least result=%d\n", res);
else
//cout << "NO" << endl;
printf("NO!\n");
return 0;
}
以上程序编译运行后结果如下:
252
At least result=5
回溯算法的基本思路就是:选择一条路径,能走通则走,走不通则退回来,换条路径重新尝试。回溯算法也叫试探法,它是一种系统地搜索问题的解的方法,把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。回溯算法主要有以下四个步骤:1.定义一个解空间,它包含问题的解; 2.利用适于搜索的方法组织解空间;3.利用深度优先法搜索解空间;4.利用限界函数避免移动到不可能产生解的子空间。问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性。下面用背包问题和八皇后问题来说说回溯算法。
背包问题:一个小偷面前有一堆(n个)财宝,每个财宝有重量w和价值v两种属性,而他的背包只能携带一定重量的财宝(Capacity),在已知所有财宝的重量和价值的情况下,如何选取财宝,可以最大限度的利用当前的背包容量,取得最大价值的财宝(或求出能够获取财宝价值的最大值)。以下是背包问题回溯算法的C语言程序。
#include
#define N 3 //物品的数量
#define C 16 //背包的容量
int w[N] = { 10,8,5 }; //每个物品的重量
int v[N] = { 5,4,1 }; //每个物品的价值
int x[N] = { 0,0,0 }; //x[i]=1代表物品i放入背包,0代表不放入
int CurWeight = 0; //当前放入背包的物品总重量
int CurValue = 0; //当前放入背包的物品总价值
int BestValue = 0; //最优值;当前的最大价值,初始化为0
int BestX[N]; //最优解;BestX[i]=1代表物品i放入背包,0代表不放入
//t = 0 to N-1
void backtrack(int t)
{
//叶子节点,输出结果
if (t > N - 1)
{
//如果找到了一个更优的解
if (CurValue > BestValue)
{
//保存更优的值和解
BestValue = CurValue;
for (int i = 0; i < N; ++i) BestX[i] = x[i];
}
}
else
{
//遍历当前节点的子节点:0 不放入背包,1放入背包
for (int i = 0; i <= 1; ++i)
{
x[t] = i;
if (i == 0) //不放入背包
{
backtrack(t + 1);
}
else //放入背包
{
//约束条件:放的下
if ((CurWeight + w[t]) <= C)
{
CurWeight += w[t];
CurValue += v[t];
//printf("t1=:%d\n", t);
backtrack(t + 1);
//printf("t2=:%d\n", t);
CurWeight -= w[t];
CurValue -= v[t];
}
}
}
//PS:上述代码为了更符合递归回溯的范式,并不够简洁
}
}
int main(int argc, char* argv[])
{
backtrack(0);
printf("最优值BestValue:%d\n", BestValue);
for (int i = 0; i < N; i++)
{
printf("最优解BestX[%d]:%-3d",i, BestX[i]);
}
return 0;
}
以上程序编译运行后结果如下:
最优值BestValue:6
最优解BestX[0]:1 最优解BestX[1]:0 最优解BestX[2]:1
八皇后问题:该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。以下是八皇后问题回溯算法的C语言程序。
#include
#define N 8
int Board[N][N]; //棋盘 0表示空白 1表示有皇后
int way; //摆放的方法数
//判断能否在(x,y)的位置摆放一个皇后;0不可以,1可以
int Feasible(int row, int col)
{
//位置不合法
if (row > N || row<0 || col >N || col < 0)
return 0;
//该位置已经有皇后了,不能
if (Board[row][col] != 0)
{ //在行列冲突判断中也包含了该判断,单独提出来为了提高效率
return 0;
}
//
//下面判断是否和已有的冲突
//行和列是否冲突
for (int i = 0; i < N; ++i)
{
if (Board[row][i] != 0 || Board[i][col] != 0)
return 0;
}
//斜线方向冲突
for (int i = 1; i < N; ++i)
{
/* i表示从当前点(row,col)向四个斜方向扩展的长度
左上角 \ / 右上角 i=2
\/ i=1
/\ i=1
左下角 / \ 右下角 i=2
*/
//左上角
if ((row - i) >= 0 && (col - i) >= 0) //位置合法
{
if (Board[row - i][col - i] != 0)//此处已有皇后,冲突
return 0;
}
//左下角
if ((row + i) < N && (col - i) >= 0)
{
if (Board[row + i][col - i] != 0)
return 0;
}
//右上角
if ((row - i) >= 0 && (col + i) < N)
{
if (Board[row - i][col + i] != 0)
return 0;
}
//右下角
if ((row + i) < N && (col + i) < N)
{
if (Board[row + i][col + i] != 0)
return 0;
}
}
return 1; //不会发生冲突,返回1
}
//摆放第t个皇后 ;从1开始
void Queen(int t)
{
//摆放完成,输出结果
if (t > N)
{
way++;
/*如果N较大,输出结果会很慢;N较小时,可以用下面代码输出结果
for(int i=0;i
for(int j=0;j
printf("%-3d",Board[i][j]);
printf("\n");
}
printf("\n------------------------\n\n");
*/
}
else
{
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
//(i,j)位置可以摆放皇后,不冲突
if (Feasible(i, j))
{
Board[i][j] = 1; //摆放皇后t
Queen(t + 1); //递归摆放皇后t+1
Board[i][j] = 0; //恢复
}
}
}
}
}
//返回num的阶乘,num!
int factorial(int num)
{
if (num == 0 || num == 1)
return 1;
return num * factorial(num - 1);
}
int main(int argc, char* argv[])
{
//初始化
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
Board[i][j] = 0;
}
}
way = 0;
Queen(1); //从第1个皇后开始摆放
//如果每个皇后都不同
printf("考虑每个皇后都不同,摆放方法:%d\n", way);//N=8时, way=3709440 种
//如果每个皇后都一样,那么需要除以 N!出去重复的答案(因为相同,则每个皇后可任意调换位置)
printf("考虑每个皇后都不同,摆放方法:%d\n", way / factorial(N));//N=8时, way=3709440/8! = 92种
return 0;
}
以上程序编译运行后结果如下:
考虑每个皇后都不同,摆放方法:3709440
考虑每个皇后都不同,摆放方法:92