412 lines
13 KiB
HTML
412 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();
|
|
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>
|