Repository: networkx/networkx

Issue:
Prufer schemes: quadratic instead of linear complexity
A tree with set vertices `range(n)` can be encoded by a sequence of `n - 2` integer in `range(n)` known as the tree Prufer sequence and conservely, any such sequence can be decoded into a unique tree. 

The NetworkX module `/networkx/algorithms/tree/coding.py` implements the encoding and decoding schemes by mean of resp. the `to_prufer_sequence` function and `from_prufer_sequence` function. The docstring for each function references a 2009 paper describing linear time algorithms (and not n*log(n) as the docstring claims cf. §2 and §3 in the paper). 

The problem is that performances of the implemented algorithms are unsatisfactory and the time complexity seems to be more quadratic than linear. It results for instance that decoding a Prufer sequence of lenght about 20000 requires 32s.


On the other hand, based on another paper, I implemented Prufer encoding and decoding functions and the previous Prufer sequence need only 0.01s to be decoded: the NetworkX implementation has a time execution problem.


To be more accurate, I have tested the NetworkX decoding function successively on 

- data input of size 2000
- on data input of size 10 x 2000=20000


and time execution raises from 0.32s seconds to about 31.83s: this is a strong indication that the algorithm has quadratic complexity. Here is the test code :



```python
from time import clock
from random import randrange
import networkx as nx

def test_speed(n):
    sequence=[randrange(3) for i in range(n-2)] 
    d=clock()
    G=nx.from_prufer_sequence(sequence)
    print("%d: %.2fs" %(n, clock()-d))    
    
test_speed(2000)
test_speed(20000) 
```

```
2000: 0.32s
20000: 31.83s
```



I suspect that the following part of the source code has not linear complexity, cf. the last line:


```python
# from NetworkX sources
def from_prufer_sequence(sequence):

    # ...    
    # code snipped
    # ...
 
    for v in sequence:
        # ...
        # code snipped
        # ...   
        # the min function executes a loop
        # so we have 2 nested loops of size n (sometimes)
        
        index = u = min(k for k in range(index + 1, n) if degree[k] == 1)
```



The `to_prufer_sequence` function has more or less the same problem. 



Discussion:
Makes sense to me; you could probably use `heapq` instead of calling `min` repeatedly (as suggested in the referenced paper, I believe).
A heap is likely to solve the slowdown.  You will get an algorithm with n*log(n) complexity. Since you have worked on the paper upon which the algorithm is based, it's more appropriate you set the patch. Or, if you prefer, I can create a pull request implementing a linear algorithm and based on a [different paper](https://pdfs.semanticscholar.org/f6a7/dffda435d1f732d220312fb667e1ba4d41a5.pdf).
It is fairly easy to implement a linear algorithm and end up with a quadratic algorithm if you aren't careful with data structures. Let's fix this so it is linear (or n*log(n) if that is a preferred algorithm)
Hello, 
I ran into the accused function performing quadratically as well.
I read the referenced paper and identified the issue, which is in the implementation and not the referenced paper!
The problem with the referenced line
`index = u = min(k for k in range(index + 1, n) if degree[k] == 1)`
 is quite straight forward. It is always the smallest index that satisfies the predicate which should be the result. But `min`, does not stop at the first satisfying index, it unnecessarily scans the remaining indices as well. If this were avoided, every number from `0` to `n-1` would only be considered once in the entire execution of the algorithm, since the index which defines the start of the search range is moved onwards in the same operation.
A simple solution is to replace `min` with `next`. The identical line before the loop should be adjusted as well.

Resolving patch:
diff --git a/networkx/algorithms/tree/coding.py b/networkx/algorithms/tree/coding.py
--- a/networkx/algorithms/tree/coding.py
+++ b/networkx/algorithms/tree/coding.py
@@ -258,7 +258,7 @@ def to_prufer_sequence(T):
     relabel the nodes of your tree to the appropriate format.
 
     This implementation is from [1]_ and has a running time of
-    $O(n \log n)$.
+    $O(n)$.
 
     See also
     --------
@@ -303,7 +303,7 @@ def to_prufer_sequence(T):
     def parents(u):
         return next(v for v in T[u] if degree[v] > 1)
 
-    index = u = min(k for k in range(n) if degree[k] == 1)
+    index = u = next(k for k in range(n) if degree[k] == 1)
     result = []
     for i in range(n - 2):
         v = parents(u)
@@ -312,7 +312,7 @@ def parents(u):
         if v < index and degree[v] == 1:
             u = v
         else:
-            index = u = min(k for k in range(index + 1, n) if degree[k] == 1)
+            index = u = next(k for k in range(index + 1, n) if degree[k] == 1)
     return result
 
 
@@ -347,7 +347,7 @@ def from_prufer_sequence(sequence):
     relabel the nodes of your tree to the appropriate format.
 
     This implementation is from [1]_ and has a running time of
-    $O(n \log n)$.
+    $O(n)$.
 
     References
     ----------
@@ -387,7 +387,7 @@ def from_prufer_sequence(sequence):
     # tree. After the loop, there should be exactly two nodes that are
     # not in this set.
     not_orphaned = set()
-    index = u = min(k for k in range(n) if degree[k] == 1)
+    index = u = next(k for k in range(n) if degree[k] == 1)
     for v in sequence:
         T.add_edge(u, v)
         not_orphaned.add(u)
@@ -395,7 +395,7 @@ def from_prufer_sequence(sequence):
         if v < index and degree[v] == 1:
             u = v
         else:
-            index = u = min(k for k in range(index + 1, n) if degree[k] == 1)
+            index = u = next(k for k in range(index + 1, n) if degree[k] == 1)
     # At this point, there must be exactly two orphaned nodes; join them.
     orphans = set(T) - not_orphaned
     u, v = orphans


Test patch: