コード例 #1
0
bool LocalBiconnectedMerger::canMerge( Graph &G, node parent, node mergePartner, int testStrength )
{
	if ( parent->degree() <= 2 || mergePartner->degree() <= 2 || m_isCut[parent] || m_isCut[mergePartner] ) {
		return true;
	}

	unsigned int nodeLimit = (int)log((double)G.numberOfNodes()) * 2 + 50;
	unsigned int visitedNodes = 0;
	m_realNodeMarks.clear();
	HashArray<node, int> nodeMark(-1);

	HashArray<node, bool> seen(false);
	seen[parent] = true;
	seen[mergePartner] = true;

	HashArray<node, int> neighborStatus(0);
	neighborStatus[parent] = -1;
	neighborStatus[mergePartner] = -1;

	List<node> bfsQueue;
	List<node> neighbors;
	int minIndex = numeric_limits<int>::max();
	adjEntry adj;
	forall_adj(adj, parent) {
		node temp = adj->twinNode();
		bfsQueue.pushBack(temp);
		nodeMark[temp] = temp->index();
		if(neighborStatus[temp] == 0) {
			neighbors.pushBack(temp);
			neighborStatus[temp] = 1;
			if (temp->index() < minIndex) {
				minIndex = temp->index();
			}
		}
	}
コード例 #2
0
void SubgraphUpwardPlanarizer::dfsMerge(
	const GraphCopy &GC,
	BCTree &BC,
	NodeArray<GraphCopy> &biComps,
	NodeArray<UpwardPlanRep> &uprs,
	UpwardPlanRep &UPR_res,
	node parent_BC,
	node current_BC,
	NodeArray<bool> &nodesDone)
{
	// only one component.
	if (current_BC->degree() == 0) {
		merge(GC, UPR_res, biComps[current_BC], uprs[current_BC]);
		return;
	}

	for(adjEntry adj : current_BC->adjEntries) {
		node next_BC = adj->twin()->theNode();
		if (BC.typeOfBNode(current_BC) == BCTree::CComp) {
			if (parent_BC != nullptr && !nodesDone[parent_BC]) {
				merge(GC, UPR_res, biComps[parent_BC], uprs[parent_BC]);
				nodesDone[parent_BC] = true;
			}
			if (!nodesDone[next_BC]) {
				merge(GC, UPR_res, biComps[next_BC], uprs[next_BC]);
				nodesDone[next_BC] = true;
			}
		}
		if (next_BC != parent_BC )
			dfsMerge(GC, BC, biComps, uprs, UPR_res, current_BC, next_BC, nodesDone);
	}
}
コード例 #3
0
//insert a copy for original node v
void SimpleIncNodeInserter::insertCopyNode(node v, Graph::NodeType vTyp)
{
	OGDF_ASSERT(m_planRep->copy(v) == 0)

	//insert a new node copy
	node vCopy = m_planRep->newCopy(v, vTyp);
	if (v->degree() == 0) return;
	//insert all adjacent edges to already inserted nodes
	adjEntry adjOrig = v->firstAdj();
	do
	{
		node wOrig = adjOrig->twinNode();
		node wCopy = m_planRep->copy(wOrig);
		edge e = adjOrig->theEdge();
		if (wCopy && (m_planRep->chain(e).size() == 0))
		{
			//inserted edge copy
			//edge eCopy;
			//newCopy can cope with zero value for adjEntry
			if (v == e->source())
				/* eCopy = */ m_planRep->newCopy(vCopy, wCopy->firstAdj(), e);
			else
				/* eCopy = */ m_planRep->newCopy(wCopy, vCopy->firstAdj(), e);

			//TODO: update component number in planrepinc

		}//if edge to be inserted
		adjOrig = adjOrig->cyclicSucc();
	} while (adjOrig != v->firstAdj());
}//insertCopyNode
コード例 #4
0
void biconnectivity::new_start_handler(graph& /*G*/, node& st)
{
	cut_count[st] = -1;

	//
	// If this node has no adjacent edges, we
	// must write down the component right here. This is because
	// then the method after_recursive_call_handle is never
	// executed.
	//
	// 28/2/2002 MR
	// 

	if (st.degree() == 0) {
		++num_of_components;

		if (store_comp) {
			component_iterator li = components.insert(
				components.end(),
				std::pair<nodes_t, edges_t>(nodes_t(), edges_t()));

			li->first.push_back(st);
			in_component[st] = li;
		}
	}
}
コード例 #5
0
ファイル: UpwardPlanRep.cpp プロジェクト: ogdf/ogdf
void UpwardPlanRep::constructSinkArcs(face f, node t)
{
	List<adjEntry> srcList;

	if (f != m_Gamma.externalFace()) {
		for(adjEntry adj : f->entries) {
			node v = adj->theNode();
			if (v == adj->theEdge()->target()
			 && v == adj->faceCyclePred()->theEdge()->target()
			 && v != t) {
				srcList.pushBack(adj);
				// XXX: where is adjTgt used later?
				// do we have to set adjTgt = adj (top-sink-switch of f) if v != t?
			}
		}
		// contruct the sink arcs
		while(!srcList.empty()) {
			adjEntry adjSrc = srcList.popFrontRet();
			edge eNew;
			if (t->degree() == 0)
				eNew = m_Gamma.addEdgeToIsolatedNode(adjSrc, t);
			else {
				adjEntry adjTgt = getAdjEntry(m_Gamma, t, m_Gamma.rightFace(adjSrc));
				eNew = m_Gamma.splitFace(adjSrc, adjTgt);
			}
			m_isSinkArc[eNew] = true;
		}
	}
	else {
		for(adjEntry adj : f->entries) {
			node v = adj->theNode();

			OGDF_ASSERT(s_hat != nullptr);

			if (v->outdeg() == 0 && v != t_hat)
				srcList.pushBack(adj);
		}

		// contruct the sink arcs
		while(!srcList.empty()) {
			adjEntry adjSrc = srcList.popFrontRet();
			adjEntry adjTgt;
			if (adjSrc->theNode() == adjSrc->theEdge()->source()) // on the right face part of the ext. face
				adjTgt = extFaceHandle;
			else
				adjTgt = extFaceHandle->cyclicPred(); // on the left face part

			auto eNew = m_Gamma.splitFace(adjSrc, adjTgt);
			m_isSinkArc[eNew] = true;
		}

	}
}
コード例 #6
0
    forall_nodes(v,PG)
    {
        if (PG.typeOf(v) == PlanRepUML::dummy && v->degree() == 4) {
            adjEntry adj = v->firstAdj();

            edge e1 = adj->theEdge();
            edge e2 = adj->succ()->theEdge();

            if (PG.typeOf(e1) == Graph::generalization &&
                    PG.typeOf(e2) == Graph::generalization)
                return false;
        }
    }
コード例 #7
0
ファイル: PlanRep.cpp プロジェクト: lncosie/ogdf
void PlanRep::removeCrossing(node v)
{
	OGDF_ASSERT(v->degree() == 4)
	OGDF_ASSERT(isCrossingType(v))

	adjEntry a1 = v->firstAdj();
	adjEntry b1 = a1->cyclicSucc();
	adjEntry a2 = b1->cyclicSucc();
	adjEntry b2 = a2->cyclicSucc();

	removeUnnecessaryCrossing(a1, a2, b1, b2);


}//removeCrossing
コード例 #8
0
ファイル: MultilevelGraph.cpp プロジェクト: ogdf/ogdf
bool MultilevelGraph::postMerge(NodeMerge * NM, node merged)
{
	// merged has no more edges!
	int index = merged->index();
	if (merged->degree() == 0 && NM->m_changedNodes.size() > 0) {
		NM->m_mergedNode = index;
		NM->m_radius[index] = m_radius[index];
		m_changes.push_back(NM);
		m_G->delNode(merged);
		m_reverseNodeIndex[index] = nullptr;
		return true;
	} else {
		return false;
	}
}
コード例 #9
0
void CombinatorialEmbedding::removeDeg1(node v)
{
	OGDF_ASSERT(v->degree() == 1);

	adjEntry adj = v->firstAdj();
	face     f   = m_rightFace[adj];

	if (f->entries.m_adjFirst == adj || f->entries.m_adjFirst == adj->twin())
		f->entries.m_adjFirst = adj->faceCycleSucc();
	f->m_size -= 2;

	m_pGraph->delNode(v);

	OGDF_ASSERT_IF(dlConsistencyChecks, consistencyCheck());
}
コード例 #10
0
//--
//-----------------
//incremental stuff
//special version of the above function doing a pushback of the new edge
//on the adjacency list of v making it possible to insert new degree 0
//nodes into a face, end node v
edge CombinatorialEmbedding::splitFace(adjEntry adjSrc, node v)
{
	adjEntry adjTgt = v->lastAdj();
	edge e = nullptr;
	bool degZ = v->degree() == 0;
	if (degZ)
	{
		e = m_pGraph->newEdge(adjSrc, v);
	}
	else
	{
		OGDF_ASSERT(m_rightFace[adjSrc] == m_rightFace[adjTgt])
		OGDF_ASSERT(adjSrc != adjTgt)
		e = m_pGraph->newEdge(adjSrc, adjTgt); //could use ne(v,ad) here, too
	}

	face f1 = m_rightFace[adjSrc];
	//if v already had an adjacent edge, we split the face in two faces
	int subSize = 0;
	if (!degZ)
	{
		face f2 = createFaceElement(adjTgt);

		adjEntry adj = adjTgt;
		do
		{
			m_rightFace[adj] = f2;
			f2->m_size++;
			adj = adj->faceCycleSucc();
		} while (adj != adjTgt);
		subSize = f2->m_size;
	}//if not zero degree
	else
	{
		m_rightFace[e->adjSource()] = f1;
	}

	f1->entries.m_adjFirst = adjSrc;
	f1->m_size += (2 - subSize);
	m_rightFace[e->adjTarget()] = f1;

	OGDF_ASSERT_IF(dlConsistencyChecks, consistencyCheck());

	return e;
}//splitface
コード例 #11
0
ファイル: UMLGraph.cpp プロジェクト: marvin2k/ogdf
//compute a drawing of the clique around node center and save its size
//the call to circular will later be replaced by an dedicated computation
DRect UMLGraph::circularBound(node center)
{

	//TODO: hier computecliqueposition(0,...) benutzen, rest weglassen
	DRect bb;
	CircularLayout cl;
	Graph G;
	GraphAttributes AG(G);
	NodeArray<node> umlOriginal(G);

	//TODO: we need to assure that the circular drawing
	//parameters fit the drawing parameters of the whole graph
	//umlgraph clique parameter members?

	OGDF_ASSERT(center->degree() > 0)
	node lastNode = nullptr;
	node firstNode = nullptr;

	adjEntry ae = center->firstAdj();
	do {
		node w = ae->twinNode();
		node v = G.newNode();
		umlOriginal[v] = w;

		if (!firstNode) firstNode = v;
		AG.width(v) = width(w);
		AG.height(v) = height(w);
		ae = ae->cyclicSucc();
		if (lastNode != nullptr) G.newEdge(lastNode, v);
		lastNode = v;
	} while (ae != center->firstAdj());
	G.newEdge(lastNode, firstNode);

	cl.call(AG);

	for(node v : G.nodes)
	{
		m_cliqueCirclePos[umlOriginal[v]] = DPoint(AG.x(v), AG.y(v));
	}
	bb = AG.boundingBox();

	return bb;
}//circularBound
コード例 #12
0
ファイル: GraphIO_dot.cpp プロジェクト: marvin2k/ogdf
static inline bool writeNode(
	std::ostream &out, const int &depth,
	const GraphAttributes *GA, const node &v
)
{
	// Write a node iff it has some attributes or has no edges.
	if(!GA && v->degree() > 0) {
		return false;
	}

	GraphIO::indent(out, depth) << v;

	if(GA) {
		out << " ";
		writeAttributes(out, *GA, v);
	}

	out << "\n";
	return true;
}
コード例 #13
0
	m_outv       .init (E, 0);
	m_oute       .init (E, 0);
	m_seqp       .init (E, 0);
	m_virtSrc    .init (E, 0);
	m_fLink      .init (E, ListIterator<face>());
	m_fUpdate    .init (E, false);
	m_isSf       .init (E, false);
	m_outerNodes .init (E);
	m_onBase     .init (G, false);

	initVInFStruct(E);

	// initialization of degree
	node v, w;
	forall_nodes(v,G)
		m_deg[v] = v->degree();

	// initialization of m_onBase[v]
	adjEntry adj;
	for(adj = m_adjRight; adj != m_adjLeft; adj = adj->faceCyclePred())
		m_onBase[adj->theNode()] = true;
	m_onBase [m_vLeft] = m_onBase [m_vRight] = true;

	adj = m_adjLeft;
	do {
		v = adj->theNode();
		adjEntry adj2;
		forall_adj(adj2,v)
		{
			face f = E.rightFace(adj2);
			if (f != m_extFace) {
コード例 #14
0
ファイル: MultilevelGraph.cpp プロジェクト: ogdf/ogdf
std::vector<edge> MultilevelGraph::moveEdgesToParent(NodeMerge * NM, node theNode, node parent, bool deleteDoubleEdges, int adjustEdgeLengths)
{
	OGDF_ASSERT(theNode != parent);

	std::vector<edge> doubleEdges;
	std::vector<edge> adjEdges;

	for(adjEntry adj : theNode->adjEntries) {
		adjEdges.push_back(adj->theEdge());
	}

	double nodeToParentLen = 0.0;
	for (edge e : adjEdges)
	{
		node newSource = e->source();
		node newTarget = e->target();
		if ((newSource == theNode && newTarget == parent)
		|| (newSource == parent && newTarget == theNode)){
			nodeToParentLen = m_weight[e];
			break;
		}
	}

	for (edge e : adjEdges)
	{
		node newSource = e->source();
		node newTarget = e->target();

		if (newSource == theNode) {
			newSource = parent;
		}
		if (newTarget == theNode) {
			newTarget = parent;
		}

		bool exists = false;
		edge twinEdge = nullptr;
		for(adjEntry adj : parent->adjEntries) {
			if (adj->twinNode() != parent && (adj->twinNode() == newSource || adj->twinNode() == newTarget)) {
				exists = true;
				twinEdge = adj->theEdge();
				double extraLength = 0.0;
				if(adjustEdgeLengths != 0) {
					extraLength = m_weight[twinEdge] + adjustEdgeLengths * nodeToParentLen;
				}
				changeEdge(NM, twinEdge, (m_weight[twinEdge] + m_weight[e] + extraLength) * 0.5f, twinEdge->source(), twinEdge->target());
				break;
			}
		}

		// has this edge already
		if (exists || newSource == newTarget) {
			doubleEdges.push_back(e);
		} else {
			changeEdge(NM, e, m_weight[e], newSource, newTarget);
		}
	}

	if (deleteDoubleEdges) {
		while (!doubleEdges.empty()) {
			deleteEdge(NM, doubleEdges.back());
			doubleEdges.pop_back();
		}
	}

	OGDF_ASSERT(theNode->degree() == (int)doubleEdges.size());

	// not deleted edges that are adjacent to theNode are returned.
	return doubleEdges;
}
コード例 #15
0
ファイル: UMLGraph.cpp プロジェクト: marvin2k/ogdf
//computes relative positions of all nodes in List cList on a minimum size
//circle (needed to compute positions with different ordering than given in *this).
//Precondition: nodes in adjNodes are adjacent to center
//first node in adjNodes is positioned to the right
void UMLGraph::computeCliquePosition(List<node> &adjNodes, node center, double rectMin)//, const adjEntry &startAdj)
{
	DRect boundingBox;
	OGDF_ASSERT(center->degree() > 0)
	OGDF_ASSERT(center->degree() == adjNodes.size())

	node v;
	double radius = 0.0;
	//TODO: member, parameter
	//const
		double minDist = 1.0;
	//TODO: necessary?
	double minCCDist = 20.0;

	ListIterator<node> itNode = adjNodes.begin();

	//--------------------------------------------------------------------------
	//for the temporary solution (scale clique to fixed rect if possible instead
	//of guaranteeing the rect size in compaction) we check in advance if the sum
	//of diameters plus dists fits into the given rect by heuristic estimate (biggest
	//node size + radius)
	if (rectMin > 0.0)
	{
		double rectDist = m_cliqueCenterSize; //dist to rect border todo: parameter
		double rectBound = rectMin - 2.0*rectDist;
		double maxSize = 0.0;
		double pureSumDiameters = 0.0;
		while (itNode.valid())
		{
			node q  =(*itNode);
			double d = sqrt(
				width(q)*width(q) + height(q)*height(q));
			pureSumDiameters += d;

			if (d > maxSize) maxSize = d;

			++itNode;
		}//while
		double totalSum = pureSumDiameters+(center->degree()-1)*minDist;
		//TODO: scling, not just counting
		while (totalSum/Math::pi < rectBound*0.75)
		{
			minDist = minDist + 1.0;
			totalSum += (center->degree()-1.0);
		}//while
		if (minDist > 1.1) minDist -= 1.0;
		//do not use larger value than cliquecentersize (used with separation)
		//if (minDist > m_cliqueCenterSize) minDist = m_cliqueCenterSize;
		itNode = adjNodes.begin();
	}
	//temporary part ends-------------------------------------------------------
	//------------------------------------------
	//first, we compute the radius of the circle

	const int n = center->degree();
	//sum of all diameters around the nodes and the max diameter radius
	double sumDiameters = 0.0, maxR = 0;
	//list of angles for all nodes
	List<double> angles; //node at startAdj gets 0.0
	double lastDiameter = 0.0; //temporary storage of space needed for previous node
	bool first = true;

	while (itNode.valid())
	{
		v  =(*itNode);
		double d = sqrt(
			width(v)*width(v) + height(v)*height(v));

		sumDiameters += d;

		if (d/2.0 > maxR) maxR = d/2.0;

		//save current position relative to startadj
		//later on, compute angle out of these values
		if (first)
		{
			angles.pushBack(0.0);
			first = false;
		}
		else
		{
			angles.pushBack(lastDiameter+d/2.0+minDist+angles.back());
		}
		lastDiameter = d/2.0; //its only half diameter...

		++itNode;
	}//while

	OGDF_ASSERT(adjNodes.size() == angles.size())

	if(n == 1) {
			radius      = 0;

		} else if (n == 2) {
			radius      = 0.5*minDist + sumDiameters / 4;

		} else {
			double perimeter = (n*minDist + sumDiameters);
			radius      = perimeter / (2*Math::pi);

			ListIterator<double> it = angles.begin();
			itNode = adjNodes.begin();
			while (it.valid())
			{
				(*it) = (*it)*360.0/perimeter;
				node w = *itNode;
				double angle = Math::pi*(*it)/180.0;
				m_cliqueCirclePos[w].m_x = radius*cos(angle);
				m_cliqueCirclePos[w].m_y = radius*sin(angle);
				++itNode;
				++it;
			}//while
		}//if n>2

		//now we normalize the values (start with 0.0) and
		//derive the bounding box
		v = adjNodes.front();
		double minX = m_cliqueCirclePos[v].m_x,
			maxX = m_cliqueCirclePos[v].m_x;
		double minY = m_cliqueCirclePos[v].m_y,
			maxY = m_cliqueCirclePos[v].m_y;
		itNode = adjNodes.begin();
		while (itNode.valid())
		{
			node w = *itNode;
			double wx = m_cliqueCirclePos[w].m_x;
			double wy = m_cliqueCirclePos[w].m_y;
			if(wx-width (w)/2.0 < minX) minX = wx-width(w)/2.0;
			if(wx+width (w)/2.0 > maxX) maxX = wx+width(w)/2.0;
			if(wy-height(w)/2.0 < minY) minY = wy-height(w)/2.0;
			if(wy+height(w)/2.0 > maxY) maxY = wy+height(w)/2.0;
			++itNode;
		}
		//allow distance
		minX -= minCCDist;
		minY -= minCCDist;
		//normalize
		//cout<<"\n";

		itNode = adjNodes.begin();
		while (itNode.valid())
		{
			node w = *itNode;
			//cout<<"x1:"<<m_cliqueCirclePos[w].m_x<<":y:"<<m_cliqueCirclePos[w].m_y<<"\n";
			m_cliqueCirclePos[w].m_x -= minX;
			m_cliqueCirclePos[w].m_y -= minY;
			//cout<<"x:"<<m_cliqueCirclePos[w].m_x<<":y:"<<m_cliqueCirclePos[w].m_y<<"\n";
			++itNode;
		}

		//reassign the size, this time it is the final value
		m_cliqueCircleSize[center] = DRect(0.0, 0.0, maxX-minX, maxY-minY);
}//computecliqueposition
コード例 #16
0
ファイル: ModifiedNibbleClusterer.cpp プロジェクト: ogdf/ogdf
// Returns conductance of best cluster
double ModifiedNibbleClusterer::findBestCluster(NodeArray<bool> &isActive, std::vector<node> &activeNodes, std::vector<node> &cluster)
{
	ArrayBuffer< Prioritized<int> > sortedPairs((int)activeNodes.size());
	cluster.clear();

	// collect pairs and sort them
	for (int i = 0; i < (int)activeNodes.size(); ++i) {
		const node v = activeNodes.at(i);
		sortedPairs.push(Prioritized<int>(i, - m_prob[v] / v->degree()));
	}
	sortedPairs.quicksort();
	int maxSize = min(static_cast<int>(activeNodes.size()), static_cast<int>(m_maxClusterSize));
	// Now we search for the best cluster in the list (paper description is ambiguous)
	NodeArray<bool> inCluster(*m_pGC, false); // costs dearly but hard to avoid without knowing sizes
	// Save list entry in frontier list of current set to delete nodes that are added to the list
	NodeArray< ListIterator<node> > frontierEntry(*m_pGC, nullptr);
	List<node> frontier; //frontier of current set (next node in list doesn't need to be a member!)
	// Save nodes that become abandoned, i.e. only have neighbors in a current cluster set.
	// These will never again become non-abandoned, i.e. can be collected, but could become
	// part of the cluster, i.e. we need to take care as there might be duplicates in cluster plus
	// abandoned list (but deleting would make it impossible to simply store the index for the solution).
	std::vector<node> abandoned;
	NodeArray<bool> wasAbandoned(*m_pGC, false);
	// As the cluster and the list of abandoned nodes might overlap, we cannot simply add their size
	long numRealAband = 0;
	int volume = 0;
	long cutSize = 0;
	long bestindex = 0;
	double bestConductance = std::numeric_limits<double>::max();
	long bestindexaband = -1;
	for (int run = 0; run < maxSize; ++run) {
		//Check the conductance of the current set
		node next = activeNodes.at(sortedPairs[run].item());
		inCluster[next] = true;

		OGDF_ASSERT(numRealAband >= 0);
		//in case the node was in our frontier make sure we won't consider it later
		if (!(((ListIterator<node>)frontierEntry[next]) == (ListIterator<node>)nullptr)){
			frontier.del(frontierEntry[next]);
			frontierEntry[next] = (ListIterator<node>)nullptr; //will never be used again
		}
		//volume changes by degree of node if not already taken into account
		// as previously abandoned node
		if (wasAbandoned[next]) numRealAband--;
		else volume += next->degree();
		//cutsize changes according to new nodes adjacency
		for(adjEntry adj : next->adjEntries){
			node w = adj->theEdge()->opposite(next);
			if (inCluster[w]) {
				cutSize--;
			}
			else {
				cutSize++;
				frontierEntry[w] = frontier.pushBack(w);
			}
		}

		// We add the abandoned nodes here, i.e. nodes that only have
		// neighbors in the current set left.
		ListIterator<node> itn = frontier.begin();
		while (itn.valid()) {
			node t = (*itn);
			if (wasAbandoned[t]) {
				++itn;
				continue;
			}
			bool aband = true;
			for(adjEntry adj : t->adjEntries){
				if (!inCluster[adj->theEdge()->opposite(t)]) {
					aband = false;
					break;
				}
			}
			if (aband) {
				wasAbandoned[t] = true;
				abandoned.push_back(t);
				numRealAband++;
				// Abandoned nodes are part of the prospective cluster
				// Adding them does not change the cut, but the volume
				volume += t->degree();
			}
			++itn;
		}


		OGDF_ASSERT(cutSize >= 0);
		if (run + numRealAband > m_maxClusterSize) break; // Can only get bigger now
		// Calculate conductance
		double conductance = static_cast<double>(cutSize) / static_cast<double>(min(volume, max(1, 2*m_pGC->numberOfEdges() - volume)));
		if (conductance < bestConductance) {
			bestConductance = conductance;
			bestindex = run;
			bestindexaband = (long)(abandoned.size()-1);
		}
	}
	// Put together our result
	for (int run = 0; run <= bestindex; ++run) {
		node next = activeNodes.at(sortedPairs[run].item());
		if (!wasAbandoned[next]) cluster.push_back(next);
	}
	for (int run = 0; run <= bestindexaband; ++run) {
		cluster.push_back(abandoned.at(run));
	}
#if 0
	std::cout << "Cluster found "<<cluster.size()<< " " << bestConductance<<"\n";
#endif
#ifdef OGDF_DEBUG
	NodeArray<bool> test(*m_pGC, false);
	for (node v : cluster) {
		OGDF_ASSERT(!test[v]);
		test[v] = true;
	}
#endif
	return bestConductance;
}