'circular linked list builder' 的最快算法
Fastest algorithm for a 'circular linked list builder'
我有两个问题:
1) 将此列表按 "connecting" 顺序排序的最快算法是什么?
2) 这是现有的 problem/algorithm,它的名称是什么?
节点始终以循环方式连接,因此我的起始 ID 无关紧要。
鉴于此列表:
id node1 node2
A 4 9
B 6 1
C 1 3
D 3 10
E 7 2
F 4 2
G 9 8
H 10 5
I 7 5
J 6 8
node1 和 node2 没有特定的顺序,所以 id A 可以是 4 - 9 以及 9 - 4。
输出应该是这样的(A开头无所谓,只要输出是链即可)
node ids: 4 - 9 - 8 - 6 - 1 - 3 - 10 - 5 - 7 - 2 - 4
ids: A G J B C D H I E F
(我正在用 C# 编写代码。但是任何语言的伪代码都可以)
我相信您正在寻找 Eulerian path
我不知道这是否是一些命名的数学问题。这是伪代码,它允许您以 O(N) 方式(复杂性和内存使用)解决问题。
1) 创建数组(我们假设节点在[0..N-1]范围内具有唯一ID。并用节点填充它(具有id的节点应放在id位置)
2) 选择任何节点并将其推送到单独的列表中(它将包含 "circular" 顺序的节点)。该列表中的最后一个节点将被命名为已处理节点。
3) 从1迭代到N -1 每一步选择未遍历的已处理节点的邻居。将未遍历的节点推入循环链表。继续进程
注意:检查 "not traversed" 属性 可以以 O(1) 的方式执行。请看,它已经存在于循环列表中的位置。它应该是最后一个(处理过的)节点的邻居
主要缺点 - 这种算法的假设是存在唯一的欧拉路径。
这里建议使用字典(哈希table)进行计算。
我已将问题中提供的 sheet 的一行命名为 "cell"(但我们不知道您的数据结构)。
似乎是 O(n),因为字典提供 O(1) 检索。
所有这些都假定初始数据与问题一致(至少据我所知)。
代码在 C# 中并已注释。如果评论不够解释,请告诉我。
class Program
{
class Cell
{
public string Id { get; set; }
public int Node1 { get; set; }
public int Node2 { get; set; }
public int Min { get { return Math.Min( Node1, Node2 ); } }
public Cell( string name, int node1, int node2 )
{
Id = name;
Node1 = node1;
Node2 = node2;
}
public override string ToString()
{
return Id + "(" + Node1.ToString() + "," + Node2.ToString() + ")";
}
}
static void Main( string[] args )
{
var A = new Cell( "A", 4, 9 );
var B = new Cell( "B", 6, 1 );
var C = new Cell( "C", 1, 3 );
var D = new Cell( "D", 3, 10 );
var E = new Cell( "E", 7, 2 );
var F = new Cell( "F", 4, 2 );
var G = new Cell( "G", 9, 8 );
var H = new Cell( "H", 10, 5 );
var I = new Cell( "I", 7, 5 );
var J = new Cell( "J", 6, 8 );
var cells = new List<Cell>() { A, B, C, D, E, F, G, H, I, J };
// A dictionary to store the cells corresponding to each node values
Dictionary<int, List<Cell>> dict = new Dictionary<int, List<Cell>>();
// Add all the cells to the dictionary
foreach ( var cell in cells )
AddCell( dict, cell );
// Start with arbitrary first cell and remove it from the dictionary
var currentCell = GetCell( dict, A.Node1 );
RemoveCell( dict, currentCell );
// The result is a list of cells initialized with the first cell
var result = new List<Cell>() { currentCell };
// Calculation loop
bool doContinue = true;
while ( doContinue )
{
// Tries to get a next cell from the node1 of current cell...
var nextCell = GetCell( dict, currentCell.Node1 );
// ... or if not found, tries to get it from node2
if ( nextCell == null )
nextCell = GetCell( dict, currentCell.Node2 );
if ( nextCell == null )
// If not found, we stop
{
doContinue = false;
}
else
// If found
{
// Add the next cell to the result
result.Add( nextCell );
// Remove the cell
RemoveCell( dict, nextCell );
}
// The next cell becomes the current cell
currentCell = nextCell;
}
}
// Adding a cell puts the cell against its two nodes values
static void AddCell( Dictionary<int, List<Cell>> dict, Cell cell )
{
List<Cell> cells = null;
if ( dict.TryGetValue( cell.Node1, out cells ) == false )
{
cells = new List<Cell>();
dict[cell.Node1] = cells;
}
cells.Add( cell );
if ( dict.TryGetValue( cell.Node2, out cells ) == false )
{
cells = new List<Cell>();
dict[cell.Node2] = cells;
}
cells.Add( cell );
}
// Gets a cell from a node value
static Cell GetCell( Dictionary<int, List<Cell>> dict, int node )
{
var cell = null as Cell;
var cells = dict[node];
if ( cells.Count > 0 )
cell = cells.First();
return cell;
}
// Removes a cell from the dictionary for both node1 and node2 entries.
static void RemoveCell( Dictionary<int, List<Cell>> dict, Cell cell )
{
dict[cell.Node1].Remove( cell );
dict[cell.Node2].Remove( cell );
}
}
起点是列表或数组,如:
1 {A, 4, 9}
2 {B, 6, 1}
3 {C, 1, 3}
4 {D, 3,10}
5 {E, 7, 2}
6 {F, 4, 2}
7 {G, 9, 8}
8 {H,10, 5}
9 {I, 7, 5}
10 {J, 6, 8}
如果我们可以将其更改为这样的列表或数组:
1 {C, 1, 3}
2 {F, 2, 4} (nodes swapped)
3 {D, 3,10}
4 {A, 4, 9}
5 {I, 5, 7} (nodes swapped)
6 {B, 6, 1}
7 {E, 7, 2}
8 {J, 8, 6} (nodes swapped)
9 {G, 9, 8}
10 {H,10, 5}
是按照node1排序的,那么我们可以把这个链表或者数组读成链表:
start with item 1: {C, 1, 3}
read node2: 3
skip to item 3: {D, 3,10}
read node2: 10
skip to item 10: {H,10, 5}
...
skip to item 6: {B, 6, 1}
read node2: 1
end of list
result: C D H I E F A G J B
可以通过交换列表中的项目或将项目复制到新列表(如果您有 space)来创建列表的第二个版本 in-place。
唯一需要注意的是,有时您可能需要交换两个节点。当 re-ordering in-place 时,这可能是这样的:
look at item 1: {A, 4, 9}
if item 4 has a node1 different from 4, swap item 1 and 4
else, change item 1 to {A, 9, 4} and swap item 1 and 9
(-> item 1 and 4 swapped)
while current item is already in-place, skip to next
(-> stay at item 1)
look at new item 1: {D, 3,10}
if item 3 has a node1 different from 3, swap item 1 and 3
else, change item 1 to {D,10, 3} and swap item 1 and 10
(-> item 1 and 3 swapped)
while current item is already in-place, skip to next
(-> item 1 is now {C, 1, 3}, so skip to item 2)
...
创建新列表或数组时,这应该更简单:
look at item 1: {A, 4, 9}
if new list has no item 4, copy item 1 as item 4 to the new list
else, change item 1 to {A, 9, 4} and copy as item 9 to the new list
move to next item
如您所见,无需多次遍历列表;每个项目都交换或复制一次,其目的地由其节点 1 或节点 2 确定。
更新
我刚刚意识到订购商品的步骤数可能比上面描述的要多(很多)。如果例如您首先将 {A,4,8} 移动到位置 4 并将 {B,5,9} 移动到位置 5,然后遇到 {C,4,5},您被卡住了。然后,您必须将 {C,4,5} 与其他两项中的一项交换,交换另一项中的节点,然后将其移动到位。那个新位置本身可能已经被占用,依此类推,因此无法知道这两个选项中哪个是较小的邪恶。在最坏的情况下,交换次数将接近 N2。
更新 2
上面提到的问题当然可以通过将项存储为双向链表来解决。当你遇到例如{A,4,8},您将 {A,8} 存储为第 4 项,将 {A,4} 存储为第 8 项,然后对于 {B,5,9},您将 {B,9} 存储为第 5 项,{B ,5} 作为第 9 项,然后对于 {C,4,5},添加到已存储的项中,以便第 4 项变为 {A,8,C,5},第 5 项变为 {B,9,C ,4}。然后,您可以双向遍历双向链表。这将增加需要完成的工作和使用的space,但它仍然与列表中的项目数成线性关系。
您可以通过以下方式进行:
static IEnumerable<Edge<T>> OrderEdges<T>(this IEnumerable<Edge<T>> edges)
where T : IEquatable<T>
{
Debug.Assert(edges.Any());
var map = new Dictionary<T, Edge<T>>();
foreach (var e in edges)
{
if (map.ContainsKey(e.Node1))
{
Debug.Assert(!map.ContainsKey(e.Node2));
map.Add(e.Node2, e);
}
else
{
map.Add(e.Node1, e);
}
}
var current = edges.First();
var orderedEdges = new HashSet<Edge<T>>();
while (true)
{
orderedEdges.Add(current);
yield return current;
if (orderedEdges.Count == map.Count)
break;
var next = map[current.Node2];
current = orderedEdges.Contains(next) ? map[current.Node1] : next;
}
}
其中 Edge<T>
class 就是:
class Edge<T> where T: IEquatable<T>
{
public T Node1 { get; }
public T Node2 { get; }
public string Name { get; }
public Edge(string name, T node1, T node2)
{
Name = name;
Node1 = node1;
Node2 = node2;
}
public override string ToString() => Name;
}
如果我们运行这个小家伙:
var edges = new List<Edge<int>>() {
new Edge<int>("A", 4, 9), new Edge<int>("B", 6, 1),
new Edge<int>("C", 1, 3), new Edge<int>("D", 3, 10),
new Edge<int>("E", 7, 2), new Edge<int>("F", 4, 2),
new Edge<int>("G", 9, 8), new Edge<int>("H", 10, 5),
new Edge<int>("I", 7, 5), new Edge<int>("J", 6, 8) };
Console.WriteLine(string.Join(" -> ", edges.OrderEdges()));
我们得到了预期的结果:
A -> G -> J -> B -> C -> D -> H -> I -> E -> F
请注意,此解决方案假定输入数据格式正确。
我有两个问题:
1) 将此列表按 "connecting" 顺序排序的最快算法是什么?
2) 这是现有的 problem/algorithm,它的名称是什么?
节点始终以循环方式连接,因此我的起始 ID 无关紧要。
鉴于此列表:
id node1 node2
A 4 9
B 6 1
C 1 3
D 3 10
E 7 2
F 4 2
G 9 8
H 10 5
I 7 5
J 6 8
node1 和 node2 没有特定的顺序,所以 id A 可以是 4 - 9 以及 9 - 4。
输出应该是这样的(A开头无所谓,只要输出是链即可)
node ids: 4 - 9 - 8 - 6 - 1 - 3 - 10 - 5 - 7 - 2 - 4
ids: A G J B C D H I E F
(我正在用 C# 编写代码。但是任何语言的伪代码都可以)
我相信您正在寻找 Eulerian path
我不知道这是否是一些命名的数学问题。这是伪代码,它允许您以 O(N) 方式(复杂性和内存使用)解决问题。
1) 创建数组(我们假设节点在[0..N-1]范围内具有唯一ID。并用节点填充它(具有id的节点应放在id位置)
2) 选择任何节点并将其推送到单独的列表中(它将包含 "circular" 顺序的节点)。该列表中的最后一个节点将被命名为已处理节点。
3) 从1迭代到N -1 每一步选择未遍历的已处理节点的邻居。将未遍历的节点推入循环链表。继续进程
注意:检查 "not traversed" 属性 可以以 O(1) 的方式执行。请看,它已经存在于循环列表中的位置。它应该是最后一个(处理过的)节点的邻居
主要缺点 - 这种算法的假设是存在唯一的欧拉路径。
这里建议使用字典(哈希table)进行计算。
我已将问题中提供的 sheet 的一行命名为 "cell"(但我们不知道您的数据结构)。
似乎是 O(n),因为字典提供 O(1) 检索。
所有这些都假定初始数据与问题一致(至少据我所知)。
代码在 C# 中并已注释。如果评论不够解释,请告诉我。
class Program
{
class Cell
{
public string Id { get; set; }
public int Node1 { get; set; }
public int Node2 { get; set; }
public int Min { get { return Math.Min( Node1, Node2 ); } }
public Cell( string name, int node1, int node2 )
{
Id = name;
Node1 = node1;
Node2 = node2;
}
public override string ToString()
{
return Id + "(" + Node1.ToString() + "," + Node2.ToString() + ")";
}
}
static void Main( string[] args )
{
var A = new Cell( "A", 4, 9 );
var B = new Cell( "B", 6, 1 );
var C = new Cell( "C", 1, 3 );
var D = new Cell( "D", 3, 10 );
var E = new Cell( "E", 7, 2 );
var F = new Cell( "F", 4, 2 );
var G = new Cell( "G", 9, 8 );
var H = new Cell( "H", 10, 5 );
var I = new Cell( "I", 7, 5 );
var J = new Cell( "J", 6, 8 );
var cells = new List<Cell>() { A, B, C, D, E, F, G, H, I, J };
// A dictionary to store the cells corresponding to each node values
Dictionary<int, List<Cell>> dict = new Dictionary<int, List<Cell>>();
// Add all the cells to the dictionary
foreach ( var cell in cells )
AddCell( dict, cell );
// Start with arbitrary first cell and remove it from the dictionary
var currentCell = GetCell( dict, A.Node1 );
RemoveCell( dict, currentCell );
// The result is a list of cells initialized with the first cell
var result = new List<Cell>() { currentCell };
// Calculation loop
bool doContinue = true;
while ( doContinue )
{
// Tries to get a next cell from the node1 of current cell...
var nextCell = GetCell( dict, currentCell.Node1 );
// ... or if not found, tries to get it from node2
if ( nextCell == null )
nextCell = GetCell( dict, currentCell.Node2 );
if ( nextCell == null )
// If not found, we stop
{
doContinue = false;
}
else
// If found
{
// Add the next cell to the result
result.Add( nextCell );
// Remove the cell
RemoveCell( dict, nextCell );
}
// The next cell becomes the current cell
currentCell = nextCell;
}
}
// Adding a cell puts the cell against its two nodes values
static void AddCell( Dictionary<int, List<Cell>> dict, Cell cell )
{
List<Cell> cells = null;
if ( dict.TryGetValue( cell.Node1, out cells ) == false )
{
cells = new List<Cell>();
dict[cell.Node1] = cells;
}
cells.Add( cell );
if ( dict.TryGetValue( cell.Node2, out cells ) == false )
{
cells = new List<Cell>();
dict[cell.Node2] = cells;
}
cells.Add( cell );
}
// Gets a cell from a node value
static Cell GetCell( Dictionary<int, List<Cell>> dict, int node )
{
var cell = null as Cell;
var cells = dict[node];
if ( cells.Count > 0 )
cell = cells.First();
return cell;
}
// Removes a cell from the dictionary for both node1 and node2 entries.
static void RemoveCell( Dictionary<int, List<Cell>> dict, Cell cell )
{
dict[cell.Node1].Remove( cell );
dict[cell.Node2].Remove( cell );
}
}
起点是列表或数组,如:
1 {A, 4, 9}
2 {B, 6, 1}
3 {C, 1, 3}
4 {D, 3,10}
5 {E, 7, 2}
6 {F, 4, 2}
7 {G, 9, 8}
8 {H,10, 5}
9 {I, 7, 5}
10 {J, 6, 8}
如果我们可以将其更改为这样的列表或数组:
1 {C, 1, 3}
2 {F, 2, 4} (nodes swapped)
3 {D, 3,10}
4 {A, 4, 9}
5 {I, 5, 7} (nodes swapped)
6 {B, 6, 1}
7 {E, 7, 2}
8 {J, 8, 6} (nodes swapped)
9 {G, 9, 8}
10 {H,10, 5}
是按照node1排序的,那么我们可以把这个链表或者数组读成链表:
start with item 1: {C, 1, 3}
read node2: 3
skip to item 3: {D, 3,10}
read node2: 10
skip to item 10: {H,10, 5}
...
skip to item 6: {B, 6, 1}
read node2: 1
end of list
result: C D H I E F A G J B
可以通过交换列表中的项目或将项目复制到新列表(如果您有 space)来创建列表的第二个版本 in-place。
唯一需要注意的是,有时您可能需要交换两个节点。当 re-ordering in-place 时,这可能是这样的:
look at item 1: {A, 4, 9}
if item 4 has a node1 different from 4, swap item 1 and 4
else, change item 1 to {A, 9, 4} and swap item 1 and 9
(-> item 1 and 4 swapped)
while current item is already in-place, skip to next
(-> stay at item 1)
look at new item 1: {D, 3,10}
if item 3 has a node1 different from 3, swap item 1 and 3
else, change item 1 to {D,10, 3} and swap item 1 and 10
(-> item 1 and 3 swapped)
while current item is already in-place, skip to next
(-> item 1 is now {C, 1, 3}, so skip to item 2)
...
创建新列表或数组时,这应该更简单:
look at item 1: {A, 4, 9}
if new list has no item 4, copy item 1 as item 4 to the new list
else, change item 1 to {A, 9, 4} and copy as item 9 to the new list
move to next item
如您所见,无需多次遍历列表;每个项目都交换或复制一次,其目的地由其节点 1 或节点 2 确定。
更新
我刚刚意识到订购商品的步骤数可能比上面描述的要多(很多)。如果例如您首先将 {A,4,8} 移动到位置 4 并将 {B,5,9} 移动到位置 5,然后遇到 {C,4,5},您被卡住了。然后,您必须将 {C,4,5} 与其他两项中的一项交换,交换另一项中的节点,然后将其移动到位。那个新位置本身可能已经被占用,依此类推,因此无法知道这两个选项中哪个是较小的邪恶。在最坏的情况下,交换次数将接近 N2。
更新 2
上面提到的问题当然可以通过将项存储为双向链表来解决。当你遇到例如{A,4,8},您将 {A,8} 存储为第 4 项,将 {A,4} 存储为第 8 项,然后对于 {B,5,9},您将 {B,9} 存储为第 5 项,{B ,5} 作为第 9 项,然后对于 {C,4,5},添加到已存储的项中,以便第 4 项变为 {A,8,C,5},第 5 项变为 {B,9,C ,4}。然后,您可以双向遍历双向链表。这将增加需要完成的工作和使用的space,但它仍然与列表中的项目数成线性关系。
您可以通过以下方式进行:
static IEnumerable<Edge<T>> OrderEdges<T>(this IEnumerable<Edge<T>> edges)
where T : IEquatable<T>
{
Debug.Assert(edges.Any());
var map = new Dictionary<T, Edge<T>>();
foreach (var e in edges)
{
if (map.ContainsKey(e.Node1))
{
Debug.Assert(!map.ContainsKey(e.Node2));
map.Add(e.Node2, e);
}
else
{
map.Add(e.Node1, e);
}
}
var current = edges.First();
var orderedEdges = new HashSet<Edge<T>>();
while (true)
{
orderedEdges.Add(current);
yield return current;
if (orderedEdges.Count == map.Count)
break;
var next = map[current.Node2];
current = orderedEdges.Contains(next) ? map[current.Node1] : next;
}
}
其中 Edge<T>
class 就是:
class Edge<T> where T: IEquatable<T>
{
public T Node1 { get; }
public T Node2 { get; }
public string Name { get; }
public Edge(string name, T node1, T node2)
{
Name = name;
Node1 = node1;
Node2 = node2;
}
public override string ToString() => Name;
}
如果我们运行这个小家伙:
var edges = new List<Edge<int>>() {
new Edge<int>("A", 4, 9), new Edge<int>("B", 6, 1),
new Edge<int>("C", 1, 3), new Edge<int>("D", 3, 10),
new Edge<int>("E", 7, 2), new Edge<int>("F", 4, 2),
new Edge<int>("G", 9, 8), new Edge<int>("H", 10, 5),
new Edge<int>("I", 7, 5), new Edge<int>("J", 6, 8) };
Console.WriteLine(string.Join(" -> ", edges.OrderEdges()));
我们得到了预期的结果:
A -> G -> J -> B -> C -> D -> H -> I -> E -> F
请注意,此解决方案假定输入数据格式正确。