Demonstration-des-Ford-Fulk.../ford-fulkerson.html

415 lines
13 KiB
HTML

<!doctype html>
<html>
<head>
<script>
SIN30=Math.sin(Math.PI/6)
COS30=Math.cos(Math.PI/6)
SIN90=Math.sin(Math.PI/2)
COS90=Math.cos(Math.PI/2)
totalFlow=0;
done=false;
iterations=0;
vertices=[];
verticesMap={};
edgesCapacity={};
edgesFlow={};
shownEdges="capacity_and_flows";
editMode="drag";
draggedVertex=null;
possibleNextSteps=[];
flowVertices=["s"];
function draw(){
function drawEdge(vertexFrom,vertexTo,number,fillColor,strokeColor){
co.fillStyle=fillColor;
co.strokeStyle=strokeColor;
xDistance=vertexFrom.x-vertexTo.x;
yDistance=vertexFrom.y-vertexTo.y;
let divideToGetLength15=Math.sqrt((xDistance)**2+(yDistance)**2)/15;
let xOfTotal15=(vertexTo.x-vertexFrom.x)/divideToGetLength15
let yOfTotal15=(vertexTo.y-vertexFrom.y)/divideToGetLength15
let rotatedXOfTotal15=xOfTotal15*COS30-yOfTotal15*SIN30;
let rotatedYOfTotal15=xOfTotal15*SIN30+yOfTotal15*COS30;
let otherDirectionRotatedXOfTotal15=xOfTotal15*COS30+yOfTotal15*SIN30;
let otherDirectionRotatedYOfTotal15=-xOfTotal15*SIN30+yOfTotal15*COS30;
let rotated90DegX=xOfTotal15*COS90-yOfTotal15*SIN90;
let rotated90DegY=xOfTotal15*SIN90+yOfTotal15*COS90;
co.beginPath();
co.moveTo(vertexFrom.x+rotatedXOfTotal15,vertexFrom.y+rotatedYOfTotal15);
co.lineTo(vertexFrom.x+rotatedXOfTotal15-xDistance-xOfTotal15*2,vertexFrom.y+rotatedYOfTotal15-yDistance-yOfTotal15*2);
co.lineTo(vertexFrom.x-xDistance-xOfTotal15*2,vertexFrom.y-yDistance-yOfTotal15*2);
co.lineTo(vertexFrom.x+rotatedXOfTotal15-xDistance-xOfTotal15*2,vertexFrom.y+rotatedYOfTotal15-yDistance-yOfTotal15*2);
co.lineTo(vertexFrom.x+rotatedXOfTotal15-otherDirectionRotatedXOfTotal15-xDistance-xOfTotal15*2,vertexFrom.y+rotatedYOfTotal15-otherDirectionRotatedYOfTotal15-yDistance-yOfTotal15*2);
co.stroke();
co.fillText(number,(vertexFrom.x+vertexTo.x)/2+rotated90DegX,(vertexFrom.y+vertexTo.y)/2+rotated90DegY);
}
co=c.getContext("2d");
co.textAlign="center";
co.textBaseline="middle";
co.fillStyle="#888888"
co.fillRect(0,0,1000,1000);
for(let vertex of vertices){
if(vertex.special=="source"){
co.fillStyle="#aaffaa";
}else if(vertex.special=="target"){
co.fillStyle="#ffaaaa";
}else{
co.fillStyle="white";
for(let alreadyVisited of flowVertices){
if(alreadyVisited==vertex.name){
co.fillStyle="#aaaaff";
break;
}
}
}
co.beginPath();
co.arc(vertex.x,vertex.y,15,0,Math.PI*2);
co.fill();
if(editMode=="flow"){
for(let possibleStep of possibleNextSteps){
if(possibleStep==vertex.name){
co.strokeStyle="blue";
co.beginPath();
co.arc(vertex.x,vertex.y,15,0,Math.PI*2);
co.stroke();
}
}
}
co.fillStyle="black";
co.fillText(vertex.name,vertex.x,vertex.y,20);
for(let otherVertex of vertices){
if(shownEdges=="capacity"){
if(edgesCapacity[vertex.name][otherVertex.name]>0){
drawEdge(vertex,otherVertex,edgesCapacity[vertex.name][otherVertex.name],"black","white");
}
}else if(shownEdges=="flows"){
if(edgesFlow[vertex.name][otherVertex.name]>0){
drawEdge(vertex,otherVertex,edgesFlow[vertex.name][otherVertex.name],"black","#8888ff");
}
}else if(shownEdges=="residuals"){
if(getResidualEdge(vertex.name,otherVertex.name)>0){
drawEdge(vertex,otherVertex,getResidualEdge(vertex.name,otherVertex.name),"black","orange");
}
}else if(shownEdges=="capacity_and_flows"){
if(edgesCapacity[vertex.name][otherVertex.name]>0){
drawEdge(vertex,otherVertex,getEdge(edgesFlow,vertex.name,otherVertex.name)+"/"+edgesCapacity[vertex.name][otherVertex.name],"black","white");
}
}
}
}
for(let i=0;i<flowVertices.length-1;i++){
drawEdge(verticesMap[flowVertices[i]],verticesMap[flowVertices[i+1]],"","blue","blue");
};
}
function getEdge(edge,node1,node2){
let value=edge[node1][node2];
if(value==undefined){
return 0;
} else {
return value;
}
}
function getResidualEdge(node1,node2){
return getEdge(edgesCapacity,node1,node2)-getEdge(edgesFlow,node1,node2)+getEdge(edgesFlow,node2,node1);
}
function parseGraph(){
try{
done=false;
iterations=0;
totalFlow=0;
vertices=[{name:"s",x:30,y:150,special:"source"},{name:"t",x:570,y:150,special:"target"}];
verticesMap={s:vertices[0],t:vertices[1]};
edgesCapacity={s:{},t:{}};
edgesFlow={s:{},t:{}};
possibleNextSteps=[];
flowVertices=["s"];
let verticesText = vDef.value.split(";");
vertexNumber=0;
for (let vertexName of verticesText){
vertexNumber++;
let trimmedVertexName=vertexName.trim();
if(trimmedVertexName==""||trimmedVertexName=="s"||trimmedVertexName=="t"){
continue;
}
if(verticesMap[trimmedVertexName]!=undefined){
throw new Error("Knoten können nicht doppelt vorkommen");
}
vertices.push({name:trimmedVertexName,x:100+vertexNumber*25,y:150,special:""});
verticesMap[trimmedVertexName]=vertices[vertices.length-1];
edgesCapacity[trimmedVertexName]={};
edgesFlow[trimmedVertexName]={};
}
for(let i=2;i<vertices.length;i++){
vertices[i].x=300+Math.cos(Math.PI*2*i/(vertices.length-2))*150;
vertices[i].y=150+Math.sin(Math.PI*2*i/(vertices.length-2))*135;
}
let edgesText=eDef.value.split(";");
for(let edgesLine of edgesText){
if(edgesLine.trim()==""){
continue;
}
let edgesLineSplit=edgesLine.split(":");
let capacity=Number(edgesLineSplit[1]);
let edgesVertices=edgesLineSplit[0].split(",");
let fromName=edgesVertices[0].trim();
let toName=edgesVertices[1].trim();
if(isNaN(capacity)){
throw new Error("Zahl "+edgesLineSplit[1]+" nicht erkannt");
}
if(capacity<0){
throw new Error("Zahl "+edgesLineSplit[1]+" ist negativ");
}
if(fromName==toName){
throw new Error("Knoten kann keine Kante zu sich selbst haben");
}
if(verticesMap[fromName]==null){
throw new Error("Knoten "+fromName+" nicht vorhanden");
}
if(verticesMap[toName]==null){
throw new Error("Knoten "+toName+" nicht vorhanden");
}
if(edgesCapacity[fromName][toName]>0/*||edgesCapacity[toName][fromName]>0*/){//Kommentar wegmachen, um doppelte Kanten nicht zuzulassen
throw new Error("Kante von "+fromName+" nach "+toName+" kommt mehrfach vor");
}
edgesCapacity[fromName][toName]=capacity;
}
statusDef.innerText="Graph erfolgreich erstellt";
statusSim.innerText="Graph neu erstellt";
}catch(error){
statusDef.innerText=error.toString();
}
calculatePossibleNextSteps();
if(bfs()==undefined){
statusSim.innerText+="- Es gibt von Anfang an keinen augmentierenden Pfad, der Algorithmus braucht nicht angewandt zu werden.";
done=true;
};
draw();
}
function bfs(){
let searches=[["s"]];
let visitedVertices=["s"];
let end=false;
while(searches.length>0){
currentSearch=searches.shift();
if(currentSearch[currentSearch.length-1]=="t"){
return currentSearch;
}
for(let otherVertex in verticesMap){
let skip=false;
for(let visitedVertex of visitedVertices){
if(visitedVertex==otherVertex){
skip=true;
break;
console.log("skipping");
}
}
if(!skip&&getResidualEdge(currentSearch[currentSearch.length-1],otherVertex)>0){
searches.push(currentSearch.concat(otherVertex));
visitedVertices.push(otherVertex);
}
}
}
}
function selectShortestPath(){
result=bfs();
if(result!=undefined){
flowVertices=result;
} else{
flowVertices=["s"];
}
calculatePossibleNextSteps();
draw();
}
function selectRandomPath(){
flowVertices=["s"];
let visitedVertices=["s"];
for(let i=0;i<2000;i++){
calculatePossibleNextSteps();
let nextSteps=[];
for(let step of possibleNextSteps){
let alreadyVisited=false;
for(let visitedVertex of visitedVertices){
if(visitedVertex==step){
alreadyVisited=true;
}
}
if(!alreadyVisited){
nextSteps.push(step);
}
}
if(nextSteps.length==0){
if(flowVertices.length==1){return}
stepTo(flowVertices[flowVertices.length-2]);
} else {
let vertexToVisit=nextSteps[Math.floor(Math.random()*nextSteps.length)%nextSteps.length];
visitedVertices.push(vertexToVisit);
stepTo(vertexToVisit);
if(vertexToVisit=="t"){
return;
}
}
}
console.log("random path doesn't end after 2000 iterations");
}
function edmondsKarp(){
while(!done){
selectShortestPath();
generateNewFlow();
}
}
function randomFordFulkerson(){
while(!done){
selectRandomPath();
generateNewFlow();
}
}
function calculatePossibleNextSteps(){
possibleNextSteps=[];
let flowFrontVertex = flowVertices[flowVertices.length-1];
if(flowFrontVertex=="t"){
return;
}
for(let vertex in verticesMap){
if(getResidualEdge(flowFrontVertex,vertex)>0){
let alreadyVisited=false;
for(let alreadyVisitedVertex of flowVertices){
if(alreadyVisitedVertex==vertex){
alreadyVisited=true;
break;
}
}
if(!alreadyVisited){
possibleNextSteps.push(vertex);
}
}
}
}
function stepTo(vertex){
for(let i=0;i<flowVertices.length;i++){
if(flowVertices[i]==vertex){
while(flowVertices.length>i+1){
flowVertices.pop();
}
calculatePossibleNextSteps();
draw();
return;
}
}
for(let possibleStep of possibleNextSteps){
if(vertex==possibleStep){
possible=true;
flowVertices.push(vertex);
calculatePossibleNextSteps();
draw();
return;
}
}
}
function generateNewFlow(){
if(done){
return;
}
if(flowVertices[flowVertices.length-1]!="t"){
statusSim.innerText="ausgewählter Pfad ist kein s-t-Pfad";
return;
}
newFlow=Infinity;
for(let i=0;i<flowVertices.length-1;i++){
newFlow=Math.min(newFlow,getResidualEdge(flowVertices[i],flowVertices[i+1]));
}
for(let i=0;i<flowVertices.length-1;i++){
let oppositeFlowMinusNewFlow=getEdge(edgesFlow,flowVertices[i+1],flowVertices[i])-newFlow;
if(oppositeFlowMinusNewFlow>=0){
edgesFlow[flowVertices[i+1]][flowVertices[i]]=oppositeFlowMinusNewFlow;
} else {
edgesFlow[flowVertices[i+1]][flowVertices[i]]=0;
edgesFlow[flowVertices[i]][flowVertices[i+1]]= getEdge(edgesFlow,flowVertices[i],flowVertices[i+1])-oppositeFlowMinusNewFlow;
}
}
iterations++;
totalFlow+=newFlow;
flowVertices=["s"];
calculatePossibleNextSteps();
draw();
statusSim.innerText="Iteration "+iterations+". Augmentierender Pfad mit Flusskapazität "+newFlow+" hinzugefügt. Insgesamt hat der Fluss die Kapazität "+totalFlow+".";
if(bfs()==undefined){
statusSim.innerText+=" Es gibt keinen augmentierenden Pfad mehr, der Algorithmus ist fertig.";
done=true;
};
}
function resetToDefaultGraph(){
vDef.value="u;v;w;x;y;z;";
eDef.value="s,v:4;\ns,u:5;\ns,w:2;\nu,v:4;\nv,u:4;\nv,w:3;\nw,x:2;\nv,x:4;\nu,z:3;\nz,y:2;\nx,y:3;\nz,x:4;\ny,t:4;\nx,t:6;\nx,u:7;";
}
function initializeEventHandlers(){
c.onmousedown=clickEventHandler;
c.onmousemove=mousemoveEventHandler;
document.onmouseup=globalMouseupEventHandler;
}
function clickEventHandler(event){
let mousex=(event.offsetX*c.width)/c.clientWidth;
let mousey=(event.offsetY*c.height)/c.clientHeight;
for(let i=vertices.length-1;i>=0;i--){
let vertex=vertices[i];
if(Math.sqrt((vertex.x-mousex)**2+(vertex.y-mousey)**2)<=15){
if(editMode=="drag"){
draggedVertex=vertex;
} else if(editMode=="flow"){
stepTo(vertex.name);
} else {
console.log("unknown edit mode");
}
break;
}
}
}
function mousemoveEventHandler(event){
let mousex=(event.offsetX*c.width)/c.clientWidth;
let mousey=(event.offsetY*c.height)/c.clientHeight;
if(draggedVertex!=null){
draggedVertex.x=mousex;
draggedVertex.y=mousey;
draw();
}
}
function globalMouseupEventHandler(event){
draggedVertex=null;
}
</script>
<style>
body {
margin-left:8%;
margin-right:8%;
}
</style>
</head>
<body onload="initializeEventHandlers();resetToDefaultGraph();parseGraph()">
<h1>Simulation des Ford-Fulkersen-Algorithmus</h1>
<h2> Graph definieren </h2>
<b>Knoten</b>, mit Semikolon getrennt (s und t werden nicht mit angegeben)
<br><textarea id="vDef" rows=5> </textarea>
<br><b>Kanten</b> und Kapazitäten, im Format "u,v:kapazität;"
<br><textarea id="eDef" rows=12> </textarea>
<br><button onclick="parseGraph()">Graph erstellen</button><button onclick="resetToDefaultGraph()">Zurück zu Beispielgraph</button>
<br><span id="statusDef"> </span>
<h2> Graphensimulation </h2>
Zeige Kanten:
<button onclick="shownEdges='capacity';draw()"> Kapazitäten </button>
<button onclick="shownEdges='flows';draw()"> Flüsse </button>
<button onclick="shownEdges='capacity_and_flows';draw()"> Kapazitäten und Flüsse </button>
<button onclick="shownEdges='residuals';draw()"> Residualkapazitäten </button>
<br> Mausklick:
<button onclick="editMode='drag';draw()"> verschiebt Knoten </button>
<button onclick="editMode='flow';draw()"> wählt Flusspfade aus </button>
<br> Aktion: <button onclick="generateNewFlow()">Fluss aus ausgewähltem augmentierenden Pfad erstellen</button> <button onclick="selectShortestPath();">kürzesten s-t-Pfad auswählen (Edmonds-Karp)</button> <button onclick="selectRandomPath();">zufälligen s-t-Pfad auswählen</button> <button onclick="edmondsKarp()">Edmonds-Karp-Algorithmus vollständig durchlaufen lassen</button> <button onclick="randomFordFulkerson()">Zufalls-Ford-Fulkerson-Algorithmus vollständig durchlaufen lassen</button>
<br> <span id="statusSim"> </span>
<canvas id="c" width=600 height=300 style="width:100%"> </canvas>
</body>
</html>