orac.amt.edu.au/notes/graphtheory2-dec2010.pdf · lowest common ancestor parent[node][i] : the 2ith...
TRANSCRIPT
<Graph Theory 10>
December 11111011010
"Everything is a graph, and a graph is everything."
Benjamin Burton, (year unknown)
Trees
● Connected and has N nodes, N1 edges
● Connected and has no cycles
● If any edge is added, exactly one cycle is formed
● If any edge is removed, the graph is disconnected
● There exists one unique path between any two nodes
● Trees can be rooted or unrooted.
● Nodes have one parent and zero or more children.
● Leaves have zero children.
● Every node is a subtree
Funky tree problems
● Height (maxdepth) of a tree:– depth[node] = depth[parent]+1
– height(node) = if no children: 0else: max(height(child) for each child )+1
● Breadth of a tree– Count how many nodes at each depth, output the highest count.
● Diameter of a tree– diameter(node) = depth[deepest child]
+ depth[2nd deepest child]
– Pick node with largest diameter.
● Length of path between two nodes
Lowest Common Ancestor
Lowest Common Ancestor
● Store the parent of each node● Store the height of each node:
– height[node] = height[parent[node]] + 1
● We could store the grandparent of each node– gparent[node] = parent[parent[node]]
● And store their greatgreatgrandparents...– gggparent[node] = gparent[gparent[node]]
● etc.
Lowest Common Ancestor
● parent[node][i] : the 2ith ancestor of 'node'● parent[node][0] : its normal parent (20 = 1)● parent[node][1] : its grandparent (21 = 2)● parent[node][2] : its gggrandparent (22 = 4)● parent[node][3] : 23 = 8 nodes up the tree● This is really cool because:
– parent[node][i] = parent[parent[node][i1]][i1]
● With 'k' sweeps of the whole tree, we can easily precompute the parent[][] table
Lowest Common Ancestor
● If parent[ node ] [ k ] = 0, then it doesn't have a 2k th parent (ie. its level is < 2k)
● In logarithmic time, we can now calculate the node 'm' steps up the tree for any node.
set parent[i][j] to 0 for all i,jinput parent[i][0] for all i
for (i = 1; i <= MAX_K; i++) for (j = 1; j <= n; j++)
parent[j][i] = parent[ parent[j][i1] ][i1];
Lowest Common Ancestor
● Wait a minute Jarrah, what has this got to do with lowest common ancestor?
//get the node 'm' steps up from node 'node'// MAX_K is defined to be log
2(maximum nodes in tree)
for (k = MAX_K; k >= 0; k) {if (pow2(k) <= m) {
node = parent[node][k];m = pow2(k);
}}return node;
Lowest Common Ancestor
● Two nodes 'a' and 'b'. lca(a, b) = ??– 1) Get depth of a and b : O(1) after precomp.– 2) If depth[a] > depth[b]
a = parent(a, depth[b]depth[a])Likewise for b.
– 3) Now they're at the same level, yay! Their LCA must be the same distance from each. Using our epic expoparential array, we effectively binary search for the LCA.
Lowest Common Ancestor
// get the lca of 'a' and 'b'// MAX_K is defined to be log
2(maximum nodes in tree)
for (k = MAX_K; k >= 0; k) {if (parent[a][k] != parent[b][k]) {
a = parent[a][k];b = parent[b][k];
}}assert(a == b);return a;
● In logarithmic time, you have found the lowest common ancestor.
● Yay! But how does this help us again?YOU JUST LOST THE GAME
Length of path in a tree
● 1) Root the tree
● 2) Precompute epic parents
● 3) The length of the path from a to b isdepth[a] – depth[lca(a,b)]
+ depth[b] – depth[lca(a, b)]
● If the edges were weighted, then instead of using 'depth' in the final formula, we would use 'distance', where distance[a] is the distance from 'a' to the root node. This is precomputable in linear time – distance[node] = weight(node, parent) + distance[parent]
Spanning trees
● Facebook has changed their privacy settings so that you can view all default public information (name, phone, address, photos, realtime footage from cameras near your current location, etc.) for any friendofafriendofafriend.......ofafriend. Find a (maximal) set of friendships that can be broken while still allowing everyone to view information on everyone else.
Spanning trees
● Any DFS will do – each node is processed only once, so if we store the edge used to reach it, we have a 'parent' for every node. We need n1 edges to remain intact for the n nodes to stay connected.
● But what if the edges are weighted? The NBN network wants to connect every house in Australia using minimum total wiring.
Minimal spanning trees
● Prim's Algorithm
for (i = 0; i < n; i++) {
next = 1;
for (j = 0; j < n; j++)if (!intree[j] && (next < 0 || dist[j] < dist[next]))
next = j;
intree[next] = true;
for (j = 0; j < n; j++)if (!intree[j] && weight(next, j) < dist[j]) {
dist[j] = weight(next, j);parent[j] = next;
}}
Minimal Spanning Trees
● Kruskal's Algorithm
sort edges by weight ascending
for (i = 0; i < num_edges; i++) {
if (!path_exists(edge[i].u, edge[i].v)) {add_to_tree(edge[i]);
}}
State explosion
+
Graphs are everywhere
● In data structures, you have lots of trees.● In DP, you have nodes (states or subproblems)
with edges (recurrence relations). These graphs are directed and acyclic. (DAGs)
● Sometimes, even when a graph is staring at you in a problem, it may be the case that these are not the nodes you're looking for.
Colour Circles
● You are given a graph with nodes and edges, each node labelled a colour, and each edge labelled a colour. You have two counters, and the two nodes that they start on. You can move a counter along an edge only if the other counter is on a node of the same colour as the edge. Your goal is to get either counter into a 'target' node in the smallest number of moves.
Colour Circles
● 'Smallest number of moves'? That sounds like a......... shortest path problem! But then who was graph?
● By exploding the nodes of the graph to accommodate for the extra information we need (what node the other counter is on), we can create new edges between these nodes and transform the graph into one that a simple BFS will work on.
Colour Circles
● Vertex = pair of numbers:(node of counter A, node of counter B)
● We can travel from vertex U (p,q) to V (x,y) if: p = x and colour(q>y) = colour(p), or q = y and colour(p>x) = colour(q)
● Each vertex in this transformed graph corresponds to a state, each edge to a move.
● The line is blurry between 'state explosion' and 'creating an abstract graph from thin air', but the idea is what's important.
What idea?
● We know we have to explode our states when we need a bunch of information to decide our moves, rather than just a simple location.– e.g. what weapons I have in a dungeon might
determine what monsters I can kill, hence which rooms I can enter.
● So to fix this, instead of BFSing 'dist[node]', we BFS on 'dist[node][other information]' and the combined state (node+information) is the new node in our transformed graph.
Other cool tricks
● Oh noes, out of time? Ask me later about...– Multisourcing shortest path, e.g. given the
locations of all the tutors + students in a graph, find the distance from every student to their closest tutor, in normal Dijkstra time complexity.
– Topsort, to find an ordering of a DAG so that every node appears before all its children.
– Hare and Tortoise: if every state has one outgoing edge, find when you hit a cycle (e.g. Relocation)
– Eulerian Paths – find a path going through every single edge once and only once
</Graph Theory 10>
"Except some things aren't graphs." Benjamin Burton, (year known)