NetworkX g.neighbors(n) time complexity
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
add a comment |
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
add a comment |
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
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
python networkx graph-theory
edited Nov 11 at 18:53
Artyer
3,911626
3,911626
asked Oct 20 at 21:06
Carlos G. Oliver
264
264
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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.
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 useG['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 graphG.
– 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
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
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 useG['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 graphG.
– 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
add a comment |
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.
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 useG['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 graphG.
– 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
add a comment |
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.
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.
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 useG['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 graphG.
– 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
add a comment |
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 useG['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 graphG.
– 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
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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