Clique problem algorithm design - algorithm

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.

Related

Maximize sum of values of K courses if all prerequisites must be met

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.

Proof that using adjacency matrix for bipartite testing has Ξ©(n^2)

so in one of my lectures I came across the proof for:
π“π‘πžπ¨π«πžπ¦: 𝐴𝑛𝑦 algorithm that determines if a graph is bipartite
that has as its input an undirected graph 𝐺 = (𝑉, 𝐸) represented
as an 𝑛 Γ— 𝑛 adjacency matrix, has the running time of Ξ©(𝑛^2)
We assume an algorithm ALG which test for bipartiteness (returns either true or false). And we also assume we have a graph 𝐺0 = (𝑉, 𝐸0) with 𝑉 = {1,2, … , 𝑛} and 𝐸0 = { 1, 𝑖 : 2 ≀ 𝑖 ≀ 𝑛} (as this is a star it is a bipartite graph)
Within the proof there's a step saying:
"For a given algorithm ALG, we will construct another graph 𝐺1 st: if ALG performs less than (π‘›βˆ’1)C2 accesses to the adjacency matrix 𝐴 of 𝐺0,
then ALG will not distinguish between 𝐺0 and 𝐺1, and 𝐺1 is not bipartite."
My question is what does (n-1)C2 accesses mean. Is it saying that for example if we have a different V = {A,B,C,D} then ALG will look at all node pairs except for the ones between D and the other nodes ?
Sorry if this isn't clear this proof really confused me.
G0 is an n-vertex star graph. It's bipartite, but if you add any other edge to it, the resulting graph is not. There are nβˆ’1 choose 2 = (nβˆ’1)(nβˆ’2)/2 = Ξ©(n2) other edges that we can add. Every correct algorithm must check every single one in order to verify that G0 is bipartite.

Quadratic-time vertex cover verification

Suppose you are given an undirected graph G with n vertices and m
edges represented by an n x n adjacency matrix A, and you are also
given a subset of vertices S (represented by an array of size m).
How can you check whether S is a vertex cover of G with quadratic
time and space complexity?
By the definition of a vertex cover, I know that we require every edge must be incident to a vertex that's contained in S.
I can easily come up with a cubic algorithm: iterate over the adjacency matrix; each 1 represents an edge (u, v). Check whether u or v are in S. If not, the answer is no. If we get to the end of the adjacency matrix, the answer is yes.
But how can I do this in O(n^2) time? I guess the only real "observation" I've made so far is that we can possibly skip intermediate rows while iterating over the adjacency matrix if we've already found the vertex corresponding to that row in S. However, this has not helped me very much.
Can someone please help me (or point me in the correct direction)?
Thanks
Construct an array T which is the positions of all of the elements NOT in S.
And then:
for i in T:
for j in T:
if A[i][j] == 1:
return False
return True

Find a path in a complete graph with cost limit and max reward

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.

Algorithm for polygon with weight on vertices and operations on edges

I am thinking about the algorithm for the following problem (found on carrercup):
Given a polygon with N vertexes and N edges. There is an int number(could be negative) on every vertex and an operation in set(*,+) on every edge. Every time, we remove an edge E from the polygon, merge the two vertexes linked by the edge(V1,V2) to a new vertex with value: V1 op(E) V2. The last case would be two vertexes with two edges, the result is the bigger one.
Return the max result value can be gotten from a given polygon.
I think we can use just greedy approach. I.e. for polygon with k edges find a pair (p, q) which produces the maximum number when collapsing: (p ,q) = max ({i operation j : i, j - adjacent edges)
Then just call a recursion on polygons:
1. Let function CollapseMaxPair( P(k) ) - gets polygon with k edges and returns 'collapsed' polygon with k-1 edges
2. Then our recursion:
P = P(N);
Releat until two edges left
P = CollapseMaxPair( P )
maxvalue = max ( two remained values)
What do you think?
I have answered this question here: Google Interview : Find the maximum sum of a polygon and it was pointed out to me that that question is a duplicate of this one. Since no one has answered this question fully yet, I have decided to add this answer here as well.
As you have identified (tagged) correctly, this indeed is very similar to the matrix multiplication problem (in what order do I multiply matrixes in order to do it quickly).
This can be solved polynomially using a dynamic algorithm.
I'm going to instead solve a similar, more classic (and identical) problem, given a formula with numbers, addition and multiplications, what way of parenthesizing it gives the maximal value, for example
6+1 * 2 becomes (6+1)*2 which is more than 6+(1*2).
Let us denote our input a1 to an real numbers and o(1),...o(n-1) either * or +. Our approach will work as follows, we will observe the subproblem F(i,j) which represents the maximal formula (after parenthasizing) for a1,...aj. We will create a table of such subproblems and observe that F(1,n) is exactly the result we were looking for.
Define
F(i,j)
- If i>j return 0 //no sub-formula of negative length
- If i=j return ai // the maximal formula for one number is the number
- If i<j return the maximal value for all m between i (including) and j (not included) of:
F(i,m) (o(m)) F(m+1,j) //check all places for possible parenthasis insertion
This goes through all possible options. TProof of correctness is done by induction on the size n=j-i and is pretty trivial.
Lets go through runtime analysis:
If we do not save the values dynamically for smaller subproblems this runs pretty slow, however we can make this algorithm perform relatively fast in O(n^3)
We create a n*n table T in which the cell at index i,j contains F(i,j) filling F(i,i) and F(i,j) for j smaller than i is done in O(1) for each cell since we can calculate these values directly, then we go diagonally and fill F(i+1,i+1) (which we can do quickly since we already know all the previous values in the recursive formula), we repeat this n times for n diagonals (all the diagonals in the table really) and filling each cell takes (O(n)), since each cell has O(n) cells we fill each diagonals in O(n^2) meaning we fill all the table in O(n^3). After filling the table we obviously know F(1,n) which is the solution to your problem.
Now back to your problem
If you translate the polygon into n different formulas (one for starting at each vertex) and run the algorithm for formula values on it, you get exactly the value you want.
Here's a case where your greedy algorithm fails:
Imagine your polygon is a square with vertices A, B, C, D (top left, top right, bottom right, bottom left). This gives us edges (A,B), (A,D), (B,C), and (C, D).
Let the weights be A=-1, B=-1, C=-1, and D=1,000,000.
A (-1) ------ B (-1)
| |
| |
| |
| |
D(1000000) ---C (-1)
Clearly, the best strategy is to collapse (A,B), and then (B,C), so that you may end up with D by itself. Your algorithm, however, will start with either (A,D) or (D,C), which will not be optimal.
A greedy algorithm that combines the min sums has a similar weakness, so we need to think of something else.
I'm starting to see how we want to try to get all positive numbers together on one side and all negatives on the other.
If we think about the initial polygon entirely as a state, then we can imagine all the possible child states to be the subsequent graphs were an edge is collapsed. This creates a tree-like structure. A BFS or DFS would eventually give us an optimal solution, but at the cost of traversing the entire tree in the worst case, which is probably not as efficient as you'd like.
What you are looking for is a greedy best-first approach to search down this tree that is provably optimal. Perhaps you could create an A*-like search through it, although I'm not sure what your admissable heuristic would be.
I don't think the greedy algorithm works. Let the vertices be A = 0, B = 1, C = 2, and the edges be AB = a - 5b, BC = b + c, CA = -20. The greedy algorithm selects BC to evaluate first, value 3. Then AB, value, -15. However, there is a better sequence to use. Evaluate AB first, value -5. Then evaluate BC, value -3. I don't know of a better algorithm though.

Resources