Data structures 迪克斯特拉';s最短路径优化算法

Data structures 迪克斯特拉';s最短路径优化算法,data-structures,dijkstra,Data Structures,Dijkstra,首先,我想说我的代码按照预期工作,并且速度相当快。然而,大部分时间都花在一个非常具体的部分上,这让我不禁要问:有没有普遍接受的更好的解决方案 以下是我的实现: var cellDistance = new double[cells.Count]; cellDistance.SetAll(idx => idx == startCellIndex ? 0 : double.PositiveInfinity); var v

首先,我想说我的代码按照预期工作,并且速度相当快。然而,大部分时间都花在一个非常具体的部分上,这让我不禁要问:有没有普遍接受的更好的解决方案

以下是我的实现:

            var cellDistance = new double[cells.Count];
            cellDistance.SetAll(idx => idx == startCellIndex ? 0 : double.PositiveInfinity);

            var visitedCells = new HashSet<int>();

            do
            {
                // current cell is the smallest unvisited tentative distance cell 
                var currentCell = cells[cellDistance.Select((d, idx) => (d, idx)).OrderBy(x => x.d).First(x => !visitedCells.Contains(cells[x.idx].Index)).idx];

                foreach (var neighbourCell in currentCell.Neighbours)
                    if (!visitedCells.Contains(neighbourCell.Index))
                    {
                        var distanceThroughCurrentCell = cellDistance[currentCell.Index] + neighbourCell.Value;
                        if (cellDistance[neighbourCell.Index] > distanceThroughCurrentCell)
                        {
                            cellDistance[neighbourCell.Index] = distanceThroughCurrentCell;
                            prevCell[neighbourCell] = currentCell;
                        }
                    }

                visitedCells.Add(currentCell.Index);
            } while (visitedCells.Count != cells.Count && !visitedCells.Contains(endCell.Index));
更具体地说,在最后的lambda中,不是那种(我发现非常令人惊讶):

由于
visitedCells
已经是一个
HashSet
,因此我的问题是:是否有一种不同的存储部分开销的方法使这个特定查询(即,部分开销最低的未访问节点)明显更快


我在考虑某种排序字典,但我需要一个按值排序的字典,因为如果它按键排序,我必须将部分成本设置为键,这使得更新它的成本很高,然后提出了如何将此结构映射到成本数组的问题,这仍然不能解决我的
访问单元格的查找问题。

使用一个标志数组而不是HashSet

HashSet可以有摊销的插入时间和预期的查询时间O(1)。然而,由于节点ID只是数组中的索引,它们是连续的,并且不会增长太多。此外,您最终将拥有哈希集中的所有ID。在这种情况下,O(1)选项比使用“任意”通用哈希表更快。您可以使用一个布尔数组来显示节点是否被访问,并使用节点ID对其进行索引

只需分配一个大小等于节点计数的布尔数组。将其填入
false
。访问新节点时,将节点id处的值设置为
true

迭代所有节点,而不是对它们进行排序以选择下一个节点

您当前的代码必须根据距离对所有节点进行排序,然后逐个遍历以找到第一个未访问的节点。由于排序的原因,在大多数情况下需要θ(nlogn)时间。(可以进行优化以对节点进行部分排序,但如果编译器/库能够自己看到这个机会,那将非常令人惊讶。)使用这种方法,您的总时间复杂度将变为θ(n^2*logn)。相反,您可以遍历节点一次,跟踪迄今为止未访问的节点的最小距离。这在θ(n)中起作用。总的时间复杂度是O(n^2),Dijkstra应该是这样

有了这两个更改,您的代码就不会有Dijkstra最短路径所不需要的东西了


我在考虑某种分类词典,但我需要它 一个按值排序的,因为如果按键排序,我必须 将部分成本作为关键,这使得更新成本很高,然后 提出了如何将此结构映射到成本数组的问题

有一种称为min heap的数据结构,可用于从集合(及其卫星数据)中提取最小值。一个简单的二进制最小堆可以在θ(logn)最坏情况下提取最小密钥或减少它所持有的一些密钥

在Dijkstra的例子中,您需要有一个稀疏图,这样比遍历所有距离(稀疏图)更有效≈ 边的数量远小于节点的数量(平方)。因为算法可能需要在每次松弛边缘时减少距离

如果存在θ(n^2)边,则最坏情况下的总时间复杂度为θ(n^2*logn)

如果存在θ(n^2/logn)边,则松弛所花费的时间变为O(n^2)。然后,您需要一个比这个更稀疏的图,以便二进制堆比使用简单数组更有效


在最坏的情况下,从堆中提取所有最小距离节点需要θ(nlogn)时间,松弛所有边需要θ(e*logn)时间,其中e是边计数,总时间是θ((n+e)logn)。正如我所说的,只有当e渐近地小于n^2/logn时,这才能比θ(n^2)更有效。

使用一个标志数组而不是HashSet

HashSet可以有摊销的插入时间和预期的查询时间O(1)。然而,由于节点ID只是数组中的索引,它们是连续的,并且不会增长太多。此外,您最终将拥有哈希集中的所有ID。在这种情况下,O(1)选项比使用“任意”通用哈希表更快。您可以使用一个布尔数组来显示节点是否被访问,并使用节点ID对其进行索引

只需分配一个大小等于节点计数的布尔数组。将其填入
false
。访问新节点时,将节点id处的值设置为
true

迭代所有节点,而不是对它们进行排序以选择下一个节点

您当前的代码必须根据距离对所有节点进行排序,然后逐个遍历以找到第一个未访问的节点。由于排序的原因,在大多数情况下需要θ(nlogn)时间。(可以进行优化以对节点进行部分排序,但如果编译器/库能够自己看到这个机会,那将非常令人惊讶。)使用这种方法,您的总时间复杂度将变为θ(n^2*logn)。相反,您可以遍历节点一次,跟踪迄今为止未访问的节点的最小距离。这在θ(n)中起作用。总的时间复杂度是O(n^2),Dijkstra应该是这样

有了这两个更改,您的代码就不会有Dijkstra最短路径所不需要的东西了


我在考虑某种分类词典,但我需要它 一个按值排序的,因为如果按键排序,我必须 将部分成本作为关键,这使得更新成本很高,然后 提出了如何将此结构映射到成本数组的问题

有一种称为min heap的数据结构,可用于从集合(及其卫星数据)中提取最小值。一个简单的二进制最小堆可以提取最小密钥o
var currentCell = cells[cellDistance.Select((d, idx) => (d, idx)).OrderBy(x => x.d).First(x => !visitedCells.Contains(cells[x.idx].Index)).idx];
x => !visitedCells.Contains(cells[x.idx].Index)