文章目录
- 分治
- 动态规划
- 贪心算法
分治
实现思路可参考:【算法】分治算法
之前写的Java版有思路。
- 二分搜索
#include <iostream>
#include <vector>using namespace std;// 二分搜索函数
int binarySearch(const vector<int>& array, int target) {int left = 0;int right = array.size() - 1;while (left <= right) {int mid = left + (right - left) / 2;// 如果找到目标元素,返回其索引if (array[mid] == target) {return mid;}// 如果目标元素在数组的左半部分,缩小搜索范围到左半部分if (array[mid] > target) {right = mid - 1;}// 如果目标元素在数组的右半部分,缩小搜索范围到右半部分else {left = mid + 1;}}// 如果数组中没有找到目标元素,返回-1表示未找到return -1;
}int main() {vector<int> array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int target = 6;// 调用二分搜索函数int result = binarySearch(array, target);// 输出结果if (result != -1) {cout << "元素 " << target << " 在数组中的索引为 " << result << endl;} else {cout << "未找到元素 " << target << endl;}return 0;
}
- 合并排序
#include <iostream>
#include <vector>using namespace std;// 合并两个有序数组
void merge(vector<int>& array, int left, int mid, int right) {int n1 = mid - left + 1;int n2 = right - mid;// 创建临时数组存储左右两个子数组vector<int> leftArray(n1);vector<int> rightArray(n2);// 复制数据到临时数组 leftArray 和 rightArrayfor (int i = 0; i < n1; i++) {leftArray[i] = array[left + i];}for (int j = 0; j < n2; j++) {rightArray[j] = array[mid + 1 + j];}// 合并左右子数组int i = 0; // 初始化左子数组的索引int j = 0; // 初始化右子数组的索引int k = left; // 初始化合并后的数组的索引while (i < n1 && j < n2) {if (leftArray[i] <= rightArray[j]) {array[k] = leftArray[i];i++;} else {array[k] = rightArray[j];j++;}k++;}// 将左子数组的剩余部分复制到合并后的数组while (i < n1) {array[k] = leftArray[i];i++;k++;}// 将右子数组的剩余部分复制到合并后的数组while (j < n2) {array[k] = rightArray[j];j++;k++;}
}// 归并排序
void mergeSort(vector<int>& array, int left, int right) {if (left < right) {// 找到数组的中间位置int mid = left + (right - left) / 2;// 递归地对左右两部分进行排序mergeSort(array, left, mid);mergeSort(array, mid + 1, right);// 合并已排序的两部分merge(array, left, mid, right);}
}int main() {vector<int> array = {12, 11, 13, 5, 6, 7};cout << "原始数组: ";for (int num : array) {cout << num << " ";}cout << endl;// 调用归并排序函数mergeSort(array, 0, array.size() - 1);cout << "排序后的数组: ";for (int num : array) {cout << num << " ";}cout << endl;return 0;
}
- 快速排序
#include <iostream>
#include <vector>using namespace std;// 交换数组中两个元素的位置
void swap(vector<int>& array, int i, int j) {int temp = array[i];array[i] = array[j];array[j] = temp;
}// 根据选定的基准元素将数组划分为两部分,并返回基准元素的索引
int partition(vector<int>& array, int low, int high) {// 选择最右侧元素作为基准int pivot = array[high];int i = low - 1; // i是小于等于基准元素的子数组的最后一个元素的索引// 遍历数组,将小于等于基准元素的元素放在左侧,大于基准元素的元素放在右侧for (int j = low; j < high; j++) {if (array[j] <= pivot) {i++;swap(array, i, j);}}// 将基准元素放在正确的位置swap(array, i + 1, high);return i + 1;
}// 快速排序的递归函数
void quickSort(vector<int>& array, int low, int high) {if (low < high) {// 划分数组,获取基准元素的索引int pivotIndex = partition(array, low, high);// 递归排序左侧子数组quickSort(array, low, pivotIndex - 1);// 递归排序右侧子数组quickSort(array, pivotIndex + 1, high);}
}int main() {vector<int> array = {12, 4, 5, 6, 7, 3, 1, 15};cout << "Original array: ";for (int num : array) {cout << num << " ";}// 调用快速排序算法quickSort(array, 0, array.size() -1);cout << "\nSorted array: ";for (int num : array) {cout << num << " ";}return 0;
}
- 最接近点对问题
最接近点对问题是一个经典的计算几何问题,目标是找到平面上一组点中距离最近的两个点。分治算法是解决这个问题的一种有效方法。下面是最接近点对问题的分治算法实现思路:
- 按照 x 坐标将点集排序:
将点集按照 x 坐标的顺序进行排序。这可以通过常见的排序算法(如快速排序)实现。 - 分治:
将排序后的点集平均分成两部分,分别递归求解左右两部分的最接近点对。 - 查找跨越中间的最接近点对:
在左右两部分的边界附近,找到距离中间线最近的两个点。这一步的时间复杂度是线性的。 - 合并:
比较左右两部分的最接近点对,选择其中的最小值作为候选。 - 找到最终的最接近点对:
在之前的步骤中,我们找到了两个候选点对,分别来自左右两部分。最终的最接近点对要么在其中一个部分,要么跨越两个部分。 - 计算最终的最接近点对:
在考虑跨越边界的情况时,需要检查距离边界线距离小于当前最小距离的点。如果找到更小的距离,更新最小距离。
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <limits>using namespace std;struct Point {double x, y;
};// 按照 x 坐标升序排序
bool compareX(const Point& a, const Point& b) {return a.x < b.x;
}// 按照 y 坐标升序排序
bool compareY(const Point& a, const Point& b) {return a.y < b.y;
}// 计算两点之间的距离
double distance(const Point& a, const Point& b) {return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
}// 暴力解法,计算所有点对之间的距离,找到最小值
pair<Point, Point> bruteForceClosestPair(const vector<Point>& points, int left, int right) {double minDist = numeric_limits<double>::max();pair<Point, Point> closestPair;for (int i = left; i <= right; ++i) {for (int j = i + 1; j <= right; ++j) {double dist = distance(points[i], points[j]);if (dist < minDist) {minDist = dist;closestPair = {points[i], points[j]};}}}return closestPair;
}// 在给定的点集中找到最接近的点对
pair<Point, Point> closestPairUtil(const vector<Point>& points, int left, int right) {if (right - left <= 3) {// 对于小规模问题,使用暴力解法return bruteForceClosestPair(points, left, right);}int mid = (left + right) / 2;// 分别递归求解左右两边的最近点对pair<Point, Point> leftClosest = closestPairUtil(points, left, mid);pair<Point, Point> rightClosest = closestPairUtil(points, mid + 1, right);// 取左右两边的最近点对中的最小距离double minDist = min(distance(leftClosest.first, leftClosest.second),distance(rightClosest.first, rightClosest.second));// 在距离中间线小于minDist的点集中查找可能更小的距离vector<Point> strip;for (int i = left; i <= right; ++i) {if (abs(points[i].x - points[mid].x) < minDist) {strip.push_back(points[i]);}}// 按照 y 坐标升序排序 stripsort(strip.begin(), strip.end(), compareY);// 在 strip 中查找可能更小的距离for (int i = 0; i < strip.size(); ++i) {for (int j = i + 1; j < strip.size() && (strip[j].y - strip[i].y) < minDist; ++j) {double dist = distance(strip[i], strip[j]);if (dist < minDist) {minDist = dist;leftClosest = {strip[i], strip[j]};}}}// 返回最终的最近点对return leftClosest;
}// 最接近点对算法的入口函数
pair<Point, Point> closestPair(const vector<Point>& points) {// 按照 x 坐标升序排序点集vector<Point> sortedPoints = points;sort(sortedPoints.begin(), sortedPoints.end(), compareX);return closestPairUtil(sortedPoints, 0, sortedPoints.size() - 1);
}int main() {vector<Point> points = {{0, 0}, {1, 1}, {2, 2}, {3, 3}, {5, 5}, {7, 7}, {8, 8}};// 调用最接近点对算法pair<Point, Point> closestPairPoints = closestPair(points);// 输出结果cout << "最接近点对: (" << closestPairPoints.first.x << ", " << closestPairPoints.first.y << ") and ("<< closestPairPoints.second.x << ", " << closestPairPoints.second.y << ")" << endl;return 0;
}
动态规划
实现思路可参考:【算法】动态规划(Dynamic Programming)
- 矩阵连乘
#include<iostream>
using namespace std;#define N 7 //N为7,实际表示有6个矩阵
/*
*矩阵链构造函数:构造m[][]和s[][]
*m中存储的值是计算出来的最小乘法次数,比如m[1][5]就是A1A2A3A4A5的最小乘法次数
*s中存储的是获取最小乘法次数时的断链点,s[1][5]对应的就是如何拆分A1A2A3A4A5,
*比如S[1][5]=3可表示:(A1A2A3)(A4A5),当然内部断链还会继续划分A1A2A3
*/
int MatrixChain(int *p, int n, int m[][N], int s[][N]){for(int i=1;i<=n;i++){ //矩阵链中只有一个矩阵时,次数为0,注意m[0][X]时未使用的m[i][i]=0;}for(int r=2;r <= n;r++){ //矩阵链长度,从长度为2开始for(int i=1;i <= n-r+1;i++){ //根据链长度,控制链最大的可起始点int j = i+(r-1); //矩阵链的末尾矩阵,注意r-1,因为矩阵链为2时,实际是往右+1m[i][j] = m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j]; //先设置最好的划分方法就是直接右边开刀,后续改正,也可合并到下面的for循环中s[i][j]=i;for(int k=i+1;k < j;k++){ //这里面将断链点从i+1开始,可以断链的点直到j-1为止int t = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];if(t<m[i][j]){m[i][j] = t;s[i][j] = k;}}}}return m[1][n];
}
/*
*追踪函数:根据输入的i,j限定需要获取的矩阵链的始末位置,s存储断链点
*/
void Traceback(int i,int j, int s[][N]){if(i==j) //回归条件{cout<<"A"<<i;}else //按照最佳断点一分为二,接着继续递归{cout<<"(";Traceback(i,s[i][j],s);Traceback(s[i][j]+1,j,s);cout<<")";}
}
int main(){int p[N]={30,35,15,5,10,20,25};int m[N][N],s[N][N];int minCost = MatrixChain(p,N-1,m,s);//N-1因为只有六个矩阵Traceback(1,6,s);cout << "最小计算代价为: " << minCost << endl;return 0;
}
- 流水作业调度
流水作业调度问题(Flow Shop Scheduling Problem)是一个经典的调度问题,通常涉及到多个作业(jobs)需要在多台机器上执行。每个作业都有一系列的任务(tasks),并且每个任务需要在不同的机器上执行。流水作业调度的目标是找到一个调度方案,使得完成所有作业的时间最短。
参考:法设计与分析】流水作业调度问题 动态规划算法与代码实现 超详细
#include<iostream>
#include<algorithm>
#include<math.h>
#define NUM 5 //工件总数
#define POWNUM 32 //2^NUM 状态总数using namespace std;//2 5 7 3 6 2 4 7 6 9 8 2
//答案:35//2 5 4 2 3 3 6 1 1 7
//答案:19class JiHe
{
public:int a[NUM]; //1:被加工 0:不被加工int num; //当前状态下 被加工的工件个数int minTime[100]; //数组下标是等待时间t
};class GongJian
{
public:int t1; //该工件在 M1 上加工需要的时间int t2; //该工件在 M2 上加工需要的时间
};GongJian gongjian[NUM];
JiHe jihe[POWNUM];//寻找最小时间的递归函数
int findMinTime(JiHe jihe, int t)
{if (jihe.num == 0) //集合中无元素时 等待时间就是加工时间{return t;}int i;int curMinTime;int mintime = 1000; //初始化巨大值JiHe withoutI = jihe;for (i = 0; i < NUM; i++) //i放在第一个加工 循环找到最小值情况下的i 但是i没有被记录{withoutI = jihe;if (jihe.a[i] == 1){withoutI.a[i] = 0;withoutI.num = jihe.num - 1;curMinTime = gongjian[i].t1 + findMinTime(withoutI, gongjian[i].t2 + max(t - gongjian[i].t1, 0));if (curMinTime < mintime){mintime = curMinTime;}}}return mintime;
}int main()
{//每个工件的时间状况cout << "请输入工件在机器M1 M2上的加工时间:\n";for (int i = 0; i < NUM; i++){cout << "工件序号" << i << "的加工时间\n";cout << "t1 = ";cin >> gongjian[i].t1;cout << "t2 = ";cin >> gongjian[i].t2;}//填写每个工件在与不在的状态 32个int i;int x[NUM];for (int i = 0; i < NUM; i++)//用于二进制计数{x[i] = 1;}int num;int t, p;for (i = 0; i < POWNUM; i++){//二进制计数 罗列所有工件组合for (t = NUM - 1, p = 0; t >= 0; t--, p++){if ((i % (int)pow(2, t)) == 0){x[p] = -x[p];}}/*if (i % 32 == 0)x[0] = -x[0];if (i % 16 == 0)x[1] = -x[1];if (i % 8 == 0)x[2] = -x[2];if (i % 4 == 0)x[3] = -x[3];if (i % 2 == 0)x[4] = -x[4];if (i % 1 == 0)x[5] = -x[5];*/for (t = 0; t < NUM; t++)//把-1改成0{jihe[i].a[t] = (x[t] > 0 ? x[t] : 0);}/*jihe[i].a[0] = (x[0] > 0 ? x[0] : 0);jihe[i].a[1] = (x[1] > 0 ? x[1] : 0);jihe[i].a[2] = (x[2] > 0 ? x[2] : 0);jihe[i].a[3] = (x[3] > 0 ? x[3] : 0);jihe[i].a[4] = (x[4] > 0 ? x[4] : 0);jihe[i].a[5] = (x[5] > 0 ? x[5] : 0);*///填写当前状态下有几个工件被加工 numnum = 0;for (int j = 0; j < NUM; j++){if (jihe[i].a[j] == 1)num++;}jihe[i].num = num;}//自底向上 计算每个状态的最短时间int k;int workNum;for (workNum = 0; workNum <= NUM; workNum++){// 0,1,2,...,workNum 个工件被加工for (i = 0; i < POWNUM; i++){if (jihe[i].num == workNum){//等待时间kfor (k = 0; k < 100; k++){jihe[i].minTime[k] = findMinTime(jihe[i], k);}}}}cout << "最短时间:" << jihe[POWNUM - 1].minTime[0] << endl;system("pause");
}
- 背包问题
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 物品结构体
struct Item {int weight; // 物品重量int value; // 物品价值
};// 动态规划解决0/1背包问题
int knapsack(const vector<Item>& items, int capacity) {int numItems = items.size();// 创建二维数组存储最大价值vector<vector<int>> dp(numItems + 1, vector<int>(capacity + 1, 0));// 填充动态规划表for (int i = 1; i <= numItems; i++) {for (int w = 1; w <= capacity; w++) {if (items[i - 1].weight <= w) {// 当前物品可以放入背包dp[i][w] = max(dp[i - 1][w], items[i - 1].value + dp[i - 1][w - items[i - 1].weight]);} else {// 当前物品不能放入背包dp[i][w] = dp[i - 1][w];}}}// 返回最终的最大价值return dp[numItems][capacity];
}int main() {// 定义一组物品vector<Item> items = {{2, 3}, {3, 4}, {4, 5}, {5, 6}};// 背包容量int capacity = 8;// 调用0/1背包问题算法int maxValue = knapsack(items, capacity);// 输出结果cout << "背包中物品的最大总价值为: " << maxValue << endl;return 0;
}
贪心算法
实现思路可参考:【算法】贪心算法
- 最优装载问题(首次适用)
#include <iostream>
#include <vector>using namespace std;// 最优装载问题的首次适应算法
int firstFit(const vector<int>& items, int binCapacity) {int numBins = 0;vector<int> binSpace; // 每个容器的剩余空间for (int item : items) {bool binFound = false;// 在现有的容器中找到第一个可以装下当前物品的容器for (int i = 0; i < numBins; i++) {if (binSpace[i] >= item) {binSpace[i] -= item;binFound = true;break;}}// 如果没有找到合适的容器,则开启一个新的容器if (!binFound) {binSpace.push_back(binCapacity - item);numBins++;}}return numBins;
}int main() {// 定义一组物品vector<int> items = {4, 8, 1, 4, 2, 1, 8, 5};// 容器的容量int binCapacity = 10;// 调用首次适应算法解决最优装载问题int numBins = firstFit(items, binCapacity);// 输出结果 4cout << "最优装载问题的最小容器数量(首次适应算法)为: " << numBins << endl;return 0;
}
- 单源最短路径
贪心算法解决单源最短路径问题的经典算法是Dijkstra算法。Dijkstra算法通过贪心策略逐步确定从源节点到各个其他节点的最短路径,具体实现思路如下:
- 初始化:
将源节点到自身的距离设为0,将源节点到其他所有节点的距离设为无穷大(或一个较大的数)。 - 选择最短路径节点:
从未确定最短路径的节点中选择一个节点,该节点到源节点的距离最短。一开始选择源节点。 - 更新距离:
对于选定的节点,更新它的邻居节点到源节点的距离。如果通过选定节点到某个邻居节点的路径比当前已知的最短路径更短,则更新邻居节点的距离。 - 标记节点:
将选定的节点标记为已确定最短路径。 - 重复步骤2~4:
重复步骤2~4,直到所有节点都被标记为已确定最短路径或者没有可选的节点。
#include <iostream>
#include <vector>
#include <set>
#include <limits>using namespace std;const int INF = numeric_limits<int>::max();// 图的邻接矩阵表示
using Graph = vector<vector<int>>;// Dijkstra算法实现
void dijkstra(const Graph& graph, int source, vector<int>& distances) {int n = graph.size();set<pair<int, int>> pq; // 优先队列,用于选择当前距离最短的节点distances.resize(n, INF);distances[source] = 0;pq.insert({0, source});while (!pq.empty()) {int u = pq.begin()->second;pq.erase(pq.begin());for (int v = 0; v < n; ++v) {if (graph[u][v] != INF && distances[u] + graph[u][v] < distances[v]) {// 更新距离pq.erase({distances[v], v});distances[v] = distances[u] + graph[u][v];pq.insert({distances[v], v});}}}
}int main() {// 例子:有向图的邻接矩阵表示Graph graph = {{0, 1, 4, INF, INF},{INF, 0, 2, 5, INF},{INF, INF, 0, INF, 1},{INF, INF, INF, 0, 3},{INF, INF, INF, INF, 0}};int source = 0; // 源节点vector<int> distances; // 存储最短路径长度dijkstra(graph, source, distances);// 输出结果cout << "从节点 " << source << " 出发到各节点的最短路径长度:" << endl;for (int i = 0; i < distances.size(); ++i) {cout << "到节点 " << i << " 的最短路径长度为: " << distances[i] << endl;}return 0;
}
graph
是一个有向图的邻接矩阵表示,source 是源节点的编号。dijkstra
函数实现了Dijkstra
算法,其中使用了一个优先队列来选择当前距离最短的节点。最终,distances
向量存储了从源节点到各个节点的最短路径长度。
3. 最小生成树
贪心算法解决最小生成树问题的一个经典算法是Prim算法。Prim算法基于贪心策略,逐步构建最小生成树。以下是Prim算法的实现思路:
- 选择起始节点:
从图中选择一个起始节点作为最小生成树的起点。 - 初始化集合:
将起始节点加入一个集合,表示已经包含在最小生成树中的节点。 - 选择最短边:
从已选择的节点集合中,选择一条连接到未选择节点的最短边。 - 将节点加入集合:
将该边连接的未选择节点加入节点集合。 - 重复步骤3~4:
重复步骤3和4,直到所有节点都包含在最小生成树中。
#include <iostream>
#include <vector>
#include <set>
#include <limits>using namespace std;const int INF = numeric_limits<int>::max();// 图的邻接矩阵表示
using Graph = vector<vector<int>>;// Prim算法实现
void prim(const Graph& graph, int start) {int n = graph.size();// 使用集合存储已选择的节点set<int> selectedNodes;// 初始化起始节点selectedNodes.insert(start);// 重复选择最短边,直到所有节点都被选择while (selectedNodes.size() < n) {int minWeight = INF;int minNode = -1;// 在已选择的节点集合中,找到一条连接到未选择节点的最短边for (int node : selectedNodes) {for (int neighbor = 0; neighbor < n; ++neighbor) {if (graph[node][neighbor] < minWeight && selectedNodes.find(neighbor) == selectedNodes.end()) {minWeight = graph[node][neighbor];minNode = neighbor;}}}// 输出最短边的信息cout << "Edge: (" << minNode << ", " << minWeight << ")" << endl;// 将连接的未选择节点加入集合selectedNodes.insert(minNode);}
}int main() {// 例子:无向图的邻接矩阵表示Graph graph = {{0, 2, INF, 6, INF},{2, 0, 3, 8, 5},{INF, 3, 0, INF, 7},{6, 8, INF, 0, 9},{INF, 5, 7, 9, 0}};int startNode = 0; // 起始节点cout << "最小生成树的边:" << endl;prim(graph, startNode);return 0;
}
graph
是一个无向图的邻接矩阵表示,startNode
是起始节点的编号。prim
函数实现了Prim
算法,它使用一个集合 selectedNodes
来存储已选择的节点。算法通过在已选择的节点中找到一条连接到未选择节点的最短边,将未选择节点加入集合,并输出最短边的信息。最终,算法输出了构建最小生成树的边。