696 行
17 KiB
JavaScript

// Copyright 2011 David Galles, University of San Francisco. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other materials
// provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY David Galles ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL David Galles OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those of the
// authors and should not be interpreted as representing official policies, either expressed
// or implied, of the University of San Francisco
var ARRAY_START_X = 50;
var ARRAY_WIDTH = 30;
var ARRAY_HEIGHT = 30;
var TREE_START_X = 50;
var TREE_ELEM_WIDTH = 50;
var TREE_ELEM_HEIGHT = 50;
var SIZE = 16;
var LINK_COLOR = "#007700"
var HIGHLIGHT_CIRCLE_COLOR = "#007700";
var FOREGROUND_COLOR = "#007700";
var BACKGROUND_COLOR = "#EEFFEE";
var PRINT_COLOR = FOREGROUND_COLOR;
function DisjointSet(am, w, h)
{
this.array_start_y = h - 2 * ARRAY_HEIGHT;
this.tree_start_y = this.array_start_y - 50;
this.init(am);
}
DisjointSet.prototype = new Algorithm();
DisjointSet.prototype.constructor = DisjointSet;
DisjointSet.superclass = Algorithm.prototype;
DisjointSet.prototype.init = function(am, w, h)
{
var sc = DisjointSet.superclass.init.call(this, am, w, h);
this.addControls();
this.commands = [];
this.nextIndex = 0;
this.highlight1ID = this.nextIndex++;
this.highlight2ID = this.nextIndex++;
this.arrayID = new Array(SIZE);
this.arrayLabelID = new Array(SIZE);
this.treeID = new Array(SIZE);
this.setData = new Array(SIZE);
this.treeY = new Array(SIZE);
this.treeIndexToLocation = new Array(SIZE);
this.locationToTreeIndex = new Array(SIZE);
this.heights = new Array(SIZE);
for (var i = 0; i < SIZE; i++)
{
this.treeIndexToLocation[i] = i;
this.locationToTreeIndex[i] = i;
this.arrayID[i]= this.nextIndex++;
this.arrayLabelID[i]= this.nextIndex++;
this.treeID[i] = this.nextIndex++;
this.setData[i] = -1;
this.treeY[i] = this.tree_start_y;
this.heights[i] = 0;
}
this.pathCompression = false;
this.unionByRank = false;
this.rankAsHeight = false;
this.setup();
}
DisjointSet.prototype.addControls = function()
{
this.controls = [];
this.findField = addControlToAlgorithmBar("Text", "");
this.findField.onkeydown = this.returnSubmit(this.findField, this.findCallback.bind(this), 4, true);
this.controls.push(this.findField);
var findButton = addControlToAlgorithmBar("Button", "搜索");
findButton.onclick = this.findCallback.bind(this);
this.controls.push(findButton);
this.unionField1 = addControlToAlgorithmBar("Text", "");
this.unionField1.onkeydown = this.returnSubmit(this.unionField1, this.unionCallback.bind(this), 4, true);
this.controls.push(this.unionField1);
this.unionField2 = addControlToAlgorithmBar("Text", "");
this.unionField2.onkeydown = this.returnSubmit(this.unionField2, this.unionCallback.bind(this), 4, true);
this.unionButton = addControlToAlgorithmBar("Button", "Union");
this.unionButton.onclick = this.unionCallback.bind(this);
this.controls.push(this.unionField2);
this.pathCompressionBox = addCheckboxToAlgorithmBar("Path Compression");
this.pathCompressionBox.onclick = this.pathCompressionChangeCallback.bind(this);
this.controls.push(this.pathCompressionBox);
this.unionByRankBox = addCheckboxToAlgorithmBar("Union By Rank");
this.unionByRankBox.onclick = this.unionByRankChangeCallback.bind(this);
this.controls.push(this.unionByRankBox);
var radioButtonList = addRadioButtonGroupToAlgorithmBar(["Rank = # of nodes",
"Rank = estimated height",
],
"RankType");
this.rankNumberOfNodesButton = radioButtonList[0];
this.rankNumberOfNodesButton.onclick = this.rankTypeChangedCallback.bind(this, false);
this.controls.push(this.rankNumberOfNodesButton);
this.rankEstimatedHeightButton = radioButtonList[1];
this.rankEstimatedHeightButton.onclick = this.rankTypeChangedCallback.bind(this, true);
this.controls.push(this.rankEstimatedHeightButton);
this.rankNumberOfNodesButton.checked = !this.rankAsHeight;
this.rankEstimatedHeightButton.checked = this.rankAsHeight;
}
DisjointSet.prototype.setup = function()
{
this.commands = new Array();
for (var i = 0; i < SIZE; i++)
{
this.cmd("CreateRectangle", this.arrayID[i], this.setData[i], ARRAY_WIDTH, ARRAY_HEIGHT, ARRAY_START_X + i *ARRAY_WIDTH, this.array_start_y);
this.cmd("CreateLabel",this.arrayLabelID[i], i, ARRAY_START_X + i *ARRAY_WIDTH, this.array_start_y + ARRAY_HEIGHT);
this.cmd("SetForegroundColor", this.arrayLabelID[i], "#0000FF");
this.cmd("CreateCircle", this.treeID[i], i, TREE_START_X + this.treeIndexToLocation[i] * TREE_ELEM_WIDTH, this.treeY[i]);
this.cmd("SetForegroundColor", this.treeID[i], FOREGROUND_COLOR);
this.cmd("SetBackgroundColor", this.treeID[i], BACKGROUND_COLOR);
}
animationManager.StartNewAnimation(this.commands);
animationManager.skipForward();
animationManager.clearHistory();
}
DisjointSet.prototype.reset = function()
{
for (var i = 0; i < SIZE; i++)
{
this.setData[i] = -1;
}
this.pathCompression = false;
this.unionByRank = false;
this.rankAsHeight = false;
this.pathCompressionBox.selected = this.pathCompression;
this.unionByRankBox.selected = this.unionByRank;
this.rankNumberOfNodesButton.checked = !this.rankAsHeight;
this.rankEstimatedHeightButton.checked = this.rankAsHeight;
}
DisjointSet.prototype.enableUI = function(event)
{
for (var i = 0; i < this.controls.length; i++)
{
this.controls[i].disabled = false;
}
}
DisjointSet.prototype.disableUI = function(event)
{
for (var i = 0; i < this.controls.length; i++)
{
this.controls[i].disabled = true;
}
}
DisjointSet.prototype.rankTypeChangedCallback = function(rankAsHeight, event)
{
if (this.rankAsHeight != rankAsHeight)
{
this.implementAction(this.changeRankType.bind(this), rankAsHeight);
}
}
DisjointSet.prototype.pathCompressionChangeCallback = function(event)
{
if (this.pathCompression != this.pathCompressionBox.checked)
{
this.implementAction(this.changePathCompression.bind(this), this.pathCompressionBox.checked);
}
}
DisjointSet.prototype.unionByRankChangeCallback = function(event)
{
if (this.unionByRank != this.unionByRankBox.checked)
{
this.implementAction(this.changeUnionByRank.bind(this), this.unionByRankBox.checked);
}
}
DisjointSet.prototype.changeRankType = function(newValue)
{
this.commands = new Array();
this.rankAsHeight = newValue
if (this.rankNumberOfNodesButton.checked == this.rankAsHeight)
{
this.rankNumberOfNodesButton.checked = !this.rankAsHeight;
}
if (this.rankEstimatedHeightButton.checked != this.rankAsHeight)
{
this.rankEstimatedHeightButton.checked = this.rankAsHeight;
}
// When we change union by rank, we can either create a blank slate using clearAll,
// or we can rebuild the root values to what they shoue be given the current state of
// the tree.
// clearAll();
this.rebuildRootValues();
return this.commands;
}
DisjointSet.prototype.changeUnionByRank = function(newValue)
{
this.commands = new Array();
this.unionByRank = newValue;
if (this.unionByRankBox.selected != this.unionByRank)
{
this.unionByRankBox.selected = this.unionByRank;
}
// When we change union by rank, we can either create a blank slate using clearAll,
// or we can rebuild the root values to what they shoue be given the current state of
// the tree.
// clearAll();
this.rebuildRootValues();
return this.commands;
}
DisjointSet.prototype.changePathCompression = function(newValue)
{
this.commands = new Array();
this.cmd("Step");
this.pathCompression = newValue;
if (this.pathCompressionBox.selected != this.pathCompression)
{
this.pathCompressionBox.selected = this.pathCompression;
}
this.rebuildRootValues();
// clearAll();
return this.commands;
}
DisjointSet.prototype.findCallback = function(event)
{
var findValue;
findValue = this.findField.value;
if (findValue != "" && parseInt(findValue) < SIZE)
{
this.findField.value.value = "";
this.implementAction(this.findElement.bind(this), findValue);
}
}
DisjointSet.prototype.clearCallback = function(event)
{
this.implementAction(this.clearData.bind(this), "");
}
DisjointSet.prototype.clearData = function(ignored)
{
this.commands = new Array();
clearAll();
return this.commands;
}
DisjointSet.prototype.getSizes = function()
{
var sizes = new Array(SIZE);
for (var i = 0; i < SIZE; i++)
{
sizes[i] = 1;
}
var changed = true;
while (changed)
{
changed = false;
for (i = 0; i < SIZE; i++)
{
if (sizes[i] > 0 && this.setData[i] >= 0)
{
sizes[this.setData[i]] += sizes[i];
sizes[i] = 0;
changed = true;
}
}
}
return sizes;
}
DisjointSet.prototype.rebuildRootValues = function()
{
var changed = false;
if (this.unionByRank)
{
if (!this.rankAsHeight)
{
var sizes = this.getSizes();
}
for (var i = 0; i < SIZE; i++)
{
if (this.setData[i] < 0)
{
if (this.rankAsHeight)
{
this.setData[i] = 0 - this.heights[i] - 1;
}
else
{
this.setData[i] = - sizes[i];
}
}
}
}
else
{
for (i = 0; i < SIZE; i++)
{
if (this.setData[i] < 0)
{
this.setData[i] = -1;
}
}
}
for (i = 0; i < SIZE; i++)
{
this.cmd("SetText", this.arrayID[i], this.setData[i]);
}
}
DisjointSet.prototype.unionCallback = function(event)
{
var union1;
var union2;
union1 = this.unionField1.value;
union2 = this.unionField2.value;
if ( union1 != "" && parseInt(union1) < SIZE &&
union2 != "" && parseInt(union2) < SIZE)
{
this.unionField1.value = "";
this.unionField2.value = "";
this.implementAction(this.doUnion.bind(this), union1 + ";" + union2);
}
}
DisjointSet.prototype.clearAll = function()
{
for (var i = 0; i < SIZE; i++)
{
if (this.setData[i] >= 0)
{
this.cmd("Disconnect", this.treeID[i], this.treeID[this.setData[i]]);
}
this.setData[i] = -1;
this.cmd("SetText", this.arrayID[i], this.setData[i]);
this.treeIndexToLocation[i] = i;
this.locationToTreeIndex[i] = i;
this.treeY[i] = this.tree_start_y;
this.cmd("SetPosition", this.treeID[i], TREE_START_X + this.treeIndexToLocation[i] * TREE_ELEM_WIDTH, this.treeY[i]);
}
}
DisjointSet.prototype.findElement = function(findValue)
{
this.commands = new Array();
var found = this.doFind(parseInt(findValue));
if (this.pathCompression)
{
var changed = this.adjustHeights();
if (changed)
{
this.animateNewPositions();
}
}
return this.commands;
}
DisjointSet.prototype.doFind = function(elem)
{
this.cmd("SetHighlight", this.treeID[elem], 1);
this.cmd("SetHighlight", this.arrayID[elem], 1);
this.cmd("Step");
this.cmd("SetHighlight", this.treeID[elem], 0);
this.cmd("SetHighlight", this.arrayID[elem], 0);
if (this.setData[elem] >= 0)
{
var treeRoot = this.doFind(this.setData[elem]);
if (this.pathCompression)
{
if (this.setData[elem] != treeRoot)
{
this.cmd("Disconnect", this.treeID[elem], this.treeID[this.setData[elem]]);
this.setData[elem] = treeRoot;
this.cmd("SetText", this.arrayID[elem], this.setData[elem]);
this.cmd("Connect", this.treeID[elem],
this.treeID[treeRoot],
FOREGROUND_COLOR,
0, // Curve
1, // Directed
""); // Label
}
}
return treeRoot;
}
else
{
return elem;
}
}
DisjointSet.prototype.findRoot = function (elem)
{
while (this.setData[elem] >= 0)
elem = this.setData[elem];
return elem;
}
// After linking two trees, move them next to each other.
DisjointSet.prototype.adjustXPos = function(pos1, pos2)
{
var left1 = this.treeIndexToLocation[pos1];
while (left1 > 0 && this.findRoot(this.locationToTreeIndex[left1 - 1]) == pos1)
{
left1--;
}
var right1 = this.treeIndexToLocation[pos1];
while (right1 < SIZE - 1 && this.findRoot(this.locationToTreeIndex[right1 + 1]) == pos1)
{
right1++;
}
var left2 = this.treeIndexToLocation[pos2];
while (left2 > 0 && this.findRoot(this.locationToTreeIndex[left2-1]) == pos2)
{
left2--;
}
var right2 = this.treeIndexToLocation[pos2];
while (right2 < SIZE - 1 && this.findRoot(this.locationToTreeIndex[right2 + 1]) == pos2)
{
right2++;
}
if (right1 == left2-1)
{
return false;
}
var tmpLocationToTreeIndex = new Array(SIZE);
var nextInsertIndex = 0;
for (var i = 0; i <= right1; i++)
{
tmpLocationToTreeIndex[nextInsertIndex++] = this.locationToTreeIndex[i];
}
for (i = left2; i <= right2; i++)
{
tmpLocationToTreeIndex[nextInsertIndex++] = this.locationToTreeIndex[i];
}
for (i = right1+1; i < left2; i++)
{
tmpLocationToTreeIndex[nextInsertIndex++] = this.locationToTreeIndex[i];
}
for (i = right2+1; i < SIZE; i++)
{
tmpLocationToTreeIndex[nextInsertIndex++] = this.locationToTreeIndex[i];
}
for (i = 0; i < SIZE; i++)
{
this.locationToTreeIndex[i] = tmpLocationToTreeIndex[i];
}
for (i = 0; i < SIZE; i++)
{
this.treeIndexToLocation[this.locationToTreeIndex[i]] = i;
}
return true;
}
DisjointSet.prototype.doUnion = function(value)
{
this.commands = new Array();
var args = value.split(";");
var arg1 = this.doFind(parseInt(args[0]));
this.cmd("CreateHighlightCircle", this.highlight1ID, HIGHLIGHT_CIRCLE_COLOR, TREE_START_X + this.treeIndexToLocation[arg1] * TREE_ELEM_WIDTH, this.treeY[arg1]);
var arg2 = this.doFind(parseInt(args[1]));
this.cmd("CreateHighlightCircle", this.highlight2ID, HIGHLIGHT_CIRCLE_COLOR, TREE_START_X + this.treeIndexToLocation[arg2] * TREE_ELEM_WIDTH, this.treeY[arg2]);
if (arg1 == arg2)
{
this.cmd("Delete", this.highlight1ID);
this.cmd("Delete", this.highlight2ID);
return this.commands;
}
var changed;
if (this.treeIndexToLocation[arg1] < this.treeIndexToLocation[arg2])
{
changed = this.adjustXPos(arg1, arg2) || changed
}
else
{
changed = this.adjustXPos(arg2, arg1) || changed
}
if (this.unionByRank && this.setData[arg1] < this.setData[arg2])
{
var tmp = arg1;
arg1 = arg2;
arg2 = tmp;
}
if (this.unionByRank && this.rankAsHeight)
{
if (this.setData[arg2] == this.setData[arg1])
{
this.setData[arg2] -= 1;
}
}
else if (this.unionByRank)
{
this.setData[arg2] = this.setData[arg2] + this.setData[arg1];
}
this.setData[arg1] = arg2;
this.cmd("SetText", this.arrayID[arg1], this.setData[arg1]);
this.cmd("SetText", this.arrayID[arg2], this.setData[arg2]);
this.cmd("Connect", this.treeID[arg1],
this.treeID[arg2],
FOREGROUND_COLOR,
0, // Curve
1, // Directed
""); // Label
if (this.adjustHeights())
{
changed = true;
}
if (changed)
{
this.cmd("Step");
this.cmd("Delete", this.highlight1ID);
this.cmd("Delete", this.highlight2ID);
this.animateNewPositions();
}
else
{
this.cmd("Delete", this.highlight1ID);
this.cmd("Delete", this.highlight2ID);
}
return this.commands;
}
DisjointSet.prototype.adjustHeights = function()
{
var changed = false;
for (var i = 0; i < SIZE; i++)
{
this.heights[i] = 0;
}
for (var j = 0; j < SIZE; j++)
{
for (i = 0; i < SIZE; i++)
{
if (this.setData[i] >= 0)
{
this.heights[this.setData[i]] = Math.max(this.heights[this.setData[i]], this.heights[i] + 1);
}
}
}
for (j = 0; j < SIZE; j++)
{
for (i = 0; i < SIZE; i++)
{
if (this.setData[i] >= 0)
{
this.heights[i] = this.heights[this.setData[i]] - 1;
}
}
}
for (i = 0; i < SIZE; i++)
{
var newY = this.tree_start_y - this.heights[i] * TREE_ELEM_HEIGHT;
if (this.treeY[i] != newY)
{
this.treeY[i] = newY;
changed = true;
}
}
return changed;
}
DisjointSet.prototype.animateNewPositions = function()
{
for (var i = 0; i < SIZE; i++)
{
this.cmd("Move", this.treeID[i], TREE_START_X + this.treeIndexToLocation[i] * TREE_ELEM_WIDTH, this.treeY[i]);
}
}
var currentAlg;
function init()
{
var animManag = initCanvas();
currentAlg = new DisjointSet(animManag, canvas.width, canvas.height);
}