A universal sink in a directed graph is a vertex v where the in-degree of v is
|V|-1 and out-degree is 0.
I can determine whether a directed graph G has a universal sink by the following alg.
Note: G is represented as an adjacency matrix AdjM and AdjM is given:
for (i=1 to |V|)
if (AdjM[i,1] + AdjM[i,2] + AdjM[i,3] + ... + AdjM[i,|V|] == 0)
&& (AdjM[1,i] + AdjM[2,i] + AdjM[3,i] + ... + AdjM[|V|,i] == |V|-1)
then return i; // i is a universal sink
I solved this problem in O(|V|) time by writing all
|V| of AdjM[i,] and AdjM[,i] values in the code and thus eliminating an inner loop to do these summations.
Is there a way of doing this-- solving it in O(|V|) time without explicitly
coding the summations with each AdjM[i,] and AdjM[,i] as the terms in the summations?
There must be a better way to do it using bit-wise operations, but I can't see it now.
This is Q 22.1-6 in section "Representations of Graphs" of CLRS, p.530.
Thanks in advance.
I think you can easily construct a graph with one universal sink, and change it to a graph with no universal sink by changing only one value in AdjM. This means that you must examine every value in AdjM in order to determine if a universal sink exists.
No matter how cleverly you manipulate the indices and pointers, you cannot beat O(|V|^2).
This can be done in O(|V|) as shown here
Basically, you don't need the sum. You can just check that
all AdjM[i, _] are 0
all AdjM[_, i] are 1 except for AdjM[i, i].
I don't however see how you can eliminate the loop. The solution with checks will be typically faster because you can break out of the loop as soon as any of the condition checks fails.
Related
Background: The following problem occurred to me when I was trying to come up with a hiring challenge for software engineers in my company. I quickly realized that it was probably too difficult since I couldn't come up with a good solution myself :-) Still, I would be interested to know if somebody else can think of an efficient algorithm.
Consider a worker who is deciding which of a selection of continuing education courses to take. Each course will teach them a skill, which will boost their salary by a certain known amount. The worker wants the maximum salary boost, but there are some constraints to consider: Each course takes a month to complete, and the worker can only take one course at a time. They only have K months available for their education. Furthermore, some courses have prerequisites and can only been taken if the worker already completed all of the prerequisite courses. How can the worker find the curriculum that will give them the maximum raise?
More formally, consider a directed acyclic graph with N nodes, which have values v[0], ..., v[N-1] (all positive), and a list of M directed edges E = [(a[0],b[0]), ..., (a[M-1],b[M-1])]. For simplicity we can assume topological order (i.e. 0 <= a[i] < b[i] < N for i = 0, ..., M-1). What is the maximum sum of values of K nodes if we can only select a node if all of its ancestors in the DAG have been selected?
We can trivially solve this problem in O(M + K * N^K) by looping over all size-K subsets and checking if all prerequisites are met. A simple Python implementation would be as follows:
def maxSumNodes(v, E, K):
# For each node, compute all of its direct ancestors in the DAG
N = len(v)
ancestors = [[] for i in range(N)]
for a, b in E:
ancestors[b].append(a)
maxSumValues = 0
for r in itertools.combinations(range(N), K):
sumValues = sum(v[i] for i in r)
nodesSelected = set(r)
if all(all(x in nodesSelected for x in ancestors[y]) for y in r):
maxSumValues = max(maxSumValues, sumValues)
return maxSumValues
However, this becomes prohibitively expensive if K is large (e.g. N = 1,000,000, K = 500,000). Is there a polynomial algorithm in N that works for any K? Alternatively, can it be proven that the problem is NP-hard?
I found this algorithm, which only compares all k-sets with valid requirements
class Node(val value: Int, val children: List<Node> = emptyList())
fun maximise(activeNodes: List<Node>, capacity: Int) : Int {
if(capacity == 0 || activeNodes.isEmpty()) return 0
return activeNodes.maxOf { it.value + maximise(activeNodes - it + it.children, capacity - 1) }
}
val courses = listOf(Node(value = 1, children = listOf(Node(value = 20))), Node(value = 5))
val months = 2
maximise(courses, months)
(Building a DAG isn't an issue, so I'm just assuming my input is already in DAG form)
This algorithm will perform better than yours if there are lots of requirements. However, the worst case for this algorithm (no requirements) boils down to checking each possible k-set, meaning O(N^K).
The problem certainly looks NP-hard, but proving it is complicated. The closest to a proof I got is transforming it into a modified knapsack problem (which is NP-hard) (also mentioned by #user3386109 in the comments):
Start with a list of all paths in the DAG with their combined values as value and their length as weight. Now start solving your knapsack-problem, but whenever an item is picked,
remove all subsets of that path
modify all supersets of that path as following: reduce value by that paths value, reduce weight by that paths weight.
I think that makes this problem at least as hard as knapsack, but I can't prove it.
This problem is NP-Complete. You can show that the problem of deciding, for a given vertex-weighted DAG and integers k and M, whether there exists a valid schedule with k tasks of total value at least M, is NP-Complete. The proof uses gadgets and a strategy borrowed from some of the first hardness proofs for scheduling of Garey and Johnson.
In fact, I'll prove the stronger statement that even restricting all weights to be 1 or 2 and the DAG to have a longest path and maximum in-degree of at most 2, the problem is NP-Hard.
First, the problem is in NP: Given a witness (schedule) of k tasks, we can test in linear time that the schedule is valid and has a value at least M.
You can give a reduction from CLIQUE: Suppose we're given an instance of CLIQUE, which is an undirected simple graph G with vertices V = {v1, v2...vq} and edges E = {e1, e2,...er} along with an integer p, and we're asked whether G has a clique of size p.
We transform this into the following DAG-scheduling instance:
Our vertex set (tasks) is {V1, V2...Vq} U {E1, E2,...Er}
All tasks Vi have value 1 for 1 <= i <= q
All tasks Ej have value 2 for 1 <= j <= r
There is an arc (Vi, Ej) in our DAG whenever vi is an endpoint of ej in G.
k = p(p+1)/2
M = p^2
The precedence constraints/DAG edges are precisely the requirement that we cannot perform an edge task Ej until we've completed both vertex-tasks corresponding to its endpoints. We have to show that there is a valid schedule of k tasks with value at least M in our DAG if and only if G has a clique of size p.
Suppose we have a valid schedule of k tasks with value at least M. Since M is p^2, and k = p(p+1)/2, one way of reaching this value is by completing at least p(p-1)/2 edge tasks, which have value 2, in addition to any p other tasks. In fact, we must complete at least this many edge tasks to reach value M:
Letting E_completed be the set of edge tasks in our schedule and V_completed be the set of vertex tasks in our schedule, we get that
2|E_completed| + |V_completed| >= p^2 and |E_completed| + |V_completed| = p(p+1)/2. Substituting variables and rearranging, we get that |V_completed| <= p, meaning we've completed at most p vertex tasks and thus at least p(p-1)/2 edge tasks.
Since G is a simple graph, for p(p-1)/2 edges to have both endpoints covered by vertices, we must use at least p vertices. So it must be that exactly p vertex tasks were completed, and thus that exactly p(p-1)/2 edge tasks were completed, which, by the precedence constraints, imply that those p corresponding vertices form a clique in G.
For proving the other direction, it's immediate that given a clique in G of size p, we can choose the p corresponding vertex tasks and p(p-1)/2 corresponding edge tasks to get a schedule of k tasks of value M. This concludes the proof.
Some closing notes on this fascinating problem and the winding journey of trying to solve it efficiently:
CLIQUE is, in a sense, among the hardest of the NP-Complete problems, so a reduction from CLIQUE means that this problem inherits its hardness: the DAG scheduling problem is hard-to-approximate and fixed-parameter intractable, making it harder than Knapsack in that sense.
Because the DAG scheduling only depends on reachability constraints, it seemed at first that an efficient solution could be possible. You can, in practice, take the transitive reduction of your graph, and then use a topological ordering where vertices are also ordered by their depth (longest path ending at a vertex).
In practice, many interesting instances of this problem could be efficiently solvable (which is one reason I intuitively suspected the problem was not NP-Hard). If the DAG is deep and some vertices have many ancestors, rather than at most 2, we can do a depth-first search backwards through the topological order. A guess that one vertex is in our schedule forces all of its ancestors to also be in the schedule. This cuts the graph size considerably (possibly into many disconnected, easily solved components) and reduces k by the number of ancestors as well.
Overall, a very interesting problem-- I was unable to find the exact problem elsewhere, or even much information about the relevant structural properties of DAGs. However, CLIQUE can be used for many scheduling problems: the terminology and use of vertex and edge tasks is adapted from Garey and Johnson's 1976 hardness proof, (which I highly recommend reading) of a superficially different problem: scheduling an entire (unweighted) DAG, but where tasks have variable deadlines and unit processing time, and we only want to know whether we can make at most k tasks late for their deadlines.
Given: An unweighted, directed Graph (G=(E,V)), which can contain any number of cycles.
Goal: For all vertices I want the longest simple path to some target vertex X in V
Algorithm Idea:
For each v in V
v.distanceToTarget = DepthFirstSearch(v)
Next
DepthFirstSearch(v as Vertex)
if v = target then
'Distance towards target is 0 for target itself
return 0
elseif v.isVisitedInCurrentDFSPath then
'Cycle found -> I wont find the target when I go around in cycles -> abort
return -infinity
else
'Return the maximum Distance of all Successors + 1
return max(v.Successors.ForEach(Function(v) DepthFirstSearch(v) )) + 1
end if
Is this correct for all cases? (Assuming, that the target can be reached from every vertex)
The number of edges in my graphs is very small.
Assume |E| <= 3*|V| holds. How would I compute the average time complexity?
Thanks!
Time complexity is about what values influence your runtime most. In your case your evaluating all possible paths between v and target. That is basically O(number of routes). Now you need to figure out how to express number of all possible routes in terms of E and V.
Most likely result with be something like O(exp(E)) or O(exp(V)) because number of routes going through each node/vertex goes exponentially when you add new possible routes.
EDIT: I missed a detail that you were asking for average time complexity that would mean amortized complexity. But as your algorithm is always evaluates all possible routes worst case complexity is same as average complexity.
I'm looking for an algorithm to solve this problem. I have to implement it (so I need a not np solution XD)
I have a complete graph with a cost on each arch and a reward on each vertex. I have only a start point, but it doesn't matter the end point, becouse the problem is to find a path to see as many vertex as possible, in order to have the maximum reward possible, but subject to a maximum cost limit. (for this reason it doesn't matter the end position).
I think to find the optimum solution is a np-hard problem, but also an approximate solution is apprecciated :D
Thanks
I'm trying study how to solve the problem with branch & bound...
update: complete problem dscription
I have a region in which there are several areas identify by its id and x,y,z position. Each vertex identifies one ot these areas. The maximum number of ares is 200.
From a start point S, I know the cost, specified in seconds and inserted in the arch (so only integer values), to reach each vertex from each other vertex (a complete graph).
When I visit a vertex I get a reward (float valiues).
My objective is to find a paths in a the graph that maximize the reward but I'm subject to a cost constraint on the paths. Indeed I have only limited minutes to complete the path (for example 600 seconds.)
The graph is made as matrix adjacency matrix for the cost and reward (but if is useful I can change the representation).
I can visit vertex more time but with one reward only!
Since you're interested in branch and bound, let's formulate a linear program. Use Floyd–Warshall to adjust the costs minimally downward so that cost(uw) ≤ cost(uv) + cost(vw) for all vertices u, v, w.
Let s be the starting vertex. We have 0-1 variables x(v) that indicate whether vertex v is part of the path and 0-1 variables y(uv) that indicate whether the arc uv is part of the path. We seek to maximize
sum over all vertices v of reward(v) x(v).
The constraints unfortunately are rather complicated. We first relate the x and y variables.
for all vertices v ≠ s, x(v) - sum over all vertices u of y(uv) = 0
Then we bound the cost.
sum over all arcs uv of cost(uv) y(uv) ≤ budget
We have (pre)flow constraints to ensure that the arcs chosen look like a path possibly accompanied by cycles (we'll handle the cycles shortly).
for all vertices v, sum over all vertices u of y(uv)
- sum over all vertices w of y(vw)
≥ -1 if v = s
0 if v ≠ s
To handle the cycles, we add cut covering constraints.
for all subsets of vertices T such that s is not in T,
for all vertices t in T,
x(t) - sum over all vertices u not in T and v in T of y(uv) ≥ 0
Because of the preflow constraints, a cycle necessarily is disconnected from the path structure.
There are exponentially many cut covering constraints, so when solving the LP, we have to generate them on demand. This means finding the minimum cut between s and each other vertex t, then verifying that the capacity of the cut is no greater than x(t). If we find a violation, then we add the constraint and use the dual simplex method to find the new optimum (repeat as necessary).
I'm going to pass on describing the branching machinery – this should be taken care of by your LP solver anyway.
Finding the optimal solution
Here is a recursive approach to solving your problem.
Let's begin with some definitions :
Let A = (Ai)1 ≤ i ≤ N be the areas.
Let wi,j = wj,i the time cost for traveling from Ai to Aj and vice versa.
Let ri the reward for visiting area Ai
Here is the recursive procedure that will output the exact requested solution : (pseudo-code)
List<Area> GetBestPath(int time_limit, Area S, int *rwd) {
int best_reward(0), possible_reward(0), best_fit(0);
List<Area> possible_path[N] = {[]};
if (time_limit < 0) {
return [];
}
if (!S.visited) {
*rwd += S.reward;
S.visit();
}
for (int i = 0; i < N; ++i) {
if (S.index != i) {
possible_path[i] = GetBestPath(time_limit - W[S.index][i], A[i], &possible_reward);
if (possible_reward > best_reward) {
best_reward = possible_reward;
best_fit = i;
}
}
}
*rwd+= best_reward;
possible_path[best_fit].push_front(S);
return possible_path[best_fit];
}
For obvious clarity reasons, I supposed the Ai to be globally reachable, as well as the wi,j.
Explanations
You start at S. First thing you do ? Collect the reward and mark the node as visited. Then you have to check which way to go is best between the S's N-1 neighbors (lets call them NS,i for 1 ≤ i ≤ N-1).
This is the exact same thing as solving the problem for NS,i with a time limit of :
time_limit - W(S ↔ NS,i)
And since you mark the visited nodes, when arriving at an area, you first check if it is marked. If so you have no reward ... Else you collect and mark it as visited ...
And so forth !
The ending condition is when time_limit (C) becomes negative. This tells us we reached the limit and cannot proceed to further moves : the recursion ends. The final path may contain useless journeys if all the rewards have been collected before the time limit C is reached. You'll have to "prune" the output list.
Complexity ?
Oh this solution is soooooooo awful in terms of complexity !
Each calls leads to N-1 calls ... Until the time limit is reached. The longest possible call sequence is yielded by going back and forth each time on the shortest edge. Let wmin be the weight of this edge.
Then obviously, the overall complexity is bounded by NC/wmin.C/wmin.
This is huuuuuge.
Another approach
Maintain a hash table of all the visited nodes.
On the other side, maintain a Max-priority queue (eg. using a MaxHeap) of the nodes that have not been collected yet. (The top of the heap is the node with the highest reward). The priority value for each node Ai in the queue is set as the couple (ri, E[wi,j])
Pop the heap : Target <- heap.pop().
Compute the shortest path to this node using Dijkstra algorithm.
Check out the path : If the cost of the path is too high, then the node is not reachable, add it to the unreachable nodes list.
Else collect all the uncollected nodes that you find in it and ...
Remove each collected node from the heap.
Set Target as the new starting point.
In either case, proceed to step 1. until the heap is empty.
Note : A hash table is the best suited to keep track of the collected node. This way, we can check a node in a path computed using Dijkstra in O(1).
Likewise, maintaining a hashtable leading to the position of each node in the heap might be useful to optimise the "pruning" of the heap, when collecting the nodes along a path.
A little analysis
This approach is slightly better than the first one in terms of complexity, but may not lead to the optimal result. In fact, it can even perform quite poorly on some graph configurations. For example, if all nodes have a reward r, except one node T that has r+1 and W(N ↔ T) = C for every node N, but the other edges would be all reachable, then this will only make you collect T and miss every other node. In this particular case, the best solution would have been to ignore T and collect everyone else leading to a reward of (N-1).r instead of only r+1.
You are given a complete undirected graph with N vertices. All but K edges have a cost of A. Those K edges have a cost of B and you know them (as a list of pairs). What's the minimum cost from node 0 to node N - 1.
2 <= N <= 500k
0 <= K <= 500k
1 <= A, B <= 500k
The problem is, obviously, when those K edges cost more than the other ones and node 0 and node N - 1 are connected by a K-edge.
Dijkstra doesn't work. I've even tried something very similar with a BFS.
Step1: Let G(0) be the set of "good" adjacent nodes with node 0.
Step2: For each node in G(0):
compute G(node)
if G(node) contains N - 1
return step
else
add node to some queue
repeat step2 and increment step
The problem is that this uses up a lot of time due to the fact that for every node you have to make a loop from 0 to N - 1 in order to find the "good" adjacent nodes.
Does anyone have any better ideas? Thank you.
Edit: Here is a link from the ACM contest: http://acm.ro/prob/probleme/B.pdf
This is laborous case work:
A < B and 0 and N-1 are joined by A -> trivial.
B < A and 0 and N-1 are joined by B -> trivial.
B < A and 0 and N-1 are joined by A ->
Do BFS on graph with only K edges.
A < B and 0 and N-1 are joined by B ->
You can check in O(N) time is there is a path with length 2*A (try every vertex in middle).
To check other path lengths following algorithm should do the trick:
Let X(d) be set of nodes reachable by using d shorter edges from 0. You can find X(d) using following algorithm: Take each vertex v with unknown distance and iterativelly check edges between v and vertices from X(d-1). If you found short edge, then v is in X(d) otherwise you stepped on long edge. Since there are at most K long edges you can step on them at most K times. So you should find distance of each vertex in at most O(N + K) time.
I propose a solution to a somewhat more general problem where you might have more than two types of edges and the edge weights are not bounded. For your scenario the idea is probably a bit overkill, but the implementation is quite simple, so it might be a good way to go about the problem.
You can use a segment tree to make Dijkstra more efficient. You will need the operations
set upper bound in a range as in, given U, L, R; for all x[i] with L <= i <= R, set x[i] = min(x[i], u)
find a global minimum
The upper bounds can be pushed down the tree lazily, so both can be implemented in O(log n)
When relaxing outgoing edges, look for the edges with cost B, sort them and update the ranges in between all at once.
The runtime should be O(n log n + m log m) if you sort all the edges upfront (by outgoing vertex).
EDIT: Got accepted with this approach. The good thing about it is that it avoids any kind of special casing. It's still ~80 lines of code.
In the case when A < B, I would go with kind of a BFS, where you would check where you can't reach instead of where you can. Here's the pseudocode:
G(k) is the set of nodes reachable by k cheap edges and no less. We start with G(0) = {v0}
while G(k) isn't empty and G(k) doesn't contain vN-1 and k*A < B
A = array[N] of zeroes
for every node n in G(k)
for every expensive edge (n,m)
A[m]++
# now we have that A[m] == |G(k)| iff m can't be reached by a cheap edge from any of G(k)
set G(k+1) to {m; A[m] < |G(k)|} except {n; n is in G(0),...G(k)}
k++
This way you avoid iterating through the (many) cheap edges and only iterate through the relatively few expensive edges.
As you have correctly noted, the problem comes when A > B and edge from 0 to n-1 has a cost of A.
In this case you can simply delete all edges in the graph that have a cost of A. This is because an optimal route shall only have edges with cost B.
Then you can perform a simple BFS since the costs of all edges are the same. It will give you optimal performance as pointed out by this link: Finding shortest path for equal weighted graph
Moreover, you can stop your BFS when the total cost exceeds A.
One of the assignments in my algorithms class is to design an exhaustive search algorithm to solve the clique problem. That is, given a graph of size n, the algorithm is supposed to determine if there is a complete sub-graph of size k. I think I've gotten the answer, but I can't help but think it could be improved. Here's what I have:
Version 1
input: A graph represented by an array A[0,...n-1], the size k of the subgraph to find.
output: True if a subgraph exists, False otherwise
Algorithm (in python-like pseudocode):
def clique(A, k):
P = A x A x A //Cartesian product
for tuple in P:
if connected(tuple):
return true
return false
def connected(tuple):
unconnected = tuple
for vertex in tuple:
for test_vertex in unconnected:
if vertex is linked to test_vertex:
remove test_vertex from unconnected
if unconnected is empty:
return true
else:
return false
Version 2
input: An adjacency matrix of size n by n, and k the size of the subgraph to find
output: All complete subgraphs in A of size k.
Algorithm (this time in functional/Python pseudocode):
//Base case: return all vertices in a list since each
//one is a 1-clique
def clique(A, 1):
S = new list
for i in range(0 to n-1):
add i to S
return S
//Get a tuple representing all the cliques where
//k = k - 1, then find any cliques for k
def clique(A,k):
C = clique(A, k-1)
S = new list
for tuple in C:
for i in range(0 to n-1):
//make sure the ith vertex is linked to each
//vertex in tuple
for j in tuple:
if A[i,j] != 1:
break
//This means that vertex i makes a clique
if j is the last element:
newtuple = (i | tuple) //make a new tuple with i added
add newtuple to S
//Return the list of k-cliques
return S
Does anybody have any thoughts, comments, or suggestions? This includes bugs I might have missed as well as ways to make this more readable (I'm not used to using much pseudocode).
Version 3
Fortunately, I talked to my professor before submitting the assignment. When I showed him the pseudo-code I had written, he smiled and told me that I did way too much work. For one, I didn't have to submit pseudo-code; I just had to demonstrate that I understood the problem. And two, he was wanting the brute force solution. So what I turned in looked something like this:
input: A graph G = (V,E), the size of the clique to find k
output: True if a clique does exist, false otherwise
Algorithm:
Find the Cartesian Product Vk.
For each tuple in the result, test whether each vertex is connected to every other. If all are connected, return true and exit.
Return false and exit.
UPDATE: Added second version. I think this is getting better although I haven't added any fancy dynamic programming (that I know of).
UPDATE 2: Added some more commenting and documentation to make version 2 more readable. This will probably be the version I turn in today. Thanks for everyone's help! I wish I could accept more than one answer, but I accepted the answer by the person that's helped me out the most. I'll let you guys know what my professor thinks.
Some comments:
You only need to consider n-choose-k combinations of vertices, not all k-tuples (n^k of them).
connected(tuple) doesn't look right. Don't you need to reset unconnected inside the loop?
As the others have suggested, there are better ways of brute-forcing this. Consider the following recursive relation: A (k+1)-subgraph is a clique if the first k vertices form a clique and vertex (k+1) is adjacent to each of the first k vertices. You can apply this in two directions:
Start with a 1-clique and gradually expand the clique until you get the desired size. For example, if m is the largest vertex in the current clique, try to add vertex {m+1, m+2, ..., n-1} to get a clique that is one vertex larger. (This is similar to a depth-first tree traversal, where the children of a tree node are the vertices larger than the largest vertex in the current clique.)
Start with a subgraph of the desired size and check if it is a clique, using the recursive relation. Set up a memoization table to store results along the way.
(implementation suggestion) Use an adjacency matrix (0-1) to represent edges in the graph.
(initial pruning) Throw away all vertices with degree less than k.
I once implemented an algorithm to find all maximal cliques in a graph, which is a similar problem to yours. The way I did it was based on this paper: http://portal.acm.org/citation.cfm?doid=362342.362367 - it described a backtracking solution which I found very useful as a guide, although I changed quite a lot from that paper. You'd need a subscription to get at that though, but I presume your University would have one available.
One thing about that paper though is I really think they should have named the "not set" the "already considered set" because it's just too confusing otherwise.
The algorithm "for each k-tuple of vertices, if it is a clique, then return true" works for sure. However, it's brute force, which is probably not what an algorithms course is searching for. Instead, consider the following:
Every vertex is a 1-clique.
For every 1-clique, every vertex that connects to the vertex in the 1-clique contributes to a 2-clique.
For every 2-clique, every vertex that connects to each vertex in the 2-clique contributes to a 3-clique.
...
For every (k-1)-clique, every vertex that connects to each vertex in the (k-1) clique contributes to a k-clique.
This idea might lead to a better approach.
It's amazing what typing things down as a question will show you about what you've just written. This line:
P = A x A x A //Cartesian product
should be this:
P = A k //Cartesian product
What do you mean by A^k? Are you taking a matrix product? If so, is A the adjacency matrix (you said it was an array of n+1 elements)?
In setbuilder notation, it would look something like this:
P = {(x0, x1, ... xk) | x0 ∈ A and x1 ∈ A ... and xk ∈ A}
It's basically just a Cartesian product of A taken k times. On paper, I wrote it down as k being a superscript of A (I just now figured out how to do that using markdown).
Plus, A is just an array of each individual vertex without regard for adjacency.