目录
正文
1. 深度优先搜索(DFS - Depth First Search)
1.1 原理
深度优先搜索是一种用于遍历或搜索图(包括树结构,树是一种特殊的图)的算法。它从起始节点开始,沿着一条路径尽可能深地探索下去,直到无法继续或者达到目标节点,然后回溯到上一个未完全探索的节点,继续探索其他分支。简单来说,就是优先往深度方向挖掘,类似走迷宫时一直沿着一条路走到头再回头找其他路的感觉。
1.2 实现方式(以递归形式在树结构上为例)
以下是用 C++ 实现的对二叉树进行深度优先搜索(先序遍历)的代码示例,二叉树节点结构体定义如下:
#include <iostream>
using namespace std;
// 二叉树节点结构体
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
// 先序遍历(深度优先搜索的一种体现形式)
void preorderTraversal(TreeNode* root) {
if (root == NULL) {
return;
}
cout << root->val << " "; // 访问根节点
preorderTraversal(root->left); // 递归遍历左子树
preorderTraversal(root->right); // 递归遍历右子树
}
int main() {
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
cout << "先序遍历结果: ";
preorderTraversal(root);
cout << endl;
return 0;
}
1.3 应用场景
- 用于遍历各种树形结构,如二叉树、多叉树等,计算树的高度、统计节点个数等。
- 在图的连通性判断、寻找路径等问题中也有广泛应用,例如判断一个无向图中是否存在从起点到某个特定节点的路径。
2. 广度优先搜索(BFS - Breadth First Search)
2.1 原理
广度优先搜索同样用于遍历或搜索图。它从起始节点开始,先访问起始节点的所有邻接节点,再依次访问这些邻接节点的邻接节点,一层一层地向外扩展,就像水波一样,以起始点为中心向外一圈一圈地扩散,直到找到目标节点或者遍历完整个图。
2.2 实现方式(以队列辅助实现,在图结构上为例)
下面是用 C++ 实现的对一个简单无向图进行广度优先搜索的代码示例,这里使用邻接表来表示图(假设图中节点编号从 0 开始):
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// 进行广度优先搜索
void bfs(vector<vector<int>>& graph, int start) {
int n = graph.size();
vector<bool> visited(n, false); // 标记节点是否被访问过
queue<int> q;
visited[start] = true;
q.push(start);
while (!q.empty()) {
int cur = q.front();
q.pop();
cout << cur << " "; // 访问当前节点
for (int neighbor : graph[cur]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
q.push(neighbor);
}
}
}
}
int main() {
// 构建一个简单的无向图示例(邻接表表示)
vector<vector<int>> graph = {
{1, 2},
{0, 3},
{0, 3},
{1, 2}
};
int start = 0;
cout << "广度优先搜索结果: ";
bfs(graph, start);
cout << endl;
return 0;
}
2.3 应用场景
- 求图中两点之间的最短路径(无权图情况下),因为它是按照距离起始点由近到远的顺序来遍历节点的。
- 查找图中所有与起始节点连通的节点,判断图的连通性等。
3. 二分搜索(Binary Search)
3.1 原理
二分搜索适用于有序数组(或有序序列)。它每次通过比较中间元素与目标元素的大小,将搜索区间缩小一半。如果中间元素等于目标元素,则找到目标;如果中间元素大于目标元素,则在左半区间继续搜索;如果中间元素小于目标元素,则在右半区间继续搜索,不断重复这个过程直到找到目标或者确定目标不存在。
3.2 实现方式
前面在算法基础篇中已经介绍过二分搜索的代码示例,这里再强调下它的基本代码框架(在升序排列的整数数组中查找目标元素):
#include <iostream>
#include <vector>
using namespace std;
int binarySearch(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 表示没找到
}
int main() {
vector<int> nums = {1, 3, 5, 7, 9};
int target = 5;
int index = binarySearch(nums, target);
if (index!= -1) {
cout << "找到目标元素,下标为: " << index << endl;
} else {
cout << "未找到目标元素" << endl;
}
return 0;
}
3.3 应用场景
- 在有序的数据集合中快速查找特定元素,例如在一个已排序的学生成绩列表中查找某个学生的成绩对应的排名等。
- 可用于解决一些满足单调性的最值问题,通过二分答案的思路,不断缩小范围来确定符合条件的最优值。
4. A搜索算法
4.1 原理
A搜索算法是一种启发式搜索算法,它结合了广度优先搜索和贪心算法的思想。在搜索过程中,它通过一个评估函数 f(n)
来决定下一个要扩展的节点,评估函数一般定义为 f(n) = g(n) + h(n)
,其中 g(n)
表示从起始节点到当前节点 n
的实际代价(已经走过的路径代价),h(n)
表示从当前节点 n
到目标节点的预估代价(启发式函数给出的估计值)。通过这个评估函数,A算法优先选择那些看起来更有可能通向目标且代价较小的节点进行扩展,从而朝着目标方向更高效地搜索。
4.2 实现方式(以下是一个简单的伪代码示例,实际应用中要根据具体问题定义节点、状态、启发式函数等)
初始化:
创建两个列表,open列表(存放待扩展的节点)和closed列表(存放已扩展的节点);
将起始节点放入open列表,设置起始节点的g值为0,h值(根据启发式函数计算),f值为g + h;
循环:
while open列表不为空:
从open列表中取出f值最小的节点,记为当前节点;
如果当前节点是目标节点,返回找到的路径;
将当前节点放入closed列表;
遍历当前节点的所有邻接节点:
对于每个邻接节点:
如果邻接节点在closed列表中,跳过;
计算从起始节点经过当前节点到邻接节点的新g值;
如果邻接节点不在open列表中:
设置邻接节点的g值为新g值,计算h值,f值为g + h,将邻接节点放入open列表,并记录邻接节点的父节点为当前节点;
else if 新g值小于邻接节点当前的g值:
更新邻接节点的g值为新g值,相应更新f值,更新邻接节点的父节点为当前节点;
如果open列表为空,说明没有找到目标节点,返回失败;
4.3 应用场景
- 在游戏开发中,常用于角色寻路,比如在复杂的游戏地图里让角色找到从当前位置到目标位置的最优路径(综合考虑距离、障碍物等因素)。
- 在路径规划领域,例如机器人在未知环境中寻找从起始点到目标点的可行且较优的行走路线等情况。
5. 迭代加深搜索(Iterative Deepening Search)
5.1 原理
迭代加深搜索结合了深度优先搜索和广度优先搜索的优点。它先进行深度限制为 1 的深度优先搜索,如果没有找到目标,就增加深度限制为 2 再进行深度优先搜索,以此类推,不断增加深度限制进行深度优先搜索,直到找到目标或者达到最大允许深度。这样既避免了深度优先搜索可能陷入无限深的路径而无法找到最优解的问题,又能在一定程度上利用深度优先搜索占用空间相对较小(不需要像广度优先搜索那样存储大量同层节点信息)的优势。
5.2 实现方式(以下以在树结构上搜索目标节点为例,给出简单的代码框架,假设树节点结构体与前面深度优先搜索中的类似)
#include <iostream>
#include <vector>
using namespace std;
// 树节点结构体(这里省略具体定义,可参考前面深度优先搜索部分)
// 迭代加深搜索核心函数
bool iterativeDeepeningSearch(TreeNode* root, int target) {
for (int depth = 1; depth < INT_MAX; depth++) { // 不断增加深度限制
vector<bool> visited; // 标记每层搜索时节点是否被访问过
if (dfsWithDepthLimit(root, target, depth, visited)) {
return true; // 找到目标,返回true
}
}
return false; // 达到最大允许深度仍未找到,返回false
}
// 带有深度限制的深度优先搜索辅助函数
bool dfsWithDepthLimit(TreeNode* root, int target, int depthLimit, vector<bool>& visited) {
if (root == NULL || depthLimit == 0) {
return false;
}
if (root->val == target) {
return true;
}
visited.push_back(true);
bool foundInLeft = dfsWithDepthLimit(root->left, target, depthLimit - 1, visited);
bool foundInRight = dfsWithDepthLimit(root->left, target, depthLimit - 1, visited);
visited.pop_back();
return foundInLeft || foundInRight;
}
int main() {
// 构建树(这里省略具体构建代码,参考前面深度优先搜索部分构建树示例)
TreeNode* root =...;
int target = 5;
if (iterativeDeepeningSearch(root, target)) {
cout << "找到目标节点" << endl;
} else {
cout << "未找到目标节点" << endl;
}
return 0;
}
5.3 应用场景
- 常用于解决一些搜索空间较大且深度不确定,但又需要保证能找到最优解(或者较优解)的问题,比如在国际象棋等棋类游戏中,在一定时间限制内寻找最佳的落子策略等,因为不知道最佳策略在多深的搜索层次中,采用迭代加深搜索可以逐步探索合适的深度范围。
6. 双向搜索(Bidirectional Search)
6.1 原理
双向搜索是同时从起始节点和目标节点两个方向进行搜索。可以想象为两队人分别从起点和终点出发,朝着对方的方向去寻找,当两队人相遇时,也就找到了从起点到终点的路径,这样往往能比只从一个方向搜索更快地找到目标,尤其在搜索空间较大的情况下,它能有效减少搜索的节点数量,提高搜索效率。
6.2 实现方式(以下以在图结构上搜索为例,简单示意代码框架,假设图用邻接表表示等相关设定与前面类似)
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
using namespace std;
// 从起始方向进行搜索(类似广度优先搜索)
vector<int> forwardSearch(vector<vector<int>>& graph, int start) {
int n = graph.size();
vector<bool> visitedFromStart(n, false);
queue<int> q;
visitedFromStart[start] = true;
q.push(start);
vector<int> parentFromStart(n, -1);
while (!q.empty()) {
int cur = q.front();
q.pop();
for (int neighbor : graph[cur]) {
if (!visitedFromStart[neighbor]) {
visitedFromStart[neighbor] = true;
parentFromStart[neighbor] = cur;
q.push(neighbor);
}
}
}
return parentFromStart;
}
// 从目标方向进行搜索(类似广度优先搜索)
vector<int> backwardSearch(vector<vector<int>>& graph, int target) {
int n = graph.size();
vector<bool> visitedFromTarget(n, false);
queue<int> q;
visitedFromTarget[target] = true;
q.push(target);
vector<int> parentFromTarget(n, -1);
while (!q.empty()) {
int cur = q.front();
q.pop();
for (int neighbor : graph[cur]) {
if (!visitedFromTarget[neighbor]) {
visitedFromTarget[neighbor] = true;
parentFromTarget[neighbor] = cur;
q.push(neighbor);
}
}
}
return parentFromTarget;
}
// 双向搜索主函数,尝试找到从起始到目标的路径
vector<int> bidirectionalSearch(vector<vector<int>>& graph, int start, int target) {
vector<int> parentFromStart = forwardSearch(graph, start);
vector<int> parentFromTarget = backwardSearch(graph, target);
for (int i = 0; i < graph.size(); i++) {
if (parentFromStart[i]!= -1 && parentFromTarget[i]!= -1) {
vector<int> path;
int node = i;
while (node!= -1) {
path.push_back(node);
node = parentFromStart[node];
}
reverse(path.begin(), path.end());
node = i;
while (node!= -1) {
path.push_back(node);
node = parentFromTarget[node];
}
return path;
}
}
return {}; // 表示未找到路径
}
int main() {
// 构建一个图示例(邻接表表示,省略具体构建代码,参考前面相关示例)
vector<vector<int>> graph =...;
int start = 0;
int target = 4;
vector<int> path = bidirectionalSearch(graph, start, target);
if (path.size() > 0) {
cout << "找到从起始到目标的路径: ";
for (int node : path) {
cout << node << " ";
}
cout << endl;
} else {
cout << "未找到从起始到目标的路径" << endl;
}
return 0;
}
6.3 应用场景
- 在地图导航中,如果要寻找两个远距离地点之间的路径,双向搜索可以加快路径查找速度,从出发地和目的地同时探索,减少总的搜索范围。
- 在一些大型网络拓扑结构中查找两点之间的连接路径等情况,能有效提高搜索效率,节省搜索时间和资源。
结语
感谢您的阅读!期待您的一键三连!欢迎指正!