ダイクストラアルゴリズムについては記事が多すぎて説明しきれないので割愛しますが、あくまで個人的なまとめ兼記録なので、コードのコメントに思考過程を書きます。
743. ネットワーク遅延時間(有向グラフ)
1 から n までのラベルが付けられた n 個のネットワーク ノードがあります。
信号が有向エッジを通過する時間を表すリスト時間を与えます。time[i] = (ui, vi, wi)。ここで、ui はソース ノード、vi はターゲット ノード、wi は信号がソース ノードからターゲット ノードに送信される時間です。
今、あるノード K から信号が送信されます。すべてのノードが信号を受信するまでにどれくらい時間がかかりますか? すべてのノードがシグナルを受信できない場合は、-1 が返されます。
1. 隣接リスト方式
class Solution {
public:
// distances数组 保存:起点k 到其他点的最短距离
// visited数组 保存:某点是否被访问过了,被访问过则设置为true
// graph 二维数组:邻接表
// n : 节点总数
void dijkstra(vector<vector<long long>>& graph,vector<int>& distances,int n,int k,vector<bool>& visited){
distances[k] = 0;// 起始点到起始点自身的距离为0
// 这里只需要循环 n-1次
// 原因:除k点以外只有 n-1个点,每循环一次,就更新k点到一个点的距离
for(int i = 1;i<=n-1;i++){
int id = 0,minDistance = INT_MAX;
for(int j = 1;j<=n;j++){
// 这个循环的目的:去找 距离 k点最近的点
// 原因:可以通过 这个点,去更新 其他点到 k点的距离
if(!visited[j] && distances[j]<minDistance){
minDistance = distances[j];
id = j;
}
}
// 所有点都被访问过了 或者 所有点都不可达
if(id == 0) return;
visited[id] = true;
// 得到一个中间点 id后,比较k->id->某一点的距离 和 k->某一点的距离
// 来更新distances数组,注意,这个数组存的是k到某一点的距离
for(int j = 1;j<=n;j++){
if(!visited[j] && distances[id]+graph[id][j]<distances[j]){
distances[j] = distances[id]+graph[id][j];
}
}
}
}
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
vector<vector<long long>> graph(n+1,vector<long long>(n+1,INT_MAX));
vector<int> distances(n+1,INT_MAX);
vector<bool> visited(n+1,false);
// 建立邻接表
for(auto t:times){
int u = t[0],v = t[1],w = t[2];
graph[u][v] = w;
}
//dijkstra算法
dijkstra(graph,distances,n,k,visited);
int ans = 0;
for(int i = 1;i<=n;i++){
if(distances[i] == INT_MAX) return -1;
ans = max(ans,distances[i]);
}
return ans;
}
};
2. 優先キュー
class Solution {
public:
void dijktra(vector<vector<int>>& times,vector<int>& dis, int n, int k){
dis[k] = 0;
using Pair = pair<int,int>; // first是距离,second是目标点
priority_queue<Pair,vector<Pair>, greater<Pair>> pq;
pq.push({
0,k});
while(!pq.empty()){
auto e = pq.top();
pq.pop();
if(e.first>dis[e.second]) continue;
for(int i = 0;i<times.size();i++){
if(times[i][0] == e.second){
int v = times[i][1];
// 到v点的的距离
int w = e.first+times[i][2];
if(dis[v]==-1 || dis[v]>w){
dis[v] = w;
pq.emplace(w,v);
}
}
}
}
}
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
vector<int> dis(n+1,-1);
dijktra(times,dis,n,k);
int ans = 0;
for(int i = 1;i<=n;i++){
if(dis[i]==-1) return -1;
ans = max(ans,dis[i]);
}
return ans;
}
};
1334. しきい値距離内に隣接する都市が最も少ない都市
n 個の都市があり、0 から n-1 までの番号が付けられます。エッジ配列edgesを与えます。ここで、edges[i] = [fromi, toi,weighti]は、2つの都市fromiとtoiの間の双方向の重み付きエッジを表し、距離のしきい値は整数の distanceThreshold です。
特定のパスを通じて到達できる他の都市の数が最小で、パスの距離が distanceThreshold で最大である都市を返します。このような都市が複数ある場合は、最も大きい番号を持つ都市が返されます。
都市 i と j を結ぶパスの距離は、パスに沿ったすべてのエッジの重みの合計に等しいことに注意してください。
1. 隣接リスト方式
class Solution {
public:
void Dijkstra(vector<vector<long long>>& graph,vector<int>& distances,vector<bool> &visited,int n,int distanceThreshold,int start){
// 自身到自身的距离为0
distances[start] = 0;
for(int i = 0;i<n-1;i++){
int u = -1,minDis = INT_MAX;
// 下面这个这个循环的目的:
// 为了在 和 start 点相连的所有点中找到一个最近的点,再把它设为新的起点
for(int j = 0;j<n;j++){
if(!visited[j] && distances[j] <minDis){
u = j; // 设置新的起点
minDis =distances[j];
}
}
// 如果所有点都访问过了 或者 不可达,直接return 即可
if(u==-1) return;
// 把新的起点设置为访问过
visited[u] = true;
// 接下来更新 start到所有点的 新的 距离
// 为什么有新的距离?
// 回答:因为此时我们有了一个新的点,那么从 start点开始到其他点就不止一种直连路线
// 而是可以借助 刚刚确立好的新的点 ,比如从 start到某点 需要距离为5
// 而start 到 u 的距离为1,u到某点的距离为2,那么就可以更新新的距离了
for(int j = 0;j<n;j++){
// 注意:仍然需要不去管那些已经访问过的节点
//因为已经访问过的节点就已经是最短的距离了
if(!visited[j] && distances[u]+graph[u][j]<distances[j]){
distances[j] = distances[u]+graph[u][j];
}
}
}
}
// 使用Dijkstra算法,邻接矩阵版
int findTheCity(int n, vector<vector<int>>& edges, int distanceThreshold) {
vector<vector<long long>> graph(n,vector<long long>(n,INT_MAX));// 邻接矩阵
for(auto edge:edges){
int u = edge[0],v = edge[1],w = edge[2];
graph[u][v] = graph[v][u] = w;
}
// minCount:在指定的阈值内可以 到达节点 的最少数量
int idx = -1,minCount = INT_MAX;
for(int i = 0;i<n;i++){
vector<int> distances(n,INT_MAX);// 单源最短路径数组
vector<bool> visited(n,false); // 用来记录某一节点是否被访问过
// 调用函数可以得到从i点出发到其他各个点的最短距离
// 这些距离被保存在distances数组中
Dijkstra(graph,distances,visited,n,distanceThreshold,i);
int count = 0; // 小于等于阈值的城市个数
for(int j = 0;j<n;j++){
if(i!=j && distances[j] <= distanceThreshold ){
count++;
}
}
if(count<=minCount){
minCount = count;
// 保存当下这个出发点
idx = i;
}
}
return idx;
}
};
2. 優先キュー
class Solution {
//优先队列版
public:
void Dijkstra(vector<vector<int>>& graph, vector<int>& distances, int n, int distanceThreshold, int start) {
//小顶堆,按照距离dist从小到大排序,pair中first存dist
priority_queue <pair<int, int>,vector<pair<int, int>>, greater<pair<int, int>>> q;
distances[start] = 0;
q.push({
distances[start],start});
while (!q.empty()) {
pair<int, int>p = q.top();
int u = p.second;
q.pop();
if (distances[u] < p.first) {
continue;
}
for (int v=0; v<n; ++v) {
if (graph[u][v] != INT_MAX && distances[v]>distances[u]+graph[u][v]) {
distances[v]=distances[u]+graph[u][v];
q.push({
distances[v],v});
}
}
}
}
int findTheCity(int n, vector<vector<int>>& edges, int distanceThreshold) {
vector<vector<int>>graph(n,vector<int>(n,INT_MAX)); //邻接矩阵
for (vector<int> edge : edges) {
int u=edge[0], v=edge[1], w=edge[2];
graph[u][v] = graph[v][u] = w;
}
int idx = -1, minCount = INT_MAX;
for (int i=0; i<n; ++i) {
vector<int>distances(n,INT_MAX); //单源最短路径数组
Dijkstra(graph, distances, n, distanceThreshold, i);
int count = 0; //小于等于距离阈值的城市个数
for (int j=0; j<n; ++j) {
if (distances[j]<=distanceThreshold && i!=j) {
count++;
}
}
if (count <= minCount) {
minCount = count;
idx = i;
}
}
return idx;
}
};
1514. 最も確率の高いパス
n 個のノード (添字は 0 から始まります) で構成される無向重み付きグラフを作成します。グラフはエッジを記述するリストで構成されます。ここで、edges[i] = [a, b] はノード a と b を接続するリンクを表します。無向エッジであり、このエッジのトラバースが成功する確率は succProb[i] です。
2 つのノードをそれぞれ始点 start と終点 end として指定し、始点から終点までの最も成功確率が高いパスを見つけて、その成功確率を返します。
開始から終了までのパスがない場合は 0 を返します。解答と標準解答との誤差が 1e-5 を超えない限り、正解とみなされます。
この質問で蝶ネクタイ テーブルを使用するとメモリ制限を超えるため、この質問には若干の変更が必要であり、訪問された配列は必要ありません。私の意見では、この質問に対する解決策は、ダイクストラのアルゴリズムを記述する最適な方法です。
class Solution {
public:
struct cmp{
bool operator()(pair<double,int> a,pair<double,int> b)const{
return a.first<b.first;// 升序排列
}
};
double maxProbability(int n, vector<vector<int>>& edges, vector<double>& succProb, int start_node, int end_node) {
vector<vector<pair<double,int>>> graph(n);
for(int i = 0;i<edges.size();i++){
auto &e = edges[i];
graph[e[0]].emplace_back(succProb[i],e[1]);
graph[e[1]].emplace_back(succProb[i],e[0]);
}
priority_queue<pair<double,int>,vector<pair<double,int>>,cmp> que;
vector<double> prob(n,0);
que.emplace(1,start_node);
prob[start_node] = 1;
while(!que.empty()){
auto [pr,node] = que.top();
que.pop();
if(pr<prob[node]) continue;
for(auto &[prNext,nodeNext]:graph[node]){
if(prob[nodeNext]<prNext * prob[node]){
prob[nodeNext] = prNext * prob[node];
que.emplace(prob[nodeNext],nodeNext);
}
}
}
return prob[end_node];
}
};
やっと:
実際、ダイクストラ アルゴリズム (隣接リスト バージョン) は有向グラフと無向グラフで同じように使用され、違いはありません。[0,3,2] の 0 と 3 を入れ替えたら状況は変わるのではないかと考えていましたが、答えはノーです。隣接リストを作成するときに双方向の距離が設定されるため、順序が変更されても別の点から開始しても、距離は同じままであり、訪問された配列は次のとおりであるため、繰り返し訪問することを心配する必要はありません。を区別するために使用されます。