1.图搜索算法 --邻接矩阵 邻接链表型数据
BFS 广度优先搜索
从根节点开始,沿着树的宽度遍历节点,遍历所有节点算法结束
从算法的观点,所有因为展开节点而得到的子节点都会被加进一个先进先出的队列中。
一般实现里,其邻居节点尚未被检测过的节点会被放置在一个被成为open 的容器中(队列或链表)而被检验过的节点则被放置在被成为closed的容器中。
实现方法:1.首先从根节点放入队列中。
2.从队列中取出第一个节点,并检验它是否为目标。* 如果找到目标,则结束搜索并回传结果。 * 否则将它所有尚未检验过的直接子节点加入队列中。
3.若队列为空,表示整张图都检查过了——即图中没有欲搜索的目标。结束搜索并回传“找不到目标”
4.重复步骤2
struct node{
int self;
node *left;
node *right;
};
std::queue<node *> visited, unvisited;
node nodes[9];
node *current;
unvisited.push(&nodes[0]); //root放入 unvisited queue
while (! unvisited.empty()){
current = (unvisited.front());
if (current ->left != NULL)
unvisited.push(current->left);
if (current ->right != NULL)
unvisited.push(current ->right);
visited.push(current);
cout << current ->self <<endl;
unvisited.pop();
}
应用: 查找图中所有连接组件,一个组件是图中最大的相连子图。
查找连接组件中的所有节点
查找非加权图中任两点的最短路径
测试一个图是否是二分图。
DFS深度优先搜索
尽可能深的搜索树的分支,当节点v的所有变都被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程知道发现源节点可达的所有节点为止。这种算法不会根据图的结构等信息调整执行策略。
实现方法:1.首先将根节点放入stack中。
2.从stack中取出第一个节点,并检验它是否为目标。 如果找到目标,则结束搜寻并回传结果。 否则将它某一个尚未检验过的直接子节点加入stack中。
3.重复步骤2
4.如果不存在未检测过的直接子节点。 将上一级节点加入stack中,重复步骤2
5.重复步骤4
6.若stack为空,表示整张图都检查过了——即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。
struct Node{
int self;
Node *left;
Node *right;
};
const int TREE_SIZE = 9;
std::stack<Node *> unvisited;
Node nodes[TREE_SZIE];
Node *current;
//初始化
for (int i = 0; i < TREE_SIZE; i++){
nodes[i].self = i;
int child = i * 2 + 1;
if (child < TREE_SIZE)
nodes[i].left = &nodes[child];
else
nodes[i].left = NULL;
child++;
if (child < TREE_SIZE)
nodes[i].right = &nodes[child];
else
nodes[i].right = NULL;
}
unvisited.push(&nodes[0]);
while(!unvisited.empty()){
current = (unvisited.top());
unvisited.pop();
if (current ->right !=NULL)
unvisited.push(current -> right);
if (current ->left !=NULL)
unvisited.push(current -> left);
cout << current -> self <<endl;
}
分治法
把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
应用:循环赛日程安排问题,快速排序,归并排序, 傅立叶变换
实现方法:循环递归,显堆栈
循环赛日程安排问题
设有n=2k个选手要进行网球循环赛, 要求设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次; (2)每个选手一天只能赛一次。
按此要求,可将比赛日程表设计成一个 n 行n-1列的二维表, 其中,第 i 行第 j 列表示和第 i 个选 手在第 j 天比赛的选手。
#include<iostream>
#include<math.h>
using namespace std;
//循环赛日程安排函数声明
void MatchTable(int k, int n, int **table);
int main()
{
int n = 0, k =0;
cin >>k;
if (cin.fail() || k<0)
{
cout << "k错误" <<endl;
system("pause");
return 0;
}
n = pow(2, k); //计算比赛日程表大小
//分配日程表空间
int **table = new int *[n+1];
for (int i = 0; i <= n; i++)
{
table[i] = new int[n +1];
}
MatchTable(k, n, table);
//显示输出
for(int i = 1; i <= n; i++)
{
for (int j = 1; j <=n; j++)
{
cout << table[i][j] <<"\t";
}
cout << endl;
//释放内存
for (int i =0; i <= n; i++)
delete[] table[i];
delete[] table;
table = NULL;
return 0;
}
// 进行循环赛日程安排,生成日程表
void MatchTable(int k, int n, int **table)
{
//设置日程表 第一行的值
for (int i = 1; i <= n; i++)
table[1][i] = i;
int begin = 1; //每次填充起始位置
//用分支法分separate份,循环求解
for (int separate = 1; separate <= k; separate++)
{
//日程表进行划分
n /= 2;
//flag为每一小份的列标记
for (int i = begin + 1; i <= 2*begin; i++)
{
//操作行
for (int j = begin + 1; i <= 2 * begin; i++)
{
//操作列
for (int j = begin + 1; j <= 2 * begin; j++)
{
//把左上角的值赋给右下角
table[i][j + (flag - 1) * begin * 2] = table[i - begin][j + (flag - 1) * begin * 2 - begin];
//把右上角的值赋给左下角
table[i][j + (flag - 1) * begin * 2 - begin] = table[i - begin][j + (flag - 1) * begin *2];
}
}
}
//进入日程表的下一个划分进行填充
begin *= 2;
}
}
动态规划
适用于有重叠子问题和最优子结构性质的问题,所耗时间往往少于朴素解法。
背包问题:
有n件物品和一个容量为V的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品不超过背包总量,且其价值总和最大。
可用方法:迭代法——通过遍历n行W列,迭代每行每列的值,并把最优解放到n行(在数组中为第n+1行)W列(在数组中为W+1列)中;递归法——通过每次返回前i个物品和承重为j的最优解,递归计算总背包问题的最优解。
#include<iostream>
#include<algorithm>
using namespace std;
int **T = NULL;
int max(int a, int b){
return (a > b) ? a : b;
}
//迭代法
int packIterative(int n, int W, int *w, int *v){
//循环遍历n行
for (int i = 1; i <= n; ++i)
{
//循环遍历W列
for (int j = 1; j <= W; ++j)
{
//第i个物品能装下,则比较包括第i个物品和不包括第i个物品,取其最大值
if (w[i] <= j)
T[i][j] = max(v[i] + T[i - 1][j - w[i]], T[i - 1][j]);
else
T[i][j] = T[i - 1][j];
}
}
return T[n][W];
}
//递归法,不支持显示背包问题的表格
int packRecursive(int n, int W, int *w, int *v){
//结束条件(初始条件),i或者j为0时最大总价值为0
if(n == 0 || W == 0) {
return 0;
}
//第i个物品不能装下,则递归装i-1个
if (w[n] > W) {
return packRecursive(n - 1, W, w, v);
}
//第i个物品能装下,则比较包括第i个物品和不包括第i个物品,取其最大值
else{
return max(v[n] + packRecursive(n - 1, W -w[n], w, v), packRecursive(n - 1, W, w, v));
}
}
贪心算法
在每一步都采取当前最好或最优的选择,从而希望结果是最好的算法
应用:求图中的最小生成树,哈夫曼编码
棋盘覆盖问题 分治法
在一个2^k * 2 ^k个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为特殊方格。棋盘覆盖问题就是要用图示的4种不同形态的L型骨牌覆盖给定棋盘上除特殊方格之外的所有方格,且任何2个L型骨牌不得重叠覆盖。
#include <iostream>
#include <math.h>
#include <cctype>
using namespace std;
int num_Now = 0;
int **board = NULL;
void ChessBoard(int num_BoardTopleftRow, int num_BoardTopLeftColumn, int num_SpecialRow, int num_SpecialColumn, int boardSize);
int main() {
int num_BoardTopLeftRow = 0,
num_BoardTopLeftColumn = 0,
num_SpecialRow = 0,
num_SpecialColumn = 0,
boardSize = 0,
k = 0,
ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn, num_SpecialRow, num_SpecialColumn, boardSize);
}
//棋盘覆盖函数
void ChessBoard(int num_BoardTopLeftRow, int num_BoardTopLeftColumn, int num_SpecialRow, int num_SpecialColumn, int boardSize)
{
//棋盘大小为1则直接返回
if (boardSize == 1) return;
int num = ++num_Now,
size = boardSize / 2;
if (num_SpeciaRow < num_BoardTopLeftRow + size && num_SpecialColumn < num_BoardTopLeftColumn + size)
{
//递归覆盖含有特殊方格的子棋盘
ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn, num_SpecialRow, num_SpecialColumn, size);
}
else
{
// 用编号为num的L型骨牌覆盖右下角
board[num_BoardTopLeftRow + size - 1][num_BoardTopLeftColumn + size - 1] = num;
// 递归覆盖其余棋盘
ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn, num_BoardTopLeftRow + size - 1, num_BoardTopLeftColumn + size - 1, size);
}
// 覆盖右上角子棋盘
if (num_SpecialRow < num_BoardTopLeftRow + size && num_SpecialColumn >= num_BoardTopLeftColumn + size)
{
// 递归覆盖含有特殊方格的子棋盘
ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn + size, num_SpecialRow, num_SpecialColumn, size);
}
else
{
// 用编号为num的L型骨牌覆盖左下角
board[num_BoardTopLeftRow + size - 1][num_BoardTopLeftColumn + size] = num;
// 递归覆盖其余棋盘
ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn + size, num_BoardTopLeftRow + size - 1, num_BoardTopLeftColumn + size, size);
}
// 覆盖左下角子棋盘
if (num_SpecialRow >= num_BoardTopLeftRow + size && num_SpecialColumn < num_BoardTopLeftColumn + size)
{
// 递归覆盖含有特殊方格的子棋盘
ChessBoard(num_BoardTopLeftRow + size, num_BoardTopLeftColumn, num_SpecialRow, num_SpecialColumn, size);
}
else
{
// 用编号为num的L型骨牌覆盖右上角
board[num_BoardTopLeftRow + size][num_BoardTopLeftColumn + size - 1] = num;
// 递归覆盖其余棋盘
ChessBoard(num_BoardTopLeftRow + size, num_BoardTopLeftColumn, num_BoardTopLeftRow + size, num_BoardTopLeftColumn + size - 1, size);
}
// 覆盖右下角子棋盘
if (num_SpecialRow >= num_BoardTopLeftRow + size && num_SpecialColumn >= num_BoardTopLeftColumn + size)
{
// 递归覆盖含有特殊方格的子棋盘
ChessBoard(num_BoardTopLeftRow + size, num_BoardTopLeftColumn + size, num_SpecialRow, num_SpecialColumn, size);
}
else
{
// 用编号为num的L型骨牌覆盖左上角
board[num_BoardTopLeftRow + size][num_BoardTopLeftColumn + size] = num;
// 递归覆盖其余棋盘
ChessBoard(num_BoardTopLeftRow + size, num_BoardTopLeftColumn + size, num_BoardTopLeftRow + size, num_BoardTopLeftColumn + size, size);
}
冯诺依曼邻居问题
某算法从一个1×1的方格开始,每次都会在上次图形的周围再加上一圈方格,在第n次的时候要生成多少个方格
#include <stdio.h>
//通项法
int Neumann2_3_12(int n){
return 2 * n*n + 2 * n + 1;
//若设第n次生成的方格数是a(n),则:a(1) = a(0) + 4 * 1 a(2) = a(1) + 4 * 2 a(3) = a(2) + 4 * 3 ... a(n) = a(n-1) + 4 * n
//化简可得:a(n) - a(1) = 4 * (n + (n-1) + ... + 2 )
//即:a(n) = 2 * n*n + 2 * n + 1
}
//递推法
int Neuman2_4_12(int n){
//0次有一个方格
if (n == 0) return 1;
return Neuman2_4_12(n - 1) + 4 * n;
//若设第n次生成的方格数是a(n),则:a(1) = a(0) + 4 * 1 a(2) = a(1) + 4 * 2 a(3) = a(2) + 4 * 3 ... a(n) = a(n-1) + 4 * n
//则可得:a(n) = a(n - 1) + 4 * n
}
输油管道问题
管道要穿过一个有n 口油井的油田。 从每口油井都要有一条输油管道沿最短路经(或南或北)与主管道相连。 如果给定n口油井的位置,即它们的x 坐标(东西向)和y 坐标(南北向), 应如何确定主管道的最优位置, 即使各油井到主管道之间的输油管道长度总和最小的位置?
#include <iostream>
using namespace std;
//油井y坐标指针
float * y = NULL;
//快速排序
void quick_sort(int low, int high)
{
if (low >= high)
return;
int first = low;
int last = high;
float key = y[first];
while (first < last)
{
//将比第一个小的移到前面
while (first < last && y[last] >= key)
last--;
if (first < last)
y[first++] = y[first];
}
// 基准位置
y[first] = key;
// 前半递归
quick_sort(low, first - 1);
// 后半递归
quick_sort(first + 1, high);
}
int main()
{
int n;
float mid;
float minDistance = 0;
cin >> n;
quick_sort(0,n - 1);
mid = y[n / 2];
for (auto i = 0; i < n; i++)
{
minDistance += abs(y[i] - mid);
}
// 判断油井奇偶,做不同的输出
if (n & 1)
{
// n为奇数,则最优位置为y数组的第n/2个油井的y坐标
cout << "主管道的最优位置为:y = " << mid << endl;
}
else
{
// n为偶数,则最优位置为y数组的中间两个油井的y坐标的区间
cout << "主管道的最优位置为:y = [" << y[n / 2 - 1] << "," << mid << "]" << endl;
}
return 0;
}