【动态规划】【广度优先搜索】【状态压缩】847 访问所有节点的最短路径

简介: 【动态规划】【广度优先搜索】【状态压缩】847 访问所有节点的最短路径

作者推荐

视频算法专题

本文涉及知识点

动态规划汇总

广度优先搜索 状态压缩

LeetCode847 访问所有节点的最短路径

存在一个由 n 个节点组成的无向连通图,图中的节点按从 0 到 n - 1 编号。

给你一个数组 graph 表示这个图。其中,graph[i] 是一个列表,由所有与节点 i 直接相连的节点组成。

返回能够访问所有节点的最短路径的长度。你可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边。

示例 1:

输入:graph = [[1,2,3],[0],[0],[0]]

输出:4

解释:一种可能的路径为 [1,0,2,0,3]

示例 2:

输入:graph = [[1],[0,2,4],[1,3,4],[2],[1,2]]

输出:4

解释:一种可能的路径为 [0,1,4,2,3]

参数范围

n == graph.length

1 <= n <= 12

0 <= graph[i].length < n

graph[i] 不包含 i

如果 graph[a] 包含 b ,那么 graph[b] 也包含 a

输入的图总是连通图

广度优先搜索

需要记录那些节点已经访问,用状态压缩 (1 << i )表示第i个节点已访问。

还要记录此路径的最后节点。

这两个状态相同,后面的路径则相同。 由于是广度优先搜索,所以路径短的先处理,每个状态只会处理一次。

vDis 记录各状态的最短路径数。

que 记录状态。

时间复杂度:O(n2nn) 枚举起点O(n) 枚举状态数O(2^n) 每个状态处理。

核心代码

class Solution {
public:
  int shortestPathLength(vector<vector<int>>& graph) {
    m_c = graph.size();
    m_iMaskCount = 1 << m_c;
    for (int i = 0; i < m_c; i++)
    {
      BFS(graph, i);
    }
    return m_iRet;
  }
  void BFS(vector<vector<int>>& neiBo,int start)
  {
    vector<vector<int>> vDis(m_c, vector<int>(m_iMaskCount, m_iNotMay));
    queue<pair<int, int>> que;
    auto Add = [&](int node, int iPreMask,int iNew)
    {
      const int iMask = iPreMask | (1 << node);
      if (vDis[node][iMask] <= iNew )
      {
        return ;
      }
      vDis[node][iMask] = iNew;
      que.emplace(node, iMask);
    };
    Add( start,0, 0);
    while (que.size())
    {
      auto [preNode, preMask] = que.front();
      const int iNew = vDis[preNode][preMask]+1;
      que.pop();
      for (const auto& next : neiBo[preNode])
      {
        Add(next, preMask, iNew);
      }
    }
    for (const auto& v : vDis)
    {
      m_iRet = min(m_iRet, v.back());
    }
  }
  const int m_iNotMay = 100'000;
  int m_c, m_iMaskCount;
  int m_iRet = m_iNotMay;
};

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{ 
  vector<vector<int>> graph;
  {
    Solution sln;
    graph = { {1,2,3},{0},{0},{0} };
    auto res = sln.shortestPathLength(graph);
    Assert(res, 4);
  }
  {
    Solution sln;
    graph = { {1},{0,2,4},{1,3,4},{2},{1,2} };
    auto res = sln.shortestPathLength(graph);
    Assert(res, 4);
  }
  
}

动态规划

节点的距离用多源路径的最短距离。

动态规划的状态表示

mask&(1 << next)表示经过了next节点。

vDis[node][mask] 有以下两种含义:

一, 以node结尾,经过mask指定节点的最短路径经过的节点数。

二,以node结尾,且只经过node节点一次,经过mask指定节点的最短路径经过的节点数。

含义二,如果存在,则是含义二,否则是含义一。 必须枚举所有符合含义二的可能。

动态规划的转移方程

vDis[next][maks|next]= MinSelfn e x t = 0 m c − 1 \Large_{next=0}^{m_c-1}next=0mc1vDis[i][mask]+距离(i,next)

vDis[i][mask] 必须合法,且mask不包括next节点

动态规划的填表顺序

mask从1到大,确保动态规划的无后效性。某路径的编码是mask,经过新节点next后,新编码为iNewMask。则iNewMask-mask = 1 << next

1 << next 恒大于0。

动态规划的初始值

全部为不存在的数

动态规划的返回值

Minj = 0 m c − 1 \Large_{j=0}^{m_c-1}j=0mc1vDis[j].back() -1

证明

将最短路径的重复节点删除,保留任意一个。删除后为: i1 \Large_11 i2 \Large_22 …in \Large_nn 。任意ik \Large_kk到ik + 1 \Large_{k+1}k+1的路径一定是最短,否则替换成最短。直接枚举,12! 超时。 用动态规划,共2nn种状态,空间复杂度O(2nn),每种状态转移时间复杂度O(n),故总时间复杂度O(2nnn)。

代码

//多源码路径
template<class T, T INF = 1000 * 1000 * 1000>
class CFloyd
{
public:
  CFloyd(const  vector<vector<T>>& mat)
  {
    m_vMat = mat;
    const int n = mat.size();
    for (int i = 0; i < n; i++)
    {//通过i中转
      for (int i1 = 0; i1 < n; i1++)
      {
        for (int i2 = 0; i2 < n; i2++)
        {
          //此时:m_vMat[i1][i2] 表示通过[0,i)中转的最短距离
          m_vMat[i1][i2] = min(m_vMat[i1][i2], m_vMat[i1][i] + m_vMat[i][i2]);
          //m_vMat[i1][i2] 表示通过[0,i]中转的最短距离
        }
      }
    }
  };
  vector<vector<T>> m_vMat;
};
class Solution {
public:
  int shortestPathLength(vector<vector<int>>& graph) {
    m_c = graph.size();
    m_iMaskCount = 1 << m_c;
    vector<vector<int>> mat(m_c, vector<int>(m_c, 1000 * 1000 * 1000));
    for (int i = 0; i < m_c; i++)
    {
      for (const auto& j : graph[i])
      {
        mat[i][j] = 1;
      }
    }
    CFloyd floyd(mat);
    vector<vector<int>> vDis(m_c, vector<int>(m_iMaskCount, m_iNotMay));
    for (int i = 0; i < m_c; i++)
    { 
      vDis[i][1 << i] = 1;
    }
    for (int mask = 1; mask < m_iMaskCount; mask++)
    {
      for (int i = 0; i < m_c; i++)
      {
        if (vDis[i][mask] >= m_iNotMay)
        {
          continue;
        }
        for (int next = 0 ;next < m_c ;next++ )
        {
          if ((1 << next) & mask)
          {
            continue;//已经访问
          }
          const int iNewMask = (1 << next) | mask;
          vDis[next][iNewMask] = min(vDis[next][iNewMask], vDis[i][mask] + floyd.m_vMat[i][next]);
        }
      }
    }
    int iRet = m_iNotMay;
    for (const auto& v : vDis)
    {
      iRet = min(iRet, v.back());
    }
    return iRet-1;
  }
  const int m_iNotMay = 100'000;
  int m_c, m_iMaskCount;
};

2023年1月

class Solution {

public:

int shortestPathLength(vector<vector>& graph) {

auto Add = [this](int iMask, int iPos, int iOpeNum)

{

if (INT_MAX != m_vMaskPosMinOpe[iMask][iPos])

{

return;

}

m_vQue.emplace_back(iMask, iPos);

m_vMaskPosMinOpe[iMask][iPos] = iOpeNum;

};

m_c = graph.size();

for (int i = 0; i < sizeof(m_vMaskPosMinOpe) / sizeof(m_vMaskPosMinOpe[0]); i++)

{

for (int j = 0; j < sizeof(m_vMaskPosMinOpe[0]) / sizeof(m_vMaskPosMinOpe[0][0]); j++)

{

m_vMaskPosMinOpe[i][j] = INT_MAX;

}

}

for (int i = 0; i < m_c; i++)

{

Add(1 << i, i, 0);

}

for (int i = 0; i < m_vQue.size(); i++)

{

const int iMask = m_vQue[i].first;

const int iPos = m_vQue[i].second;

for (auto& next : graph[iPos])

{

int iNewMask = iMask | (1 << next);

Add(iNewMask, next, m_vMaskPosMinOpe[iMask][iPos] + 1);

}

}

int iMin = INT_MAX;

for (int i = 0; i < sizeof(m_vMaskPosMinOpe[0]) / sizeof(m_vMaskPosMinOpe[0][0]); i++)

{

iMin = min(iMin, m_vMaskPosMinOpe[(1 << m_c) - 1][i]);

}

return iMin;

}

vector<std::pair<int,int>> m_vQue;

int m_vMaskPosMinOpe[1 << 12 ][12];

int m_c;

};

2023年8月

class Solution {

public:

int shortestPathLength(vector<vector>& graph) {

auto Add = [this](int iMask, int iPos, int iOpeNum)

{

if (INT_MAX != m_vMaskPosMinOpe[iMask][iPos])

{

return;

}

m_vQue.emplace_back(iMask, iPos);

m_vMaskPosMinOpe[iMask][iPos] = iOpeNum;

};

m_c = graph.size();

for (int i = 0; i < sizeof(m_vMaskPosMinOpe) / sizeof(m_vMaskPosMinOpe[0]); i++)

{

for (int j = 0; j < sizeof(m_vMaskPosMinOpe[0]) / sizeof(m_vMaskPosMinOpe[0][0]); j++)

{

m_vMaskPosMinOpe[i][j] = INT_MAX;

}

}

for (int i = 0; i < m_c; i++)

{

Add(1 << i, i, 0);

}

for (int i = 0; i < m_vQue.size(); i++)

{

const int iMask = m_vQue[i].first;

const int iPos = m_vQue[i].second;

for (auto& next : graph[iPos])

{

int iNewMask = iMask | (1 << next);

Add(iNewMask, next, m_vMaskPosMinOpe[iMask][iPos] + 1);

}

}

int iMin = INT_MAX;

for (int i = 0; i < sizeof(m_vMaskPosMinOpe[0]) / sizeof(m_vMaskPosMinOpe[0][0]); i++)

{

iMin = min(iMin, m_vMaskPosMinOpe[(1 << m_c) - 1][i]);

}

return iMin;

}

vector<std::pair<int,int>> m_vQue;

int m_vMaskPosMinOpe[1 << 12 ][12];

int m_c;

};


相关文章
|
人工智能 算法 数据可视化
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-2
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-2
|
存储 人工智能 算法
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-1
路径规划最全综述+代码+可视化绘图(Dijkstra算法+A*算法+RRT算法等)-1
|
算法 Python
Python 大神修炼手册:图的深度优先&广度优先遍历,深入骨髓的解析
在 Python 编程中,掌握图的深度优先遍历(DFS)和广度优先遍历(BFS)是进阶的关键。这两种算法不仅理论重要,还能解决实际问题。本文介绍了图的基本概念、邻接表表示方法,并给出了 DFS 和 BFS 的 Python 实现代码示例,帮助读者深入理解并应用这些算法。
301 2
|
算法 数据安全/隐私保护
BFS(Breath First Search 广度优先搜索)
BFS(Breath First Search 广度优先搜索)
262 1
|
JavaScript 应用服务中间件 nginx
Vue打包后Echarts图表不显示问题解决
Vue打包后Echarts图表不显示问题解决
【Python刷题记录】Day1-选择题
整形变量x中存放了一个两位数,要将这个两位数的个位数的个位数字和十位数字交换位置,例如,13变成31,正确的Python表达式是什么?
|
网络协议 缓存 安全
《DNS攻击防范科普系列4》--遭遇DNS缓存投毒该怎么办?
        在《DNS攻击防范科普系列》的前几讲中,我们介绍了常见的DNS攻击、以及防范DDoS攻击、保障操作安全的方法。今天我们给大家带来的是对DNS缓存投毒攻击的防范。首先我们先来说说什么是“DNS缓存投毒”。
|
Web App开发 JavaScript 程序员
浅析Node.js:一个“编码就绪”服务器
导读:Node是一个服务器端JavaScript解释器,它将改变服务器应该如何工作的概念。它的目标是帮助程序员构建高度可伸缩的应用程序,编写能够处理数万条同时连接到一个(只有一个)物理机的连接代码。本文探究了Node.js能解决哪些问题,它如何工作,如何运行一个简单应用程序,最后,Node何时是以及何时不是一个好的解决方案。
1268 0
|
6天前
|
云安全 人工智能 安全
AI被攻击怎么办?
阿里云提供 AI 全栈安全能力,其中对网络攻击的主动识别、智能阻断与快速响应构成其核心防线,依托原生安全防护为客户筑牢免疫屏障。