NetworkX g.neighbors(n) time complexity












1














What is the time complexity of getting the neighbors of a node in networkx?



>>> G = nx.Graph()
>>> G.add_edges_from([('a', 'b'), ('a', 'c')])
>>> G.neighbors('a')









share|improve this question





























    1














    What is the time complexity of getting the neighbors of a node in networkx?



    >>> G = nx.Graph()
    >>> G.add_edges_from([('a', 'b'), ('a', 'c')])
    >>> G.neighbors('a')









    share|improve this question



























      1












      1








      1







      What is the time complexity of getting the neighbors of a node in networkx?



      >>> G = nx.Graph()
      >>> G.add_edges_from([('a', 'b'), ('a', 'c')])
      >>> G.neighbors('a')









      share|improve this question















      What is the time complexity of getting the neighbors of a node in networkx?



      >>> G = nx.Graph()
      >>> G.add_edges_from([('a', 'b'), ('a', 'c')])
      >>> G.neighbors('a')






      python networkx graph-theory






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 11 at 18:53









      Artyer

      3,911626




      3,911626










      asked Oct 20 at 21:06









      Carlos G. Oliver

      264




      264
























          1 Answer
          1






          active

          oldest

          votes


















          1














          Short answer: obtaining the neighbors takes linear time O(m) (with m the number neighbors, or in unlikely scenarios O(m+n) with n the number of nodes). Adding edges to the network will normally take linear time as well O(n) in the number of edges that you add, so constant for a single edge, but in a (very) unlikely scenario could take quadratic time O(n2). You can access the dictionary of neighbors in constant time (well in a worst case scenario linear time) with `G['a'].



          We can inspect the source code, and see:



          def neighbors(self, n):
          # ...
          try:
          return list(self.adj[n])
          except KeyError:
          raise NetworkXError("The node %s is not in the graph." % (n,))


          Now if inspect the source code, we see that (by default) adj is a dictionary, indeed:



          node_dict_factory = dict
          adjlist_dict_factory = dict
          edge_attr_dict_factory = dict

          def __init__(self, data=None, **attr):
          # ...
          self.node_dict_factory = ndf = self.node_dict_factory
          # ...
          self.node = ndf() # empty node attribute dict
          # ...


          So even if the dictionary lookup takes - worst case, but very unlikely - linear time O(n) in the number of nodes, then we only have another linear time to convert the list of keys into a list.



          The add_edges_from method on the other hand is implemented as:



          def add_edges_from(self, ebunch, attr_dict=None, **attr):
          # ...
          for e in ebunch:
          ne = len(e)
          if ne == 3:
          u, v, dd = e
          elif ne == 2:
          u, v = e
          dd = {} # doesnt need edge_attr_dict_factory
          else:
          raise NetworkXError(
          "Edge tuple %s must be a 2-tuple or 3-tuple." % (e,))
          if u not in self.node:
          self.adj[u] = self.adjlist_dict_factory()
          self.node[u] = {}
          if v not in self.node:
          self.adj[v] = self.adjlist_dict_factory()
          self.node[v] = {}
          datadict = self.adj[u].get(v, self.edge_attr_dict_factory())
          datadict.update(attr_dict)
          datadict.update(dd)
          self.adj[u][v] = datadict
          self.adj[v][u] = datadict


          So we basically iterate over every item in the list, do some unpacking, and then add the data dictionary twice to the adj (once in one direction, once in the other). If the dictionary lookup is fast (usually it is constant time), then this algorithm has linear time (in the number of tuples to add). Worst case (but very unlikely) dictionary lookups can take linear time, so then inserting can scale up to O(n2).



          The Graph class however allows access to the subdictionary with G['a']:



          >>> G['a']
          AtlasView({'b': {}, 'c': {}})


          The AtlasView is a view over the dictionary to prevent one from editing the underlying dictionary.






          share|improve this answer























          • thanks! so if i want to do many fast neighbor lookups in an unchanging graph, I suppose it would be better pre-process and store the neighbors of every node in a dictionary {node: {neighbors}} ?
            – Carlos G. Oliver
            Oct 20 at 21:29










          • @CarlosG.Oliver: you can use G['a'], this will return you the subdictionary of that node, and the keys thus contain the neighbours. This will thus (under normal circumstances) give you the dictionary in constant time. Note however then you here got access to the "data" of the graph, and changing the dictionary will have effect on your graph G.
            – Willem Van Onsem
            Oct 20 at 21:32












          • @CarlosG.Oliver I wouldn't advise this preprocessing. I don't think any speedup from having your dictionary structure over the underlying dictionary structure in networkx would make up for the time it takes to do the preprocessing (in fact, I don't see why your dictionary would be any faster than networkx's). You also double the memory footprint, and introduce the possibility of a bug.
            – Joel
            Oct 20 at 22:18













          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52910115%2fnetworkx-g-neighborsn-time-complexity%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          1














          Short answer: obtaining the neighbors takes linear time O(m) (with m the number neighbors, or in unlikely scenarios O(m+n) with n the number of nodes). Adding edges to the network will normally take linear time as well O(n) in the number of edges that you add, so constant for a single edge, but in a (very) unlikely scenario could take quadratic time O(n2). You can access the dictionary of neighbors in constant time (well in a worst case scenario linear time) with `G['a'].



          We can inspect the source code, and see:



          def neighbors(self, n):
          # ...
          try:
          return list(self.adj[n])
          except KeyError:
          raise NetworkXError("The node %s is not in the graph." % (n,))


          Now if inspect the source code, we see that (by default) adj is a dictionary, indeed:



          node_dict_factory = dict
          adjlist_dict_factory = dict
          edge_attr_dict_factory = dict

          def __init__(self, data=None, **attr):
          # ...
          self.node_dict_factory = ndf = self.node_dict_factory
          # ...
          self.node = ndf() # empty node attribute dict
          # ...


          So even if the dictionary lookup takes - worst case, but very unlikely - linear time O(n) in the number of nodes, then we only have another linear time to convert the list of keys into a list.



          The add_edges_from method on the other hand is implemented as:



          def add_edges_from(self, ebunch, attr_dict=None, **attr):
          # ...
          for e in ebunch:
          ne = len(e)
          if ne == 3:
          u, v, dd = e
          elif ne == 2:
          u, v = e
          dd = {} # doesnt need edge_attr_dict_factory
          else:
          raise NetworkXError(
          "Edge tuple %s must be a 2-tuple or 3-tuple." % (e,))
          if u not in self.node:
          self.adj[u] = self.adjlist_dict_factory()
          self.node[u] = {}
          if v not in self.node:
          self.adj[v] = self.adjlist_dict_factory()
          self.node[v] = {}
          datadict = self.adj[u].get(v, self.edge_attr_dict_factory())
          datadict.update(attr_dict)
          datadict.update(dd)
          self.adj[u][v] = datadict
          self.adj[v][u] = datadict


          So we basically iterate over every item in the list, do some unpacking, and then add the data dictionary twice to the adj (once in one direction, once in the other). If the dictionary lookup is fast (usually it is constant time), then this algorithm has linear time (in the number of tuples to add). Worst case (but very unlikely) dictionary lookups can take linear time, so then inserting can scale up to O(n2).



          The Graph class however allows access to the subdictionary with G['a']:



          >>> G['a']
          AtlasView({'b': {}, 'c': {}})


          The AtlasView is a view over the dictionary to prevent one from editing the underlying dictionary.






          share|improve this answer























          • thanks! so if i want to do many fast neighbor lookups in an unchanging graph, I suppose it would be better pre-process and store the neighbors of every node in a dictionary {node: {neighbors}} ?
            – Carlos G. Oliver
            Oct 20 at 21:29










          • @CarlosG.Oliver: you can use G['a'], this will return you the subdictionary of that node, and the keys thus contain the neighbours. This will thus (under normal circumstances) give you the dictionary in constant time. Note however then you here got access to the "data" of the graph, and changing the dictionary will have effect on your graph G.
            – Willem Van Onsem
            Oct 20 at 21:32












          • @CarlosG.Oliver I wouldn't advise this preprocessing. I don't think any speedup from having your dictionary structure over the underlying dictionary structure in networkx would make up for the time it takes to do the preprocessing (in fact, I don't see why your dictionary would be any faster than networkx's). You also double the memory footprint, and introduce the possibility of a bug.
            – Joel
            Oct 20 at 22:18


















          1














          Short answer: obtaining the neighbors takes linear time O(m) (with m the number neighbors, or in unlikely scenarios O(m+n) with n the number of nodes). Adding edges to the network will normally take linear time as well O(n) in the number of edges that you add, so constant for a single edge, but in a (very) unlikely scenario could take quadratic time O(n2). You can access the dictionary of neighbors in constant time (well in a worst case scenario linear time) with `G['a'].



          We can inspect the source code, and see:



          def neighbors(self, n):
          # ...
          try:
          return list(self.adj[n])
          except KeyError:
          raise NetworkXError("The node %s is not in the graph." % (n,))


          Now if inspect the source code, we see that (by default) adj is a dictionary, indeed:



          node_dict_factory = dict
          adjlist_dict_factory = dict
          edge_attr_dict_factory = dict

          def __init__(self, data=None, **attr):
          # ...
          self.node_dict_factory = ndf = self.node_dict_factory
          # ...
          self.node = ndf() # empty node attribute dict
          # ...


          So even if the dictionary lookup takes - worst case, but very unlikely - linear time O(n) in the number of nodes, then we only have another linear time to convert the list of keys into a list.



          The add_edges_from method on the other hand is implemented as:



          def add_edges_from(self, ebunch, attr_dict=None, **attr):
          # ...
          for e in ebunch:
          ne = len(e)
          if ne == 3:
          u, v, dd = e
          elif ne == 2:
          u, v = e
          dd = {} # doesnt need edge_attr_dict_factory
          else:
          raise NetworkXError(
          "Edge tuple %s must be a 2-tuple or 3-tuple." % (e,))
          if u not in self.node:
          self.adj[u] = self.adjlist_dict_factory()
          self.node[u] = {}
          if v not in self.node:
          self.adj[v] = self.adjlist_dict_factory()
          self.node[v] = {}
          datadict = self.adj[u].get(v, self.edge_attr_dict_factory())
          datadict.update(attr_dict)
          datadict.update(dd)
          self.adj[u][v] = datadict
          self.adj[v][u] = datadict


          So we basically iterate over every item in the list, do some unpacking, and then add the data dictionary twice to the adj (once in one direction, once in the other). If the dictionary lookup is fast (usually it is constant time), then this algorithm has linear time (in the number of tuples to add). Worst case (but very unlikely) dictionary lookups can take linear time, so then inserting can scale up to O(n2).



          The Graph class however allows access to the subdictionary with G['a']:



          >>> G['a']
          AtlasView({'b': {}, 'c': {}})


          The AtlasView is a view over the dictionary to prevent one from editing the underlying dictionary.






          share|improve this answer























          • thanks! so if i want to do many fast neighbor lookups in an unchanging graph, I suppose it would be better pre-process and store the neighbors of every node in a dictionary {node: {neighbors}} ?
            – Carlos G. Oliver
            Oct 20 at 21:29










          • @CarlosG.Oliver: you can use G['a'], this will return you the subdictionary of that node, and the keys thus contain the neighbours. This will thus (under normal circumstances) give you the dictionary in constant time. Note however then you here got access to the "data" of the graph, and changing the dictionary will have effect on your graph G.
            – Willem Van Onsem
            Oct 20 at 21:32












          • @CarlosG.Oliver I wouldn't advise this preprocessing. I don't think any speedup from having your dictionary structure over the underlying dictionary structure in networkx would make up for the time it takes to do the preprocessing (in fact, I don't see why your dictionary would be any faster than networkx's). You also double the memory footprint, and introduce the possibility of a bug.
            – Joel
            Oct 20 at 22:18
















          1












          1








          1






          Short answer: obtaining the neighbors takes linear time O(m) (with m the number neighbors, or in unlikely scenarios O(m+n) with n the number of nodes). Adding edges to the network will normally take linear time as well O(n) in the number of edges that you add, so constant for a single edge, but in a (very) unlikely scenario could take quadratic time O(n2). You can access the dictionary of neighbors in constant time (well in a worst case scenario linear time) with `G['a'].



          We can inspect the source code, and see:



          def neighbors(self, n):
          # ...
          try:
          return list(self.adj[n])
          except KeyError:
          raise NetworkXError("The node %s is not in the graph." % (n,))


          Now if inspect the source code, we see that (by default) adj is a dictionary, indeed:



          node_dict_factory = dict
          adjlist_dict_factory = dict
          edge_attr_dict_factory = dict

          def __init__(self, data=None, **attr):
          # ...
          self.node_dict_factory = ndf = self.node_dict_factory
          # ...
          self.node = ndf() # empty node attribute dict
          # ...


          So even if the dictionary lookup takes - worst case, but very unlikely - linear time O(n) in the number of nodes, then we only have another linear time to convert the list of keys into a list.



          The add_edges_from method on the other hand is implemented as:



          def add_edges_from(self, ebunch, attr_dict=None, **attr):
          # ...
          for e in ebunch:
          ne = len(e)
          if ne == 3:
          u, v, dd = e
          elif ne == 2:
          u, v = e
          dd = {} # doesnt need edge_attr_dict_factory
          else:
          raise NetworkXError(
          "Edge tuple %s must be a 2-tuple or 3-tuple." % (e,))
          if u not in self.node:
          self.adj[u] = self.adjlist_dict_factory()
          self.node[u] = {}
          if v not in self.node:
          self.adj[v] = self.adjlist_dict_factory()
          self.node[v] = {}
          datadict = self.adj[u].get(v, self.edge_attr_dict_factory())
          datadict.update(attr_dict)
          datadict.update(dd)
          self.adj[u][v] = datadict
          self.adj[v][u] = datadict


          So we basically iterate over every item in the list, do some unpacking, and then add the data dictionary twice to the adj (once in one direction, once in the other). If the dictionary lookup is fast (usually it is constant time), then this algorithm has linear time (in the number of tuples to add). Worst case (but very unlikely) dictionary lookups can take linear time, so then inserting can scale up to O(n2).



          The Graph class however allows access to the subdictionary with G['a']:



          >>> G['a']
          AtlasView({'b': {}, 'c': {}})


          The AtlasView is a view over the dictionary to prevent one from editing the underlying dictionary.






          share|improve this answer














          Short answer: obtaining the neighbors takes linear time O(m) (with m the number neighbors, or in unlikely scenarios O(m+n) with n the number of nodes). Adding edges to the network will normally take linear time as well O(n) in the number of edges that you add, so constant for a single edge, but in a (very) unlikely scenario could take quadratic time O(n2). You can access the dictionary of neighbors in constant time (well in a worst case scenario linear time) with `G['a'].



          We can inspect the source code, and see:



          def neighbors(self, n):
          # ...
          try:
          return list(self.adj[n])
          except KeyError:
          raise NetworkXError("The node %s is not in the graph." % (n,))


          Now if inspect the source code, we see that (by default) adj is a dictionary, indeed:



          node_dict_factory = dict
          adjlist_dict_factory = dict
          edge_attr_dict_factory = dict

          def __init__(self, data=None, **attr):
          # ...
          self.node_dict_factory = ndf = self.node_dict_factory
          # ...
          self.node = ndf() # empty node attribute dict
          # ...


          So even if the dictionary lookup takes - worst case, but very unlikely - linear time O(n) in the number of nodes, then we only have another linear time to convert the list of keys into a list.



          The add_edges_from method on the other hand is implemented as:



          def add_edges_from(self, ebunch, attr_dict=None, **attr):
          # ...
          for e in ebunch:
          ne = len(e)
          if ne == 3:
          u, v, dd = e
          elif ne == 2:
          u, v = e
          dd = {} # doesnt need edge_attr_dict_factory
          else:
          raise NetworkXError(
          "Edge tuple %s must be a 2-tuple or 3-tuple." % (e,))
          if u not in self.node:
          self.adj[u] = self.adjlist_dict_factory()
          self.node[u] = {}
          if v not in self.node:
          self.adj[v] = self.adjlist_dict_factory()
          self.node[v] = {}
          datadict = self.adj[u].get(v, self.edge_attr_dict_factory())
          datadict.update(attr_dict)
          datadict.update(dd)
          self.adj[u][v] = datadict
          self.adj[v][u] = datadict


          So we basically iterate over every item in the list, do some unpacking, and then add the data dictionary twice to the adj (once in one direction, once in the other). If the dictionary lookup is fast (usually it is constant time), then this algorithm has linear time (in the number of tuples to add). Worst case (but very unlikely) dictionary lookups can take linear time, so then inserting can scale up to O(n2).



          The Graph class however allows access to the subdictionary with G['a']:



          >>> G['a']
          AtlasView({'b': {}, 'c': {}})


          The AtlasView is a view over the dictionary to prevent one from editing the underlying dictionary.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Oct 20 at 21:51

























          answered Oct 20 at 21:23









          Willem Van Onsem

          143k16135227




          143k16135227












          • thanks! so if i want to do many fast neighbor lookups in an unchanging graph, I suppose it would be better pre-process and store the neighbors of every node in a dictionary {node: {neighbors}} ?
            – Carlos G. Oliver
            Oct 20 at 21:29










          • @CarlosG.Oliver: you can use G['a'], this will return you the subdictionary of that node, and the keys thus contain the neighbours. This will thus (under normal circumstances) give you the dictionary in constant time. Note however then you here got access to the "data" of the graph, and changing the dictionary will have effect on your graph G.
            – Willem Van Onsem
            Oct 20 at 21:32












          • @CarlosG.Oliver I wouldn't advise this preprocessing. I don't think any speedup from having your dictionary structure over the underlying dictionary structure in networkx would make up for the time it takes to do the preprocessing (in fact, I don't see why your dictionary would be any faster than networkx's). You also double the memory footprint, and introduce the possibility of a bug.
            – Joel
            Oct 20 at 22:18




















          • thanks! so if i want to do many fast neighbor lookups in an unchanging graph, I suppose it would be better pre-process and store the neighbors of every node in a dictionary {node: {neighbors}} ?
            – Carlos G. Oliver
            Oct 20 at 21:29










          • @CarlosG.Oliver: you can use G['a'], this will return you the subdictionary of that node, and the keys thus contain the neighbours. This will thus (under normal circumstances) give you the dictionary in constant time. Note however then you here got access to the "data" of the graph, and changing the dictionary will have effect on your graph G.
            – Willem Van Onsem
            Oct 20 at 21:32












          • @CarlosG.Oliver I wouldn't advise this preprocessing. I don't think any speedup from having your dictionary structure over the underlying dictionary structure in networkx would make up for the time it takes to do the preprocessing (in fact, I don't see why your dictionary would be any faster than networkx's). You also double the memory footprint, and introduce the possibility of a bug.
            – Joel
            Oct 20 at 22:18


















          thanks! so if i want to do many fast neighbor lookups in an unchanging graph, I suppose it would be better pre-process and store the neighbors of every node in a dictionary {node: {neighbors}} ?
          – Carlos G. Oliver
          Oct 20 at 21:29




          thanks! so if i want to do many fast neighbor lookups in an unchanging graph, I suppose it would be better pre-process and store the neighbors of every node in a dictionary {node: {neighbors}} ?
          – Carlos G. Oliver
          Oct 20 at 21:29












          @CarlosG.Oliver: you can use G['a'], this will return you the subdictionary of that node, and the keys thus contain the neighbours. This will thus (under normal circumstances) give you the dictionary in constant time. Note however then you here got access to the "data" of the graph, and changing the dictionary will have effect on your graph G.
          – Willem Van Onsem
          Oct 20 at 21:32






          @CarlosG.Oliver: you can use G['a'], this will return you the subdictionary of that node, and the keys thus contain the neighbours. This will thus (under normal circumstances) give you the dictionary in constant time. Note however then you here got access to the "data" of the graph, and changing the dictionary will have effect on your graph G.
          – Willem Van Onsem
          Oct 20 at 21:32














          @CarlosG.Oliver I wouldn't advise this preprocessing. I don't think any speedup from having your dictionary structure over the underlying dictionary structure in networkx would make up for the time it takes to do the preprocessing (in fact, I don't see why your dictionary would be any faster than networkx's). You also double the memory footprint, and introduce the possibility of a bug.
          – Joel
          Oct 20 at 22:18






          @CarlosG.Oliver I wouldn't advise this preprocessing. I don't think any speedup from having your dictionary structure over the underlying dictionary structure in networkx would make up for the time it takes to do the preprocessing (in fact, I don't see why your dictionary would be any faster than networkx's). You also double the memory footprint, and introduce the possibility of a bug.
          – Joel
          Oct 20 at 22:18




















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52910115%2fnetworkx-g-neighborsn-time-complexity%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Coverage of Google Street View

          Full-time equivalent

          Surfing