分治
实现思路可参考:【算法】分治算法
之前写的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 和 rightArray
for (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 坐标升序排序 strip
sort(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时,实际是往右+1
m[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
//答案:19
class 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);*/
//填写当前状态下有几个工件被加工 num
num = 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)
{
//等待时间k
for (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<algorithm>
using namespace std;
int main() {
int n;//定义集装箱个数
double c;//定义船的最大承载量
cout << "输入船的最大承载量以及集装箱个数" << endl;
cin >> c >> n;
cout << "输入每件集装箱的重量,用空格分开" << endl;
double * w =new double[n];//用数组填装集装箱的个数
for (int i = 0; i < n; i++) {
cin >> w[i];
}
//冒泡排序
sort(w, w+n);
double temp = 0.0;//中间值(相当于一个集装箱装进去,然后将集装箱装到船上)
int count = 0;//计数器
for (int i = 0; i < n; i++) {
if (temp + w[i] <= c) {
temp += w[i];
count++;
}
else {
break;
}
}
cout << "最多可装下" << count << endl;
return 0;
}
- 单源最短路径
贪心算法解决单源最短路径问题的经典算法是Dijkstra算法。Dijkstra算法通过贪心策略逐步确定从源节点到各个其他节点的最短路径,具体实现思路如下:
按各个顶点与源点之间路径长度的递增次序,生成源点到各个顶点的最短路径的方法,即先求出长度最短的一条路径,再参照它求出长度次短的一条路径,以此类推,直到源点到其他各个顶点的最短路径全部求出为止。(Dijkstra算法思想)
假定源点u,顶点集合V被划分为两部分,集合S和V-S,其中S中的顶点到源点的最短路径的长度已经确定,集合V-S中所包含的顶点到源点的最短路径的长度待定,称从源点出发,只进过S中的点到达V-S中的点的路径为特殊路径.
#include<iostream>
#include<algorithm>
using namespace std;
#define N 5
#define M 9999999
void Dijkstra(int n, int v, int dist[], int p[], int c[][N+1])
{
bool s[N+1];//顶点集合S
for(int i = 1; i <= n; i++){
dist[i] = c[v][i];//从源点到顶点i的最短特殊路径长度
s[i] = false;
if(dist[i] == M){
p[i] = 0; // 从源点到顶点i的最短路径上千一个顶点
}
else
{
p[i] = v;
}
}
dist[v] = 0;
s[v] = true;
for(int i = 1; i < n; i++){
int temp = M;
int u = v;//上一顶点
//找到具有最短特殊路径长度的顶点u
for(int j = 1; j <= n; j++){
if((!s[j]) && (dist[j] < temp))
{
u = j;
temp = dist[j];
}
}
s[u] = true;
//更新dist值
for(int j = 1; j <= n; j++){
if((!s[j]) && c[u][j] < M)
{
int newdist = dist[u] + c[u][j];
if(newdist < dist[j]){
dist[j] = newdist;
p[j] = u;
}
}
}
}
}
//输出最短路径v源点,i终点
void Traceback(int v, int i, int p[])
{
//源点等于终点,即找出全部路径
if(v == i){
cout << i;
return;
}
Traceback(v, p[i], p);
cout << "->" << i;
}
int main(){
int v = 1;//源点 为1
int dist[N+1];//从源点到顶点i的最短特殊路径长度
int p[N+1]; //从源点到顶点i的最短路径上其哪一个顶点
//带权有向图的邻接矩阵,行和列从下标从1开始
int c[N+1][N+1];
//输入:如果输入-1,代表两个点不是邻接点,初始化为M(代表无穷大)
cout << "带权有向图的邻接矩阵为:" << endl;
for(int i = 1; i <= N; i++){
for(int j = 1; j <= N; j++){
cin >> c[i][j];
if(c[i][j] == -1){
c[i][j] = M;
}
}
}
//寻找最短路径
Dijkstra(N, v, dist, p, c);
//输出
cout << "源点1到源点5的最短路径长度为:" << dist[5] << endl;
cout << "路径为:" << endl;
Traceback(1, 5, p);
return 0;
}
- 最小生成树
贪心算法解决最小生成树问题的一个经典算法是Prim算法。Prim算法基于贪心策略,逐步构建最小生成树。以下是Prim算法的实现思路:
// 【贪心算法】最小生成树问题 - Prim算法
#include <iostream>
using namespace std;
#define M 9999 // maxint,inf,大数
const int N = 6;// 无向连通带权图的 顶点数
void Prim(int n, int c[][N + 1])
{
// 顶点j属于V-S,closest[j]是顶点j在S中的 最邻接顶点
int closest[N + 1]; // j的 最邻接顶点
// 顶点j属于V-S,顶点j到closest[j]和S中的其它邻接顶点k相比
// c[j][closest[j]] <= c[j][k],即最小权值
int lowcost[N + 1]; // 最小权值,c[j][closest[j]]
bool s[N + 1]; // 顶点集合S
s[1] = true; // 初始S={1}
//初始化
for (int i = 2; i <= n; i++)
{
closest[i] = 1;
lowcost[i] = c[1][i];
s[i] = false;
}
for (int i = 1; i < n; i++)
{
int min = M;
int j = 1;
// 找出V-S中使权值最小的顶点j
for (int k = 2; k <= n; k++)
{
if ((lowcost[k] < min) && (!s[k]))
{
min = lowcost[k];
j = k;
}
}
// 找到符合贪心选择方式的边,将顶点j加入到集合S
cout << closest[j] << "--" << j << endl;
s[j] = true;
// 找到一条边后,更新数组closest和lowcost
for (int k = 2; k <= n; k++)
{
if ((c[j][k] < lowcost[k] && (!s[k])))
{
lowcost[k] = c[j][k];
closest[k] = j;
}
}
}
}
int main()
{
// 无向连通带权图的邻接矩阵,行和列下标从1开始
int c[N + 1][N + 1] = {
{M, M, M, M, M, M, M},
{M, M, 6, 1, 5, M, M},
{M, 6, M, 5, M, 3, M},
{M, 1, 5, M, 5, 6, 4},
{M, 5, M, 5, M, M, 2},
{M, M, 3, 6, M, M, 6},
{M, M, M, 4, 2, 6, M},
};
cout << "无向连通带权图的矩阵为:\n";
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= N; j++)
cout << c[i][j] << "\t";
cout << endl;
}
cout << "\nPrim算法最小生成树选边次序:" << endl;
Prim(N, c);
return 0;
}