{"id":456,"date":"2007-06-28T19:59:54","date_gmt":"2007-06-28T19:59:54","guid":{"rendered":"http:\/\/scientopia.org\/blogs\/goodmath\/2007\/06\/28\/graph-coloring-algorithms\/"},"modified":"2007-06-28T19:59:54","modified_gmt":"2007-06-28T19:59:54","slug":"graph-coloring-algorithms","status":"publish","type":"post","link":"http:\/\/www.goodmath.org\/blog\/2007\/06\/28\/graph-coloring-algorithms\/","title":{"rendered":"Graph Coloring Algorithms"},"content":{"rendered":"<p>Graph coloring is deceptively simple. The idea of coloring a graph is very straightforward, and it seems as if it should be relatively straightforward to find a coloring. It turns out to not be &#8211; in fact, it&#8217;s extremely difficult. A simple algorithm for graph coloring is easy to describe, but potentially extremely expensive to run.<\/p>\n<p><!--more--><br \/>\nIn terms of computational complexity &#8211; the measure that we computer scientists use to describe how hard it is to compute a solution to a problem &#8211; it&#8217;s actually NP-hard. What that means that there is no known algorithm for optimal graph coloring which isn&#8217;t exponential; and that further, if there *were* a non-exponential algorithm for it, there would be a non-exponential solution for *all* NP-complete problems, but a non-exponential solution to another NP-complete problem wouldn&#8217;t necessarily produce a non-exponential time solution for graph coloring! (See [here][np] for some explanation of what this P and NP stuff is all about.)<br \/>\n[np]: http:\/\/scienceblogs.com\/goodmath\/2007\/01\/basic_complexity_classes_p_and_1.php<br \/>\nIn fact, the *only* general solution to finding an optimal graph coloring is exhaustive search: start with one node, give it a color, assign non-conflicting colors to its neighbors, and so on. Try it with two colors, if you get no result, then try with three, and so on. There are a lot of fancy algorithms that try to improve on that &#8211; both by reducing the search space, and by using heuristics. With a combination of those two<br \/>\ntechniques, we can get colorings to be quite efficient in the average case &#8211; but if always want the optimal coloring of *any* graph, then there&#8217;s no way (or at least, no way that anyone knows about) to always get the optimal result quickly.<br \/>\nFor an idea of what I mean by heuristics, here&#8217;s one very simple algorithm:<br \/>\nGiven G=(V,E):<br \/>\nCompute Degree(v) for all v in V.<br \/>\nSet uncolored = V sorted in decreasing order of Degree(v).<br \/>\nset currentColor = 0.<br \/>\nwhile there are uncolored nodes:<br \/>\nset A=first element of uncolored<br \/>\nremove A from uncolored<br \/>\nset Color(A) = currentColor<br \/>\nset coloredWithCurrent = {A}<br \/>\nfor each v in uncolored:<br \/>\nif v is not adjacent to anything in coloredWithCurrent:<br \/>\nset Color(v)=currentColor.<br \/>\nadd v to currentColor.<br \/>\nremove v from uncolored.<br \/>\nend if<br \/>\nend for<br \/>\ncurrentColor = currentColor + 1.<br \/>\nend while<br \/>\nThis is, implicitly, trying out every possible combination of colors: you can read<br \/>\n&#8220;for each v&isin;uncolored:&#8221; as &#8220;try assigning the current color to v, and see if it gives<br \/>\nan invalid coloring&#8221;. But by stopping the instant it tries to assign an invalid coloring,<br \/>\nit can prune that coloring out without completing it. That&#8217;s one of its tricks for<br \/>\nbeing fast on average: quickly eliminating invalid colorings.<br \/>\nThe other trick in this algorithm is a heuristic: *on average*, searching for colorings<br \/>\nstarting with the highest degree uncolored nodes will find an optimal result faster than<br \/>\nassigning colorings to nodes in random order. In the average case, this will perform<br \/>\nwell &#8211; and run in less that exponential time. But there are definitely graphs where this<br \/>\nheuristic fails, and this algorithm will be a disaster.<br \/>\nThe fortunate thing is that many of the most common applications of graph coloring<br \/>\nare running on relatively small graphs, and often, the coloring doesn&#8217;t really need to be optimal &#8211; just *close*.<br \/>\nFor example, one of the uses of graph coloring that I&#8217;m particularly familiar with is in<br \/>\ncompilers. In a compiler, one of the tricks to make code run faster is to store values in<br \/>\nCPU registers that are part of the CPU itself, rather than in memory which is out on the motherboard. Things stored in registers can be manipulated dramatically faster than things in memory: for example, on the Core Due processor in my MacBook, it takes 10 clock cycles to retrieve a single value from memory into the processor cache; it takes 2 cycles to get memory from the cache into the CPU. Values in a register can be accessed with *no* wait. So naturally, it makes a big difference in the performance of a program which values wind up in registers, and which don&#8217;t.<br \/>\n<img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" alt=\"register.jpg\" src=\"https:\/\/i0.wp.com\/scientopia.org\/img-archive\/goodmath\/img_167.jpg?resize=226%2C309\" width=\"226\" height=\"309\" class=\"inset right\" \/><br \/>\nGreg Chaitin (the same Chaitin of information theory), along with several other people at IBM research, [figured out that deciding how to allocate registers to variables in a program was really a graph coloring problem.][chaitin] If you lay out the program on a page, and draw a line from the *first* time a variable is given a value until the *last* time that variable is<br \/>\nused, that line is called the *lifetime* of the variable. Now, do that for all of the variables. And then build a graph: every variable in the graph is a vertex, and there&#8217;s an edge between two variables if and only if their lifetimes overlap. (The diagram to the right illustrates this: the vertical bars are the lifetimes of the different variables; beneath is the graph inferred from overlapping lifetimes.)  Then, you color the graph &#8211; with each color corresponding to a register. If you can color the entire graph with no more colors than there are registers, then you can just keep everything in registers. Otherwise, you need to figure out what variables to cut from the graph in order to produce a colorable graph; each time you cut a variable from the graph, you&#8217;re saying that it&#8217;s going to stay in memory, rather than in a register.<br \/>\nRegister allocation is typically done over relatively small regions of code &#8211; subroutines, rather than entire programs. Within a subroutine, you rarely see code with more than a couple of dozen variables that you&#8217;d like to store in registers &#8211; which means that you&#8217;re rarely going to try to do coloring of a graph with more that a couple of dozen nodes &#8211; and even then, if you use a *nearly* optimal coloring rather than a truly optimal one, you&#8217;ll be making the program a bit slower, but not causing it to generate incorrect results.<br \/>\n[chaitin]: http:\/\/en.wikipedia.org\/wiki\/Register_allocation<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Graph coloring is deceptively simple. The idea of coloring a graph is very straightforward, and it seems as if it should be relatively straightforward to find a coloring. It turns out to not be &#8211; in fact, it&#8217;s extremely difficult. A simple algorithm for graph coloring is easy to describe, but potentially extremely expensive to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[25],"tags":[],"class_list":["post-456","post","type-post","status-publish","format-standard","hentry","category-graph-theory"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p4lzZS-7m","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/posts\/456","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/comments?post=456"}],"version-history":[{"count":0,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/posts\/456\/revisions"}],"wp:attachment":[{"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/media?parent=456"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/categories?post=456"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.goodmath.org\/blog\/wp-json\/wp\/v2\/tags?post=456"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}