目录
1 算法设计基础
算法设计的过程
算法的描述方法
2 算法效率分析基础
41页基本渐进效率类型
非递归形式算法时间复杂性与空间复杂性分析
np问题
3 迭代法
迭代法的基本思想与基本步骤
例3-3 内存移动问题(轮转)
#include <stdio.h>
//使用地址来交换a和b的值
void swap(int * a,int * b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
//使用递归函数来求a和b的最大公约数
int gcd(int a,int b)
{
if(a < b)
{
swap(&a,&b);
}
if(b == 0)
{
return a;
}
int tmp = gcd(b,a%b);
return tmp;
}
void rotate(int* nums, int numsSize, int k)
{
int count = gcd(k, numsSize);//count为a和b的最大公约数,也就是循环a圈的次数
for (int start = 0; start < count; ++start)
{
int current = start;//定义一个变量来辅助交换
int prev = nums[start];
for(int j= 0; j< numsSize/count; j++){
//数组里的元素挨个右移k个
int next = (current + k) % numsSize;
swap(&nums[next], &prev);
current = next;
}
}
}
int main(void)
{
int nums[6]={1,2,3,4,5,6};//数组初始化为1,2,3,4,5,6
int k = 2;
int len;
len = sizeof(nums)/sizeof(nums[0]);
k = k % len;
rotate(nums,len,k);
for(int i = 0;i < 6;i++)
{
printf("%d ",nums[i]);
}
printf("\n");
return 0;
}
4 蛮力法
蛮力法的基本思想
例4-4 蛮力法解决旅行商问题
tsp 问题, 旅行商问题 ,即TSP问题(Travelling Salesman Problem)又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。
假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。
#include<iostream>
using namespace std;
//蛮力(穷举)
//邻接矩阵
int n = 4;
int edge[4][4] = { 0,8,5,4,
8,0,7,3,
5,7,0,1,
4,3,1,0 };
int vertex[4] = { 'a','b','c','d' };
int a[] = { 0,1,2,3 };//解空间
int minvalue = 10000;//当前最小值
int path[4] = { 0 };//当前解的路径
int minpath[4] = { 0 };//最优解的路径
void showpath(int a[])
{
cout << "showpath:";
for (int i = 0; i < n; i++)
{
cout << a[i] << "\t";
}
//回到终点
cout <<a[0]<< endl;
}
void copypath(int minpath[])
{
for (int i = 0; i < n; ++i)
{
minpath[i] = a[i];
}
}
int cost()//求当前路径的权值
{
int sum = edge[0][a[0]];
int i;
for (i = 1; i < n; ++i)
{
sum += edge[a[i - 1]][a[i]];
}
sum += edge[a[i - 1]][0];//回到0
return sum;
}
//递归
void EnumTSP(int i)
{
int k, temp;
if (i == n - 1)//最后一个结点,递归出口
{
if (cost() < minvalue)//当前解的权值 < 当前最小
{
minvalue = cost();//更新最小权值
copypath(minpath);//赋值到最优解路径
}
}
else
{
for (int k = i; k < n; k++)
{
//全排列
temp = a[i]; a[i] = a[k]; a[k] = temp;
//下一层递归
EnumTSP(i + 1);
//恢复现场
temp = a[i]; a[i] = a[k]; a[k] = temp;
}
}
}
int main()
{
EnumTSP(0);//从第0层开始
showpath(minpath);
cout <<"minvalue:" << minvalue << endl;
return 0;
}
例4-5 蛮力法解决背包问题
编程实现对于背包问题的求解,假设有6个物品,其价值与重量依次为,{(42,7),(12,3),(40,4),(25,5),(18,6),(10,1)},背包可载入的物品的最大重量为10,求解一种将物品载入背包的方案,在不超过重量限制10的情况下,求出使载入物品总价值最高的装法。
#include <stdio.h>
#include <stdlib.h>
#define N 6
int r[N];//存储最优解
struct bag{
int v; //物品价值
int w; //物品重量
int flag; //选中标志
}a[N]={42,7,0,
12,3,0,
40,4,0,
25,5,0
,18,6,0,
10,1,0};
int W=10;//背包总重量
int ans; //选中物品的数量
int sum_w=0;
int sum_v=0;
//记录当前被选择的物品——当前最优子集合
int record(int sum_v){
int i;
int count = 0;
r[0] = sum_v; //r 是当最优子集合
for (i=0; i < N; i=i+1)
if (a[i].flag) //
{
count=count+1;
r[count] = i;
}
return count;
}
void knaps(int x)
{
int i;
// 超重终止条件
if (sum_w > W)
return;
// 记录较优解,最终留下的为最优解
if (sum_v > r[0])
ans=record(sum_v);
for (i=x; i < N; ++i)
{ // 加上第 i 件物品
sum_v = sum_v + a[i].v;
sum_w = sum_w+ a[i].w;
a[i].flag = 1;
knaps(i + 1); // 递归处理下一个物品
sum_v = sum_v -a[i].v; // 减去第 i 件物品
sum_w = sum_w- a[i].w;
a[i].flag = 0;
}
}
int main()
{
int i,w=0;
knaps(0);
printf("选中物品数量:%d; 总价值:%d\n",ans,r[0]);
for(i=1;i<=ans;i++)
{
printf("物品编号:%d;物品重量:%d;物品价值:%d\n",r[i],a[r[i]].w,a[r[i]].v);
w+=a[r[i]].w;
}
printf("背包能承载的总重量:%d; 选中物品总重量:%d\n",W,w);
return 0;
}
5 分治法
分治法的基本思想
快速排序 :数组分三路 + 随机基准元素
分治法的三个步骤
例5-1 分治法查找有序数组上的特定元素
利用分支算法,在升序的数组a上查找元素x的下标,如果x在a上,返回x在a上的下标,否则,返回-1。
这段代码是一个使用二分查找算法在有序数组中查找指定元素的程序。
正确答案;
请输入数组的长度n,不超过100:请输入数组中的n个元素的值(升序):请输入要查询的数:查找的值是20,位置是-1---请输入数组的长度n,不超过100:请输入数组中的n个元素的值(升序):请输入要查询的数:查找的值是109,位置是8
#include <stdio.h>
// 定义一个二分查找函数,参数为数组a、目标元素x、左边界left和右边界right
int binarySearch(int a[], int x, int left, int right) {
while (left <= right) { // 当左边界小于等于右边界时,继续查找
int mid = left + (right - left) / 2; // 计算中间位置的下标
if (a[mid] == x) { // 如果中间位置的元素等于目标元素x,则找到了目标元素
return mid; // 返回目标元素的下标
} else if (a[mid] < x) { // 如果中间位置的元素小于目标元素x,则目标元素在右半部分
left = mid + 1; // 更新左边界为中间位置的下一个位置
} else { // 如果中间位置的元素大于目标元素x,则目标元素在左半部分
right = mid - 1; // 更新右边界为中间位置的前一个位置
}
}
return -1; // 如果左边界大于右边界,说明目标元素不存在于数组中,返回-1表示未找到
}
int main() {
int a[100]; // 定义一个长度为100的整型数组a
int n; // 定义一个整型变量n,用于存储数组的长度
printf("请输入数组的长度n,不超过100:");
scanf("%d", &n); // 从标准输入读取数组的长度n
printf("请输入数组中的n个元素的值(升序):");
for (int i = 0; i < n; i++) { // 循环读取数组中的n个元素
scanf("%d", &a[i]); // 从标准输入读取数组的第i个元素的值
}
int x; // 定义一个整型变量x,用于存储要查询的目标元素的值
printf("请输入要查询的数:");
scanf("%d", &x); // 从标准输入读取要查询的目标元素的值
int locationOfX = -1; // 定义一个整型变量locationOfX,用于存储目标元素的位置
locationOfX = binarySearch(a, x, 0, n - 1); // 调用二分查找函数,传入数组a、目标元素x、左边界0和右边界n-1,将返回的位置赋值给locationOfX
printf("查找的值是%d,位置是%d", x, locationOfX); // 输出目标元素的值和位置
return 0; // 程序正常结束,返回0
}
例? 分治法求解无序数组上的最大值
实现在数组上,基于分治法,查找最大最小值的任务
Max element in the array is 25 Min element in the array is 1---Max element in the array is 16 Min element in the array is 2
#include <stdio.h>
#define N 100
// 定义结构体,用于存储最大和最小值
typedef struct {
int max;
int min;
} MinMax;
// 递归函数,用于求解数组中的最大和最小元素
MinMax findMinMax(int arr[], int low, int high) {
if (low == high) { // 如果只有一个元素,则它既是最大值也是最小值
MinMax result;
result.max = arr[low];
result.min = arr[low];
return result;
} else {
int mid = (low + high) / 2; // 找到中点
MinMax _leftResult,_rightResult;
_leftResult = findMinMax(arr,low,mid); // 递归求解左半部分的最大和最小值
_rightResult = findMinMax(arr,mid+1,high); // 递归求解右半部分的最大和最小值
// 合并结果,得到整个数组的最大和最小值
MinMax result;
if(_leftResult.max > _rightResult.max ) {
result.max = _leftResult.max;
} else {
result.max = _rightResult.max;
}
if(_leftResult.min < _rightResult.min ) {
result.min = _leftResult.min;
} else {
result.min = _rightResult.min;
}
return result;
}
}
int main() {
int arr[N],n;
scanf("%d",&n); // 从标准输入读取n个整数
for(int i=0;i<n;i++){
scanf("%d",arr+i); // 将每个整数存入数组arr中
}
MinMax result = findMinMax(arr, 0, n - 1); // 调用递归函数求解最大和最小值
printf("Max element in the array is %d\n", result.max); // 输出最大值
printf("Min element in the array is %d\n", result.min); // 输出最小值
return 0;
}
6 回溯法
回溯法的算法思想
例6-2 回溯法求解装载问题
完成基于回溯法求解装载问题
正确答案:
10 0 0 1 0 1---16 1 0 0 1 1
#include <stdio.h>
#include <stdlib.h>
#define NUM 100
int c; // 轮船载重量
int n; // 集装箱数量
int w[NUM]; // 集装箱重量数组
int nowc; // 当前载重量
int x[NUM]; // 当前解
int maxc; // 最优载重量
int maxx[NUM]; // 最优解
int r; // 可用的集装箱重量
void init() {
for (int i = 0; i < NUM; i++) {
w[i] = 0; // 初始化集装箱重量
x[i] = 0; // 初始化当前解
maxx[i] = 0; // 初始化最优解
}
nowc = 0; // 初始化当前载重量
maxc = 0; // 初始化最优载重量
r = 0; // 初始化可用集装箱重量
}
void input() {
scanf("%d", &c); // 录入轮船载重量
scanf("%d", &n); // 录入集装箱数量
for (int i = 1; i <= n; i++) { // 录入各个集装箱重量,0号空间不用
scanf("%d", &w[i]);
r = r + w[i]; // 可用集装箱重量r最开始为全部集装箱重量
}
}
void search2(int i) {
// 递归结束条件
if (i > n) {
if (nowc > maxc) {
maxc = nowc;
for (int j = 1; j <= n; j++) {
maxx[j] = x[j];
}
}
return;
}
r = r - w[i]; // 对第i层进行搜索,那么可用重量r相应减少
// 搜索左子树,对应x[i]=1的情况
if (nowc + w[i] <= c) { // 满足约束
x[i] = 1;
nowc += w[i];
search2(i + 1);
nowc -= w[i]; // 回溯回来后,恢复nowc为原来的值
x[i] = 0; // 回溯时也要恢复x[i]的值
}
// 搜索右子树
if (nowc + r > maxc) { // 当前的载重量加上剩余的集装箱重量表示接下来可处理载重量的上界
x[i] = 0;
search2(i + 1);
}
r += w[i]; // 对第i层进行搜索回来,恢复r
}
void output() {
printf("%d\n", maxc);
for (int i = 1; i <= n; i++) { // 录入各个集装箱重量,0号空间不用
printf("%-2d", maxx[i]);
}
}
int main() {
init(); // 初始化
input(); // 录入轮船载重量、集装箱数量和重量
//search1(1); // 非递归回溯法求解
search2(1); // 递归回溯法求解
output(); // 输出结果
return 0;
}
例6-3 回溯法求解n皇后问题
n皇后问题简述:
在n*n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与
之处在同一行或同一列或同一斜线上的棋子,利用回朔法求解n皇后问题。提示:递归实现回溯算法。
正确答案:
1: 2 4 1 3 2: 3 1 4 2---1: 2 4 6 1 3 5 2: 3 6 2 5 1 4 3: 4 1 5 2 6 3 4: 5 3 1 6 4 2
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define MAX_N 100
int nq = 0; // 定义全局变量nq,表示皇后的数量
int x[MAX_N]; // 定义全局数组x,用于存储每一行皇后所在的列号
int sum = 0; // 定义全局变量sum,用于记录解的个数
// 判断当前的长度为n的数组是否满足n皇后问题的条件,满足返回1,否则返回0。
int isOk(int n) {
for (int i = 0; i < n; i++) {
if (x[i] == x[n] || abs(x[i] - x[n]) == abs(i - n)) {
return 0;
}
}
return 1;
}
// 输出一个解,并增加解的计数器sum
void solutionFind() {
sum++;
printf("%d:", sum);
for (int i = 0; i < nq; i++) {
printf(" %d", x[i] + 1); // 输出列号,调整为从1开始
}
printf("\n");
}
// 递归函数,用于生成所有可能的解
void queueRecursion(int n) {
if (n == nq) {
solutionFind(); // 如果已经放置了n个皇后,则输出一个解
} else {
for (int i = 0; i < nq; i++) {
x[n] = i; // 将第n行的皇后放在第i列
if (isOk(n)) { // 如果当前位置合法,则继续放置下一个皇后
queueRecursion(n + 1);
}
}
}
}
int main() {
scanf("%d", &nq); // 输入皇后的数量
if (nq > 0 && nq < MAX_N) {
queueRecursion(0); // 调用递归函数开始求解
}
return 0;
}
例6-4 回溯法求解0-1背包问题
问题描述:
01背包问题是算法中的经典问题,问题描述如下:
对于给定的N个物品,第i个物品的重量为Wi,价值为Vi,对于一个最多能装重量C的背包,应该如何选择放入包中的物品,使得包中物品的总价值最大?
回溯可以理解为排除这些不满足条件的基本情况的过程。
剪枝
回溯法求解0-1背包问题的过程:
由于直接描述过程比较抽象,因此直接上例题
例题:假设N=3(有三件物品),三个物品的重量为{20,15,10},三个物品的价值为{20,30,25},对于一个最大承重为25的背包,求包中物品的组合最大的价值是多少?
题目的参考思路图如下,建议图文对照:(参考书为王红梅老师的《算法设计与分析》)对三件物品分别进行编号1,2,3,初始情况背包是空的
1.首先我们把1号物品放进背包里,此时背包里只有一件物品,总重量为0+20=20,没有超过承重25,因此可以将1号物品成功放入背包内;
2.接下来尝试把2号物品放入背包内,但是发现包中1号物品和2号物品的重量和为20+15=35,超过了承重25,因此不能把2号物品放入背包内;
3.接着考虑3号物品,此时包中只有1号物品。发现1号物品和3号物品的重量和为20+10=30,超过了承重25,因此3号物品也不能放入背包内;
4.由于只有3件物品,并且对于每一种物品我们都考虑过是否将其放入背包内,也就是找到了一种基本情况。找到一个基本情况后,我们就可以看看包里的物品的总价值了。这里包里只有一个1号物品,因此总价值为20;
5.重点来了!回溯过程:每次找出一种满足条件的基本情况就进行一次回溯,找到最后放入包中的物品并将其取出,接着考虑是否放入编号在这个物品之后的第一个物品。这里我们就把1号物品取出,接下来考虑是否放入2号物品;
6.取出1号物品后背包是空的,此时如果放入2号物品,背包总重量为15,没有超过背包承重,因此把2号物品放入背包内;
7.类似地,考虑将3号物品放入背包内。由于2号物品和3号物品的重量和为15+10=25,没有超过承重25,因此将其放入背包内;
8.由于考虑完了3号物品,因此又找到了一个基本情况,记下此时包里物品的总价值,为30+25=55。由于55高于上一种基本情况的总价值,因此将最优解更新为55;
9.进行一次回溯,取出背包中最后放入的物品,也就是3号物品,但是注意:当最后放入背包中的物品恰好是编号最大的物品时,需要额外进行一次回溯。为什么呢?因为编号最大的物品之后已经没有编号更大的物品了,因此没有可以考虑的下一种情况,只能在上一个层面上在进行一次回溯才能产生可能的最优解。(此处不必考虑只放入2号物品的情况,因为一定不是最优解,原因可以自己思考一下) 这里再回溯一次,也就是将倒数第二个放入包中的物品取出来,这里就取出2号物品。先后取出3号物品和2号物品之后包应该处于空的状态。
10.上一步中取出了2号物品,因此这一步直接考虑能否放入3号物品,简单的判断后即可得出可以放入,并且同上理也可以得出这是一种基本情况,但是由于包中只有3号物品,总价值为25,没有超过当前的最优解55,因此将该情况忽略。
11.最后一次回溯,取出包中的3号元素。由于此时包已经空了,并且最后一次取出的是编号最大的元素,那么说明算法已经完成了所有情况的遍历,算法终止,55是最优解。
————————————————
原文链接:https://blog.csdn.net/hanmo22357/article/details/124371395
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<malloc.h>
#include <stdlib.h>
#define N 100
//定义货物的结构体数组
typedef struct {
int weight;//重量
int value;//价值
int flag;//标记是否选择(0、1)
}Goods[N];
//定义全局变量
int n;//物品数量
int M;//背包容量
int bestValue;//目标函数的值
int best[N];//记录最大价值货物选择的解向量,0为不选,1为选
//初始化
void Init(Goods& goods) {
printf("物品数量和背包容量:");
scanf("%d %d", &n, &M);
for (int i = 0; i < n; i++) {
printf("第%d个物品的重量和价值:", i + 1);
scanf("%d %d", &goods[i].weight, &goods[i].value);
goods[i].flag = 0;
}
}
//判断是否满足约束方程
bool isOverweight(Goods& goods, int m) {
int sum_w = 0;
for (int i = 0; i <= m; i++) {
//计算已经选择“物品”的重量
sum_w += goods[i].flag * goods[i].weight;
}
//判定是否 超过背包容量
if (sum_w > M) {
return false;
}
return true;
}
void KnapSack(Goods& goods, int index, int n) {
//index代表货物的下标,调用函数时index一般为0(从第一个货物开始)
//n是物品数量
int i = 0;
int j = 0;
int sum_v = 0;//临时变量记录当前解下的总价值
//递归的终止条件
//探索到最后一层
if (index == n) {
for (i = 0; i < index; i++) {
计算已经选择“物品”的价值
sum_v += goods[i].value * goods[i].flag;
}
//更新最优解
if (sum_v > bestValue) {
bestValue = sum_v;//如果该解大于目标函数值,则将该解更新为最大值
for ( i = 0; i < n; i++) {
best[i] = goods[i].flag;//记录当前解向量
}
}
}
else {
for (j = 0; j < 2; j++) {
goods[index].flag = j;//flag取值为0、1
if (isOverweight(goods, index)) {
//如果满足约束方程则递归至下一个货物
KnapSack(goods,index + 1, n);
}
}
}
}
int main() {
Goods goods;
Init(goods);
KnapSack(goods, 0, n);
printf("最大价值为:%d\n", bestValue);
printf("解向量为:");
for (int i = 0; i < n; i++) {
printf("%d ", best[i]);
}
system("pause");
return 0;
}
分支限界法的算法思想
例6-8 分支限界法求解TSP问题
基于分支限界法求解TSP问题。、
正确答案:
0,2,4,3,1:21 0,2,4,3,1:21---0,4,1,3,2:85 0,1,4,3,2:75---0,3,4,6,2,1,5,9,8,7:125 0,3,4,6,2,8,7,1,5,9:110
#include <stdio.h>
#include <climits>
#include <stdlib.h>
int** distanceMatrix;//城市之间的距离矩阵
int n;//城市数量
int startCity = 0;//TSP问题中履行开始的城市
int* pathBest;//TSP问题的当前最优路径
int sumBest;//TSP问题的当前最优路径的总距离
struct Node {
//以结点的方式管理TSP问题中不同的搜索状态
int st;//当前路径的起始城市
int ed;//当前路径的终止城市
int k;//路径中城市的个数
int sumv;//当前路径总长度,从起始城市到终点城市
int lb;//当前状态下界函数计算的值
int* vis;//当前路径走过的城市
int* path;//当前路径
struct Node* next;//链表指针,以此来制作一个保存路径状态的队列
};
/*
初始化求解TSP问题的必要参数,主要包括城市数量、城市之间的距离矩阵、旅行的起始城市。
*/
void init() {
/*
n=5;
distanceMatrix=(int **)malloc(sizeof(int *)*n);
for(int i=0;i<n;i++){
distanceMatrix[i]=(int*)malloc(sizeof(int)*n);
distanceMatrix[i][i]=0;
}
distanceMatrix[0][1]=4;
distanceMatrix[0][2]=2;
distanceMatrix[0][3]=6;
distanceMatrix[0][4]=9;
distanceMatrix[1][2]=7;
distanceMatrix[1][3]=8;
distanceMatrix[1][4]=10;
distanceMatrix[2][3]=5;
distanceMatrix[2][4]=3;
distanceMatrix[3][4]=4;
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
distanceMatrix[i][j]=distanceMatrix[j][i];
}
}
startCity=0;
*/
scanf("%d", &n);
distanceMatrix = (int**)malloc(sizeof(int*) * n);
for (int i = 0; i < n; i++) {
distanceMatrix[i] = (int*)malloc(sizeof(int) * n);
for (int j = 0; j < n; j++) {
scanf("%d", distanceMatrix[i] + j);
}
}
scanf("%d", &startCity);
}
//计算输入的路径的总长度(不包含终点的返回距离)
int getSum(int* path, int k) {
int r = 0;
for (int i = 1; i < k; i++) {
r = r + distanceMatrix[path[i - 1]][path[i]];
}
return r;
}
//贪心算法求解TSP问题,将求解的路径存储在全局变量pathBest中,对应的sumBest存储路径的总长度。
void greedyTSP() {
pathBest = (int*)malloc(sizeof(int) * n);
int* visited = (int*)malloc(sizeof(int) * n);//记录访问过的城市
for (int i = 0; i < n; i++) {
visited[i] = 0;
}
pathBest[0] = startCity;
visited[startCity] = 1;
for (int i = 1; i < n; i++) {
int minDistance = INT_MAX;
int closestCity;
for (int j = 0; j < n; j++) {
if (visited[j] == 0 && distanceMatrix[pathBest[i - 1]][j] < minDistance) {
minDistance = distanceMatrix[pathBest[i - 1]][j];
closestCity = j;
}
}
pathBest[i] = closestCity;
visited[closestCity] = 1;
}
sumBest = getSum(pathBest, n) + distanceMatrix[pathBest[n - 1]][startCity];//针对当前的解的路径pathBest计算出其TSP路径的总距离
free(visited);
}
//打印当前全局的最优路径及其总距离
void print() {
for (int i = 0; i < n - 1; i++) {
printf("%d,", pathBest[i]);
}
printf("%d", pathBest[n - 1]);
printf(":%d\n", sumBest);
}
//lb=((已经历程)*2+新结点的进入边长度+新结点离开边长度+其它每个结点的两个最短边长度)/2
//根据上面的公式,计算结点状态的LB值。
void fillLB(struct Node* node) {
int a, b;
int r = 0;
//遍历所有没有被访问过的城市,找到最近的进入边和离开边
if (node->st == node->ed) {
for (int i = 0; i < n; i++) {
a = -1;
b = -1;
for (int j = 0; j < n; j++) {
if (i == j) {
continue;
}
if (a == -1) {
a = distanceMatrix[i][j];
}
else if (b == -1) {
b = distanceMatrix[i][j];
}
else {
if (distanceMatrix[i][j] < a) {
b = a;
a = distanceMatrix[i][j];
}
else if (distanceMatrix[i][j] < b) {
b = distanceMatrix[i][j];
}
}
}
if (a != -1) {
if (b == -1) {
r = r + a;
}
else {
r = r + a + b;
}
}
}
node->lb = r / 2;
return;
}
r = r + node->sumv * 2;
a = -1;
b = -1;
for (int i = 0; i < n; i++) {
if (node->vis[i] == 0) {
if (a == -1 || distanceMatrix[i][node->st] < a) {
a = distanceMatrix[i][node->st];
}
if (b == -1 || distanceMatrix[i][node->ed] < b) {
b = distanceMatrix[i][node->ed];
}
}
}
r = r + a + b;
for (int i = 0; i < n; i++) {
if (node->vis[i] == 0) {
a = -1;
b = -1;
for (int j = 0; j < n; j++) {
if (i != j) {
if (a == -1 || distanceMatrix[i][j] < a) {
a = distanceMatrix[i][j];
}
else if (b == -1 || distanceMatrix[i][j] < b) {
b = distanceMatrix[i][j];
}
}
}
if (a != -1) {
if (b == -1) {
r = r + a;
}
else {
r = r + a + b;
}
}
}
}
node->lb = r / 2;
}
//打印结点状态的信息
void printNode(struct Node* node) {
printf("Node([");
for (int i = 0; i < node->k - 1; i++) {
printf("%d,", node->path[i]);
}
printf("%d],", node->path[node->k - 1]);
printf(":[%d,%d]\n", node->sumv, node->lb);
}
// 基于分支限界法求解TSP问题
void boundTSP() {
struct Node* head = NULL; // 队列头
struct Node* tail = NULL; // 队列尾
// 初始化搜索状态的队列
head = (struct Node*)malloc(sizeof(struct Node)); // 在队尾插入最初的搜索结点
head->k = 1;
head->ed = startCity;
head->st = startCity;
head->next = NULL;
head->path = (int*)malloc(sizeof(int));
head->path[0] = startCity;
head->sumv = 0;
head->vis = (int*)malloc(sizeof(int) * n);
for (int i = 0; i < n; i++) {
head->vis[i] = 0;
}
head->vis[startCity] = 1;
fillLB(head); // 计算当前搜索状态下的LB的值,并将对应的LB的值,记录在对应指针的lb值中。
tail = head;
// 循环处理队列中的节点
while (head != NULL) {
struct Node* tNode = head;
head = head->next;
if (head == NULL) {
tail = NULL;
}
if (tNode->k == n) {
int tmpSum = tNode->sumv + distanceMatrix[tNode->path[n - 1]][startCity]; // 针对当前的解的路径tNode计算出其TSP路径的总距离
if (tmpSum < sumBest) {
sumBest = tmpSum;
for (int i = 0; i < n; i++) {
pathBest[i] = tNode->path[i];
}
}
free(tNode->path);
free(tNode->vis);
free(tNode);
}
else {
for (int i = 0; i < n; i++) {
if (tNode->vis[i] == 0) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->k = tNode->k + 1;
newNode->ed = i;
newNode->st = tNode->st;
newNode->next = NULL;
newNode->path = (int*)malloc(sizeof(int) * newNode->k);
for (int j = 0; j < tNode->k; j++) {
newNode->path[j] = tNode->path[j];
}
newNode->path[newNode->k - 1] = i;
newNode->sumv = tNode->sumv + distanceMatrix[tNode->ed][i];
newNode->vis = (int*)malloc(sizeof(int) * n);
for (int j = 0; j < n; j++) {
newNode->vis[j] = tNode->vis[j];
}
newNode->vis[i] = 1;
fillLB(newNode);
if (newNode->lb < sumBest) {
if (tail == NULL) {
head = newNode;
tail = newNode;
}
else {
tail->next = newNode;
tail = newNode;
}
}
else {
free(newNode->path);
free(newNode->vis);
free(newNode);
}
}
}
free(tNode->path);
free(tNode->vis);
free(tNode);
}
}
}
// 释放内存
void freeM() {
free(pathBest);
for (int i = 0; i < n; i++) {
free(distanceMatrix[i]);
}
free(distanceMatrix);
}
// 主函数
int main() {
init(); // 初始化数据
greedyTSP(); // 贪心算法求解TSP问题
print(); // 打印结果
boundTSP(); // 基于分支限界法求解TSP问题
print(); // 打印结果
freeM(); // 释放内存
return 0;
}
7、贪心算法
例7-1 贪心法求解活动安排问题
2.什么是活动安排问题?
活动安排问题是贪心算法的经典应用之一。它的场景是给定一组活动,每个活动都有一个开始时间和结束时间,目标是安排出一个最大数量的相互兼容的活动集合,即这些活动之间不会相互冲突。贪心算法在解决活动安排问题时的思路是选择结束时间最早的活动,这样可以腾出更多的时间去安排其他活动。具体步骤如下:
1. 将活动按照结束时间从早到晚进行排序。
2. 选择结束时间最早的活动作为第一个安排的活动。
3. 从剩余的活动中选择开始时间不早于已安排活动的结束时间的活动,并且结束时间最早。
4. 重复步骤3,直到没有可选择的活动为止。贪心算法的关键在于证明这个贪心策略的正确性。对于活动安排问题,可以证明贪心选择最优解。假设存在一个最优解,其中包含了活动A和活动B,而我们的贪心策略没有选择其中一个。通过比较A和B的结束时间,我们可以发现选择结束时间较早的活动,能够为后续的活动安排腾出更多的空间。因此,我们的贪心策略可以得到最优解。
活动安排问题是贪心算法的经典案例,它的时间复杂度为O(nlogn),其中n是活动的数量。这个问题在诸如会议安排、课程表编排等实际场景中有着广泛的应用。
3.为什么要从结束时间开始排序
在活动安排问题中,选择按照结束时间来排序是因为这样的贪心策略能够最大程度地腾出时间空间,以容纳更多的活动。考虑一下,如果我们选择按照开始时间来排序。在这种情况下,我们可能会选择一个很早开始但是持续时间很长的活动,这可能会占用较大的时间段,导致其他活动无法安排进来。
然而,如果我们选择按照结束时间来排序,我们能够优先选择结束时间早的活动,这样能够更快地释放出时间段,为后续的活动提供更多的可用时间。这种策略可以最大化地利用时间资源,以安排尽可能多的活动。
当然,按照结束时间排序并不是在所有情况下都适用的通用策略。有些特定情况下,可能会存在其他的排序策略。但在活动安排问题中,按照结束时间排序是常见且有效的贪心策略,它能够提供一个近似的最优解。
————————————————
原文链接:https://blog.csdn.net/tang7mj/article/details/131031943
正确答案:
2,3 5,6---2,3 3,4 5,7 7,8 8,9
//完成基于贪心算法,安排活动的问题。
#define _CRT_SECURE_NO_WARNINGS 1 // 定义宏,用于关闭安全警告
#include <stdio.h> // 引入标准输入输出库
#include <stdlib.h> // 引入标准库,用于内存分配等操作
// 定义结构体Node,用于存储活动的开始时间和结束时间
typedef struct Node {
int s; // 活动开始时间
int e; // 活动结束时间
} Node;
// 比较函数,用于比较两个Node结构体的结束时间
int compare(const void* a, const void* b) {
Node* nodeA = (Node*)a; // 将指针a转换为Node类型的指针
Node* nodeB = (Node*)b; // 将指针b转换为Node类型的指针
return (nodeA->e - nodeB->e); // 返回两个节点的结束时间的差值
}
// 分区函数,用于快速排序算法中
int partition(Node* arr, int low, int high) {
Node pivot = arr[high]; // 选择最后一个元素作为枢轴
int i = (low - 1); // 初始化i为low-1
for (int j = low; j <= high - 1; j++) { // 遍历数组
if (compare(&arr[j], &pivot) < 0) { // 如果当前元素的结束时间小于枢轴的结束时间
i++; // i加1
Node temp = arr[i]; // 交换arr[i]和arr[j]
arr[i] = arr[j];
arr[j] = temp;
}
}
Node temp = arr[i + 1]; // 交换arr[i+1]和arr[high]
arr[i + 1] = arr[high];
arr[high] = temp;
return (i + 1); // 返回枢轴的位置
}
// 快速排序函数,用于对活动按照结束时间进行排序
void quicksort(Node* arr, int low, int high) {
if (low < high) { // 如果low小于high,说明还有元素需要排序
int pi = partition(arr, low, high); // 获取枢轴的位置
quicksort(arr, low, pi - 1); // 对左半部分进行快速排序
quicksort(arr, pi + 1, high); // 对右半部分进行快速排序
}
}
// 打印数组的函数,用于打印排序后的活动数组
void printArray(Node* arr, int size) {
for (int i = 0; i < size; i++) { // 遍历数组
printf("(%d, %d) ", arr[i].s, arr[i].e); // 打印每个元素的开始时间和结束时间
}
printf("\n"); // 换行
}
// 打印活动数组的函数,用于打印结果
void print(const Node* ns, const int n) {
for (int i = 0; i < n; i++) { // 遍历数组
printf("%d,%d\n", ns[i].s, ns[i].e); // 打印每个元素的开始时间和结束时间
}
}
// 贪婪算法函数,用于选择活动
void greedy(const Node* ns, const int n, Node* r, int* prn) {
int current_time = 0; // 初始化当前时间为0
int index = 0; // 初始化索引为0
for (int i = 0; i < n; i++) { // 遍历数组
if (ns[i].s >= current_time) { // 如果当前活动的开始时间大于等于当前时间
r[index++] = ns[i]; // 将当前活动加入结果数组
current_time = ns[i].e; // 更新当前时间为当前活动的结束时间
}
}
*prn = index; // 将结果数组的长度赋值给prn指向的变量
}
// 主函数
int main() {
int n; // 定义活动个数的变量
scanf("%d", &n); // 输入活动个数
Node* ns = (Node*)malloc(sizeof(Node) * n); // 动态分配内存存储活动
for (int i = 0; i < n; i++) { // 遍历数组
scanf("%d", &ns[i].s); // 输入每个活动的开始时间
scanf("%d", &ns[i].e); // 输入每个活动的结束时间
}
quicksort(ns, 0, n - 1); // 对活动按照结束时间进行排序
Node* r = (Node*)malloc(sizeof(Node) * n); // 动态分配内存存储结果数组
int rn = 0; // 定义结果数组的长度变量
greedy(ns, n, r, &rn); // 调用贪婪算法选择活动
print(r, rn); // 打印结果数组
free(ns); // 释放动态分配的内存
free(r); // 释放动态分配的内存
return 0; // 返回0表示程序正常结束
}
例7-3 贪心法求解最优装载
问题描述
有n个集装箱要装上一艘载重量为W的轮船,其中集装箱i(1≤i≤n)的重量为wi。
不考虑集装箱的体积限制,现要选出尽可能多的集装箱装上轮船,使它们的重量之和不超过W。贪心策略:优先选重量最轻的
正确答案:
最多可以装入的物品数量为:2---最多可以装入的物品数量为:4
问题求解
这里的最优解是选出尽可能多的集装箱个数,并采用贪心法求解。
当重量限制为W时,wi越小可装载的集装箱个数越多,所以采用优先选取重量轻的集装箱装船的贪心思路。
对wi从小到大排序得到{w1,w2,…,wn},设最优解向量为x={x1,x2,…,xn},显然,x1=1,则x’={x2,…,xn}是装载问题w’={w2,…,wn},W’=W-w1的最优解,满足贪心最优子结构性质。
对重量进行排序
然后选择最轻的
#define _CRT_SECURE_NO_WARNINGS 1 // 定义宏,用于关闭安全警告
#include <stdio.h> // 引入标准输入输出库
#include <stdlib.h> // 引入标准库,用于内存分配等操作
// 定义集装箱结构体
typedef struct Item {
int weight; // 重量
} Item;
// 根据重量对集装箱进行排序的比较函数
int compare(const void* a, const void* b) {
Item* itemA = (Item*)a; // 将指针a转换为Item类型的指针
Item* itemB = (Item*)b; // 将指针b转换为Item类型的指针
return itemA->weight - itemB->weight; // 返回两个物品的重量差值
}
int greedyKnapsack(Item items[], int n, int capacity) {
// 首先根据重量对集装箱进行升序排序
qsort(items, n, sizeof(Item), compare);
int count = 0; // 初始化装入物品的数量为0
int currentWeight = 0; // 初始化当前背包的重量为0
for (int i = 0; i < n; i++) {
if (currentWeight + items[i].weight <= capacity) { // 如果当前物品可以装入背包
currentWeight += items[i].weight; // 更新当前背包的重量
count++; // 物品数量加1
}
else {
break; // 如果当前物品无法装入背包,跳出循环
}
}
return count; // 返回成功装入的物品数量
}
int main() {
int capacity; // 定义背包容量变量
scanf("%d", &capacity); // 从标准输入读取背包容量
int n; // 定义集装箱数量变量
scanf("%d", &n); // 从标准输入读取集装箱数量
Item* items = (Item*)malloc(sizeof(Item) * n); // 动态分配内存创建集装箱数组
for (int i = 0; i < n; i++) {
scanf("%d", &items[i].weight); // 逐个读取集装箱的重量
}
// 调用贪心法求解最优装载
int maxCount = greedyKnapsack(items, n, capacity);
// 输出结果
printf("最多可以装入的物品数量为:%d\n", maxCount);
free(items); // 释放动态分配的内存
return 0; // 程序正常结束
}
例? 贪心法求解TSP问题
假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。
贪心思路:每次都选择距离最近的城市(该城市必须没有被选中过。)
#include <stdio.h>
#define n 4 // 定义城市数量为4
int main(void) {
int Dis[n][n], City[n], Sum, i, j, k, l, Dtmp, Found;
City[0] = 0; // 初始化第一个城市为0
Sum = 0; // 初始化总距离为0
// 初始化城市间的距离矩阵
Dis[0][1] = 2; Dis[0][2] = 1; Dis[0][3] = 5; Dis[1][0] = 2; Dis[1][2] = 4;
Dis[1][3] = 4; Dis[2][0] = 1; Dis[2][1] = 4; Dis[2][3] = 6; Dis[3][0] = 5;
Dis[3][1] = 4; Dis[3][2] = 6;
// 遍历剩余的城市
for (i = 1; i < n; i++) {
Dtmp = 10000; // 初始化当前最小距离为一个较大的值
// 遍历所有城市,寻找距离当前城市最近的城市
for (k = 1; k < n; k++) {
Found = 0; // 初始化找到的标志为0
// 检查该城市是否已经访问过
for (l = 0; l < i; l++) {
if (City[l] == k) {
Found = 1; // 如果已经访问过,将标志设为1
break;
}
}
// 如果该城市未访问过且距离小于当前最小距离,更新最小距离和下一个要访问的城市
if (Found == 0 && Dis[City[i - 1]][k] < Dtmp) {
j = k;
Dtmp = Dis[City[i - 1]][k];
}
}
City[i] = j; // 将下一个要访问的城市加入路径
Sum = Sum + Dtmp; // 更新总距离
}
Sum = Sum + Dis[j][0]; // 回到起点的距离
// 输出访问顺序和总距离
for (i = 0; i < n; i++) {
printf("%d\t", City[i]);
}
printf("
");
printf("Sum = %d", Sum);
getchar(); // 等待用户输入
}
8 动态规划
动态规划的基本设计思想
例8-2 数塔问题
例8-3 投资分配问题
题目:
#include<iostream>
#define N 5//项目数
#define M 6//投资总额
using namespace std;
//aItem[3][2]表示共投资2万元第3个项目的投资金额,用于追溯路径
double aItem[N][M] = {0};
//Fsum[3][2]表示前3个项目共投资2万元的最大收益
double Fsum[N][M] = { 0 };
double investMax(double f[N][M]) {
//前1个项目的情况,即边界
for (int i = 0; i <= M-1; i++) {
Fsum[1][i] = f[1][i];
aItem[1][i] = i;
}
//第k个项目
for (int k = 2; k <= N-1; k++) {
//k个项目共分配m万元
for (int m = 1; m <= M-1; m++) {
double max = -1,temp=0;
//第k个项目分配a万元
for (int a = 0; a <= m; a++) {
if (f[k][a] + Fsum[k-1][m - a] > max) {
max = f[k][a] + Fsum[k-1][m - a];
temp = a;
}
}
Fsum[k][m] = max;
aItem[k][m] = temp;
}
}
return Fsum[N-1][M-1];
}
void printInfo() {
int index = M - 1;
for (int i = N - 1; i > 0; i--) {
cout << "第" << i << "个项目,投资" << aItem[i][index] << "万元" << endl;
index -= aItem[i][index];
}
}
int main() {
double f[N][M] = {//f[1][2]表示投第1个项目2万元所产生的效益
{0, 0, 0, 0, 0, 0},//没有第0个项目
{0,11,12,13,14,15},
{0, 0, 5,10,15,20},
{0, 2,10,30,32,40},
{0,20,21,22,23,24}
};
cout << M-1 <<"万元投资"<<N-1<<"项目最大收益为:" << investMax(f) << endl;
printInfo();
return 0;
}
例8-4 背包问题
基于动态规划求解背包问题。
best,1,1,0,1:37---best,1,1,1,0,1,0,0,0,1,0:465
#include <stdio.h>
#include <stdlib.h>
struct Node{
int w;
int v;
int flag;
};//对物品价值和重量的封装
int W;//背包的承重
int n;//物品的数量
struct Node * nodes=NULL;//物品数组
int * best;//载入背包最优解
int maxV;//载入背包最优解的价值
/*
初始化背包问题的输入参数
*/
void init(){
/*
W=5;
n=4;
nodes=(struct Node *)malloc(sizeof(struct Node)*n);
nodes[0].w=2;
nodes[0].v=12;
nodes[1].w=1;
nodes[1].v=10;
nodes[2].w=3;
nodes[2].v=20;
nodes[3].w=2;
nodes[3].v=15;
*/
scanf("%d",&W);
scanf("%d",&n);
nodes=(struct Node *)malloc(sizeof(struct Node)*n);
for(int i=0;i<n;i++){
scanf("%d",&nodes[i].w);
scanf("%d",&nodes[i].v);
}
for(int i=0;i<n;i++){
nodes[i].flag=0;
}
best=(int *)malloc(sizeof(int)*n);
maxV=0;
}
/*
动态规划求解背包问题
*/
int max(int a, int b) {
return (a > b) ? a : b;
}
void da(){
int ** f=(int **)malloc(sizeof(int *)*(n+1));
for(int i=0;i<n+1;i++){
f[i]=(int *)malloc(sizeof(int)*(W+1));
}
for(int i=0;i<=n;i++){
f[i][0]=0;
}
for(int i=0;i<=W;i++){
f[0][i]=0;
}
for(int i=1;i<=n;i++){
for(int j=W;j>=nodes[i-1].w;j--){
f[i][j]=max(f[i-1][j], f[i-1][j-nodes[i-1].w]+nodes[i-1].v);
}
}
maxV=f[n][W];
int i=n;
int j=W;
while(i>=1){
if(f[i][j]==f[i-1][j]){
best[i-1]=0;
i=i-1;
}
else{
best[i-1]=1;
j=j-nodes[i-1].w;
i=i-1;
}
}
}
/*
打印解
*/
void print(){
printf("best");
for(int i=0;i<n;i++){
printf(",%d",best[i]);
}
printf(":%d\n",maxV);
}
int main(){
init();
da();
print();
}