From: Andreas K. <and...@ac...> - 2009-06-25 23:00:53
|
Andreas Kupries wrote: > Michał Antoniewski wrote: >> Hello. >> >> I updated some tests for Max-Cut ( testing subprocedures seperately ). > > Ok, will review. > 515: #create complete graph > 516: foreach v [$oddTGraph nodes] { > 517: foreach u [$oddTGraph nodes] { > 518: if { ![$oddTGraph arc exists $u$v] & ($u ne $v) This triggered when I was reading the max-cut code. Tcl does short-cut evaluation of conditions. In other words, the code above will currently always look for a loop-edge for u == v. It is better to write the simple condition u != v first, and when it returns false the more complex 'arc exists' check is never done. Also note that using & is likely a bug. The &-operator is the bitwise-and. You likely want the &&-operator, the 'logical and'. if { ($u ne $v) && ![$oddTGraph arc exists $u$v] } { > 554: > 555: proc ::struct::graph::op::findHamiltonCycle {G originalEdges originalGraph} { > 556: > 557: isEulerian? $G tourvar > 558: lappend result [$G arc source [lindex $tourvar 0]] > 559: > 560: lappend tourvar [lindex $tourvar 0] > 561: > 562: foreach i $tourvar { > 563: set u [$G arc target $i] > 564: > 565: if { $u ni $result } { > 566: set va [lindex $result end] > 567: set vb $u > 568: > 569: if { ("$va $vb" in $originalEdges) || ("$vb $va" in $originalEdges) } { [list $va $vb] etc. in all places (i.e. createCompleteGraph). The use of "" still allows the construction of colliding edge names. Ah, I note that the code in line 643 actually uses [list] to create arc names. So, no collisions, but still a bug. Because you have to use [list] here as well, when searching for the arcs. With the current code I can give you node names which become arc names you will not find. Example nodes 'a b' and 'c', the arc name constructed by [list] from that is '{a b} c', and you are searching for 'a b c' because of "..." instead of list. An optimization to consider in the future is to make the case of a complete graph as input fast again. I.e. createCompleteGraph could note when it did not add any arcs, and this information can be used here to avoid the searches by 'in'. An alternative providing the same information would be to record the arcs which were added instead of the originals, and then replace the condition with the complement, i.e. 'arc ni addedArcs'. A third alternative would be to special case this procedure, i.e. check if no arcs were added first, and use the original code and loop in that case, without checking at each step. > 570: lappend result $u > 571: } else { > 572: > 573: set path [dict get [struct::graph::op::dijkstra $G $va] $vb] > 574: > 575: #reversing the path > 576: set path [lreverse $path] > 577: #cutting the start element > 578: lremove path [lindex $path 0] Cutting the start element is easier by using the builtin command 'lrange'. set path [lrange $path 1 end] Even a direct use of 'lreplace' is easier than to run lindex, then lsearch (inside of lremove), and then lreplace. We know it is in index 0, no search is needed. The builtins lindex, lrange, lreplace are all index based. > 579: > 580: #adding the path and the target element > 581: lappend result $path The result is a list of nodes. What is added here is not a node as element, but a list of node, making the result a list of (nodes and lists of nodes). Likely meant is lappend result {*}$path which puts the nodes in the path into the result as independent nodes, and not as single list (list expansion operator). > 582: lappend result $vb > 583: } > 584: } > 585: } > 586: > 587: set path [dict get [struct::graph::op::dijkstra $originalGraph [lindex $result 0]] [lindex $result end]] > 588: set path [lreverse $path] > 589: > 590: lremove path [lindex $path 0] See above at line 578. > 591: if { $path ne {} } { path is a list, should be checked using a list operation. 'ne' is a string operation. if {[llength $path]} ... > 592: lappend result $path See above at line 581. > 593: } > 594: lappend result [$G arc source [lindex $tourvar 0]] > 595: return $result > 596: } > 597: > 628: proc ::struct::graph::op::createCompleteGraph {G originalEdges} { > 629: > 630: upvar $originalEdges st > 631: set st {} > 632: foreach e [$G arcs] { > 633: set v [$G arc source $e] > 634: set u [$G arc target $e] > 635: > 636: lappend st "$v $u" Use [list] instead of "...", see above at line 569. > 637: } > 638: > 639: foreach v [$G nodes] { > 640: foreach u [$G nodes] { > 641: if { ("$v $u" ni $st) && ("$u $v" ni $st) && ($u ne $v) && ![$G arc exists [list $u $v]] } { Use [list] instead of "...", see above at line 569 as well Also, move the condition ($u ne $v) to the front, see my comments at line 518 about short-cut evaluation of conditions. > 642: $G arc insert $v $u [list $v $u] > 643: $G arc setweight [list $v $u] Inf Use of [list] is good. > 650: proc ::struct::graph::op::lreverse l { > 651: set result {} > 652: set i [llength $l] > 653: incr i -1 > 654: while {$i >= 0} { > 655: lappend result [lindex $l $i] > 656: incr i -1 > 657: } > 658: return $result > 659: } This procedure is not needed. Tcl 8.5 has a lreverse builtin command. > 780: #K Center Problem - 2 approximation algorithm > 781: #------------------------------------------------------------------------------------- > 782: # > 783: # > 784: # > 785: # > 786: # > 787: > 788: #subprocedure creating graph with given set of edges > 789: proc ::struct::graph::op::createGi {G E} { > 790: > 791: set Gi [struct::graph] > 792: > 793: foreach v [$G nodes] { > 794: $Gi node insert $v > 795: } > 796: > 797: foreach e $E { > 798: set v [$G arc source $e] > 799: set u [$G arc target $e] > 800: > 801: $Gi arc insert $v $u $e > 802: } > 803: > 804: return $Gi > 805: } > 806: > 807: #subprocedure creating from graph G two squared graph > 808: #G^2 - graph in which edge between nodes u and v exists, > 809: #if and only if, when distance (in edges, not weights) > 810: #between those nodes is not greater than 2 and u != v. > 811: > 812: proc ::struct::graph::op::createTwoSquaredGraph {G} { > 813: > 814: set H [struct::graph] > 815: set copyG [struct::graph] > 816: > 817: foreach v [$G nodes] { > 818: $H node insert $v > 819: $copyG node insert $v > 820: } > 821: > 822: foreach e [$G arcs] { > 823: set v [$G arc source $e] > 824: set u [$G arc target $e] > 825: > 826: $copyG arc insert $v $u [list $v $u] > 827: $copyG arc setweight [list $v $u] 1 > 828: } > 829: > 830: foreach v [$copyG nodes] { > 831: foreach u [$copyG nodes] { > 832: if { [distance $copyG $v $u] <= 2 && ![$H arc exists [list $u $v]] && ($u ne $v)} { My understanding is that copyG is simply an exact copy of G, just with arc weights forced to 1 for the distance calculation, right ? Move condition ($u ne $v) to the front to stop execution of the expensive distance check for u == v. I believe that we should move the 'arc exists' check before the distance computation as well. Right now we even compute distances which do not matter because of the other conditions. An optimization should be possible here ... The distance command is dijsktra underneath, which has approx complexity O(n**2) in the number of nodes (depending on the complexity of our prioqueue we may get O(n*log n + e) ... The double loop around the distance calls here is complexity O(n**2), for a total of O(n**4) complexity. If we run dijkstra for each node once, saving the distance results then we have O(n**3) complexity, with the double loop here reduced to O(n**2), not that it then matters. > 833: $H arc insert $v $u [list $v $u] > 834: } > 835: } > 836: } > 837: copyG is not deleted, memory leak. > 838: return $H > 839: } Andreas |