import java.awt.*; import java.util.*; /** * The Heap class provides the utilities to draw a complete * binary tree on the corresponding graphical context. It also contains * some animation methods used in heap sort, priority queue * and sorting using priority queue *

* There are basically 4 types of graphical objects: input, * output, tree representation of heap, and * array representation of heap. * The Node class is used to store the graphical objects. * @see Node */ public class Heap extends Node { Font bigFont, smallFont, tinyFont, hugeFont, fixFont; DrawingPanel drawingPanel; Node tree, posn; Vector nodeList, posnList, inputList, outputList, heapArray; int max_node_per_level, depth; Node movingNode; boolean exchanging; Point[] aPoints; Color darkgreen = new Color(0, 140, 0); Color darkRed = new Color(140, 0, 0); Color darkBlue = new Color(0, 0, 140); ComBox runningCom; int max_size; /** * Create a new heap and pre-calculate the location of each node * given the maximum number of node passed in as the parameter. * @param drawingPanel The drawing panel where the heap is to be drawn. * @param max_size The maximum number of nodes in the heap. */ public Heap(DrawingPanel drawingPanel, int max_size) { this.bigFont = drawingPanel.bigFont; this.smallFont = drawingPanel.smallFont; this.tinyFont = drawingPanel.tinyFont; this.hugeFont = drawingPanel.hugeFont; this.fixFont = drawingPanel.fixFont; this.drawingPanel = drawingPanel; this.max_size = max_size; this.tree = null; nodeList = new Vector(); posnList = new Vector(); inputList = new Vector(); heapArray = new Vector(); outputList = new Vector(); calNodesCoord(max_size); hideMovingNode(); exchanging = false; runningCom = null; } /** * Repaints the drawing panel and delay. */ public void redraw() { drawingPanel.repaint(); drawingPanel.delay(); } /** * Re-initialize the heap. This method is called when the animation * is re-started. */ public void initHeap() { this.tree = null; nodeList = new Vector(); inputList = new Vector(); heapArray = new Vector(); outputList = new Vector(); exchanging = false; } /** * Set the input objects based on the array of weight given by the * parameter. * @param a Array of weights for the input objects */ public void setInput(int[] a) { inputList = new Vector(); for (int i = 0; i < a.length; i++) { Node node = new Node(a[i]); node.x = drawingPanel.offset + 30 + (i+1)*22; node.y = bottomMostPosn(posn) + 85; inputList.addElement(node); } redraw(); } /** * Adds an input object with the weight specified by the parameter. * @param i Weight of the additional input object. */ public void addInput(int i) { Node node = new Node(i); node.x = drawingPanel.panel_width - drawingPanel.offset - 50; node.y = bottomMostPosn(posn) + 50; inputList.addElement(node); runningCom = new ComBox(node.x - 120, node.y + 50, "input arriving...", Color.blue, Color.yellow, fixFont); redraw(); } /** * Remove the first input object. */ public void delFirstInput() { inputList.removeElementAt(0); } /** * Remove an object from each of the tree and array representation of * the heap and adds an object to the output object list. *

* This method involves an animation which merge the root of the heap * tree and the first object of the heap array and move the resultant * object to the output position. * @param i The weight of the output object added. */ public void addOutput(int i) { Node node = new Node(i); node.y = drawingPanel.offset; node.x = drawingPanel.offset + 80 + (outputList.size())*22; runningCom = new ComBox(150, node.y + 20, "Extracting output...", Color.blue, Color.yellow, fixFont); movingNode = new Node(-1); int srcX = movingNode.x = drawingPanel.offset + 20; int srcY = movingNode.y = ((Node)posnList.firstElement()).y; if (!heapArray.isEmpty()) { Node heapNode; if (!nodeList.isEmpty()) heapNode = (Node)nodeList.firstElement(); else { heapNode = (Node)posnList.firstElement(); movingNode.setWeight(i); movingNode.x = heapNode.x; movingNode.y = heapNode.y; } Node arrayNode = (Node)heapArray.firstElement(); if (!nodeList.isEmpty()) { heapNode.setRightNode(null); heapNode.setLeftNode(null); } int srcHx = heapNode.x; int srcAy = arrayNode.y; for (int k = 0; k < 5; k++) { if (!nodeList.isEmpty()) heapNode.x += (srcX - srcHx)/5; else movingNode.x += (srcX - srcHx)/5; arrayNode.y += (srcY - srcAy)/5; redraw(); } } if (!nodeList.isEmpty()) nodeList.removeElementAt(0); if (!heapArray.isEmpty()) heapArray.removeElementAt(0); movingNode.setWeight(i); movingNode.x = srcX; movingNode.y = srcY; redraw(); int destX = node.x; int destY = node.y; for (int k = 0; k < 3; k++) { movingNode.x += (destX - srcX)/3; redraw(); } movingNode.x = destX; for (int k = 0; k < 5; k++) { movingNode.y += (destY - srcY)/5; redraw(); } hideMovingNode(); runningCom = null; outputList.addElement(node); redraw(); } /** * Move the last object of the heap to the first/root (for both * tree and array representation). The movement is also animated. */ public void moveLast2First() { movingNode = (Node)nodeList.lastElement(); runningCom = new ComBox(movingNode.x + 30, movingNode.y, "Moving last node to root...", Color.blue, Color.yellow, fixFont); if (nodeList.size() > 2) { //remove this node from parent int p = parent(nodeList.size()+1); if ((nodeList.size()+1) == right(p)) ((Node)nodeList.elementAt(p-2)).setRightNode(null); else ((Node)nodeList.elementAt(p-2)).setLeftNode(null); } //remove this node nodeList.removeElementAt(nodeList.size()-1); //lastHeapArray Node lastHeapArray = (Node)heapArray.lastElement(); // move node down; if (!nodeList.isEmpty()) { movingNode.y += 17; lastHeapArray.y += 17; redraw(); movingNode.y += 17; lastHeapArray.y += 17; redraw(); } int destHeapArrayX = drawingPanel.offset + 20; int srcHeapArrayX = lastHeapArray.x; int destX = drawingPanel.offset + 25; int srcX = movingNode.x; for (int i = 0; i < 5; i++) { movingNode.x += (destX - srcX)/5; lastHeapArray.x += (destHeapArrayX - srcHeapArrayX)/15; redraw(); } movingNode.x = destX; int destY = ((Node)posnList.firstElement()).y; int srcY = movingNode.y; for (int i = 0; i < 5; i++) { destY = ((Node)posnList.firstElement()).y; movingNode.y += (destY - srcY)/5; lastHeapArray.x += (destHeapArrayX - srcHeapArrayX)/15; redraw(); } movingNode.y = destY; destX = ((Node)posnList.firstElement()).x; srcX = movingNode.x; for (int i = 0; i < 5; i++) { movingNode.x += (destX - srcX)/5; lastHeapArray.x += (destHeapArrayX - srcHeapArrayX)/15; redraw(); } movingNode.x = destX; lastHeapArray.x = destHeapArrayX; if (!nodeList.isEmpty()) { lastHeapArray.y -= 17; redraw(); lastHeapArray.y -= 17; } heapArray.removeElementAt(heapArray.size()-1); heapArray.insertElementAt(lastHeapArray, 0); nodeList.insertElementAt(movingNode, 0); if (nodeList.size() > 1) movingNode.setLeftNode((Node)nodeList.elementAt(1)); if (nodeList.size() > 2) movingNode.setRightNode((Node)nodeList.elementAt(2)); hideMovingNode(); runningCom = null; redraw(); } /** * Restore/remove highlight for the node in the heap (both * tree and array representation. * @param i The node to be restore. */ public void restore(int i) { if (i < nodeList.size()) { ((Node)nodeList.elementAt(i)).highlight = false; ((Node)heapArray.elementAt(i)).highlight = false; } } /** * Highlight a node on the heap tree and its correspondence on the * array representation. * @param i The node to be highlighted. */ public void highlight(int i) { if (i < nodeList.size()) { ((Node)nodeList.elementAt(i)).highlight = true; ((Node)heapArray.elementAt(i)).highlight = true; } } private void hideMovingNode() { movingNode = new Node(-1); } /** * Shows arrow from node i to node j and the other way round (animation) * to indicate that these two nodes are going to be exchanged. * @param i Parent node to be swapped. * @param j Child node to be swapped. */ public void exchangeArrow(int i, int j) { // pre-condition: i+1 is parent, j+1 is child Node child = (Node)nodeList.elementAt(j); Node parent = (Node)nodeList.elementAt(i); exchanging = true; runningCom = new ComBox(child.x + 20, child.y - 10, "Swapping...", Color.blue, Color.yellow, fixFont); // check if is left or right child if ((j+1)==left(i+1)) { // assign points: Point[] aPoints aPoints = new Point[2]; aPoints[0] = new Point(child.x + 2, child.y); aPoints[1] = new Point(child.x + 2, parent.y + 15); redraw(); aPoints = new Point[3]; aPoints[0] = new Point(child.x + 2, child.y); aPoints[1] = new Point(child.x + 2, parent.y + 10); aPoints[2] = new Point(parent.x - 8, parent.y + 10); redraw(); aPoints = new Point[3]; aPoints[0] = new Point(child.x + 2, child.y); aPoints[1] = new Point(child.x + 2, parent.y + 10); aPoints[2] = new Point(parent.x, parent.y + 10); redraw(); aPoints = new Point[2]; aPoints[0] = new Point(parent.x, parent.y + 10); aPoints[1] = new Point(child.x + 6, parent.y + 10); redraw(); aPoints = new Point[3]; aPoints[0] = new Point(parent.x, parent.y + 10); aPoints[1] = new Point(child.x + 2, parent.y + 10); aPoints[2] = new Point(child.x + 2, parent.y + 25); redraw(); aPoints[0] = new Point(parent.x, parent.y + 10); aPoints[1] = new Point(child.x + 2, parent.y + 10); aPoints[2] = new Point(child.x + 2, child.y); redraw(); } else { // right child aPoints = new Point[2]; aPoints[0] = new Point(child.x + 17, child.y); aPoints[1] = new Point(child.x + 17, parent.y + 15); redraw(); aPoints = new Point[3]; aPoints[0] = new Point(child.x + 17, child.y); aPoints[1] = new Point(child.x + 17, parent.y + 10); aPoints[2] = new Point(parent.x + 25, parent.y + 10); redraw(); aPoints = new Point[3]; aPoints[0] = new Point(child.x + 17, child.y); aPoints[1] = new Point(child.x + 17, parent.y + 10); aPoints[2] = new Point(parent.x + 20, parent.y + 10); redraw(); aPoints = new Point[2]; aPoints[0] = new Point(parent.x + 20, parent.y + 10); aPoints[1] = new Point(child.x + 12, parent.y + 10); redraw(); aPoints = new Point[3]; aPoints[0] = new Point(parent.x + 20, parent.y + 10); aPoints[1] = new Point(child.x + 17, parent.y + 10); aPoints[2] = new Point(child.x + 17, child.y - 10); redraw(); aPoints[0] = new Point(parent.x + 20, parent.y + 10); aPoints[1] = new Point(child.x + 17, parent.y + 10); aPoints[2] = new Point(child.x + 17, child.y); redraw(); } Node array1 = (Node)heapArray.elementAt(i); Node array2 = (Node)heapArray.elementAt(j); Point orig1 = new Point(array1.x, array1.y); Point orig2 = new Point(array2.x, array2.y); boolean adj = (Math.abs(i-j) == 1); if (!adj) array1.y +=17; array2.y -= 17; redraw(); if (!adj) array1.y +=17; array2.y -= 17; redraw(); for (int k = 0; k < 4; k++) { array1.x += (orig2.x - orig1.x)/4; array2.x += (orig1.x - orig2.x)/4; redraw(); } array1.x = orig2.x; array2.x = orig1.x; if (!adj) array1.y -=17; array2.y += 17; redraw(); int tmpWeight = array1.getWeight(); array1.setWeight(array2.getWeight()); array2.setWeight(tmpWeight); array1.x = orig1.x; array1.y = orig1.y; array2.x = orig2.x; array2.y = orig2.y; runningCom = null; redraw(); exchanging = false; } /** * Remove the first object from the input object list and animate its * movement to the heap, i.e. it splits into two, one to the array * representation and the other to the tree representation of the heap. */ public void input2heap() { int inputNum = inputList.size(); Node node = (Node)inputList.firstElement(); delFirstInput(); movingNode = node; movingNode.y -= 17; redraw(); Node newNode = new Node(movingNode.getWeight()); newNode.x = drawingPanel.offset + 20 + heapArray.size() * 22; movingNode.y = bottomMostPosn(posn) + 50; redraw(); runningCom = new ComBox(newNode.x + 100, node.y, "Inserting input into heap...", Color.blue, Color.yellow, fixFont); Node arrayNode = new Node(movingNode.getWeight()); arrayNode.x = movingNode.x; arrayNode.y = movingNode.y; heapArray.addElement(arrayNode); Node destNode = (Node)posnList.elementAt(nodeList.size()); int destX = destNode.x; int destY = destNode.y; int sourceX = movingNode.x; movingNode.y -= 20; redraw(); movingNode.y -= 20; redraw(); for (int l = 0; l < 5; l++) { movingNode.x += (destX - sourceX)/5; arrayNode.x += (newNode.x - sourceX)/5; redraw(); } movingNode.x = destX; arrayNode.x = newNode.x; redraw(); int srcY = node.y; for (int k = 0; k < 5; k++) { movingNode.y += (destY - srcY)/5; redraw(); } hideMovingNode(); node.x = destX; node.y = destY; insert(node); runningCom = null; redraw(); } /** * Re-assign the heap using the array of weights passed in as the * parameter. * @param a Array of weight for the newly re-assigned heap. */ public void setHeap(int[] a) { if (a.length > posnList.size()) calNodesCoord(a.length); tree = null; nodeList = new Vector(); for (int i = 0; i < a.length; i++) insert(new Node(a[i])); } // setData() private void insert(Node node) { nodeList.addElement(node); if (posnList.size() >= nodeList.size()) { node.x = ((Node)posnList.elementAt(nodeList.size()-1)).x; node.y = ((Node)posnList.elementAt(nodeList.size()-1)).y; } if (tree == null) { tree = node; } else { // locate blank branch to link to this node for (int i = 0; i < nodeList.size()-1; i++) { Node n = (Node)nodeList.elementAt(i); if (n.getLeftNode() == null) { n.setLeftNode(node); break; } else if (n.getRightNode() == null) { n.setRightNode(node); break; } } } } /** * Returns the number of nodes in the heap. * @return The heap size. */ public int heapSize() { return nodeList.size(); } /** * Re-assign the weight of node i according to the parameters. * @param i The node, which weight is going to be re-assigned. * @param val The new weight to be set. */ public void setNode(int i, int val) { ((Node)nodeList.elementAt(i)).setWeight(val); } private void calNodesCoord(int size) { // count max number of nodes needed int i = 0; int sum = power(2, i++); while (sum < size) sum += power(2, i++); max_node_per_level = power(2, i-1); depth = i-1; int max_nodes = sum; // insert nodes with no value into position tree Node backTree = tree; // backup current tree Vector backNodeList = nodeList; nodeList = new Vector(); posnList = new Vector(); for (int j = 0; j < max_nodes; j++) { Node node = new Node(-1); insert(node); } posn = tree; posnList = nodeList; tree = backTree; nodeList = backNodeList; // assign Y int curDepth = 0; sum = power(2, 0); int offset = drawingPanel.offset + 30; //int yStart = drawingPanel.panel_height/2 -100 - depth*20; int yStart = 100; for (int j = 0; j < posnList.size(); j++) { Node node = (Node)posnList.elementAt(j); if ( (j+1) > sum) { curDepth++; sum += power(2, curDepth); } node.depth = curDepth; node.y = yStart + curDepth*40; // assign X for bottom level if (curDepth == depth) { node.x = 22 + offset; offset = node.x; } } // assign remaining X for (int j = posnList.size() - 1; j >= 0; j--) { Node node = (Node)posnList.elementAt(j); if (node.depth == depth) continue; if (node.getLeftNode() != null && node.depth == depth-1) { node.x = node.getLeftNode().x + 12; } else if (node.getLeftNode() != null && node.getRightNode() != null) { node.x = (node.getLeftNode().x + node.getRightNode().x)/2; } else { // both children null -> can only at depth - 1 int l = left(j+1); } } } /* ------------------------ Utils -------------------- */ /** * Performs the exponent of num to the power of * pow. * @param num Base. * @param pow Exponent. */ public int power(int num, int pow) { int result = 1; for (int i = 0; i < pow; i++) result *= num; return result; } /** * @return The position of the parent of node i. * @param i The node, whose parent is of interest. */ public int parent(int i) { return (i/2); } /** * Returns the left child of node i. */ public int left(int i) { return (2*i); } /** * Returns the right child of node i. */ public int right(int i) { return (2*i + 1); } /* ------------------------ Graphical Primitives ------------------ */ /** * Draws all graphical objects within this class. * @param g Graphical context. */ public void drawTree(Graphics g) { for (int i = 0; i < inputList.size(); i++) { drawLeafNode(g, (Node)inputList.elementAt(i)); } // draw input box if (inputList.size() > 0) new ComBox( ((Node)inputList.lastElement()).x + 50, bottomMostPosn(posn) + 85, "Input", Color.white, darkBlue, fixFont ).draw(g); for (int i = 0; i < heapSize(); i++) { Node node = (Node)nodeList.elementAt(i); drawNode(g, node); } for (int i = 0; i < outputList.size(); i++) drawLeafNode(g, (Node)outputList.elementAt(i)); if (movingNode.getWeight() > -1) drawNode(g, movingNode); for (int i = 0; i < heapArray.size(); i++) { Node node = (Node)heapArray.elementAt(i); drawArrayNode(g, node.x, node.y, node); } if (exchanging) drawArrow(g, aPoints); // draw tree box new ComBox( ((Node)posnList.lastElement()).x + 40, ((Node)posnList.lastElement()).y - 40, "tree representation of the same heap", Color.white, darkRed, fixFont ).draw(g); // draw array box if (inputList.size() == 0) new ComBox( max_size*22 + drawingPanel.offset + 30, bottomMostPosn(posn) + 50, "array representation of the heap", Color.white, darkRed, fixFont ).draw(g); // draw output box new ComBox( drawingPanel.offset, 10, "Output", Color.white, darkBlue, fixFont ).draw(g); if (runningCom != null) runningCom.draw(g); } /** * Draw an arrowed line segment using the array of points. * @param g Graphical context * @param points The vortices of the line segment. * An arrow head will be drawn using the last point in the array at the tip. */ public void drawArrow(Graphics g, Point[] points) { // remember: no drawPolyline in jdk1.0.* int[] axPoints = new int[3], ayPoints = new int[3]; g.setColor( Color.magenta ); if (points.length < 3) { Point endArrow = points[1], startArrow = points[0]; if (endArrow.y == startArrow.y) { g.drawLine(startArrow.x, startArrow.y, endArrow.x, endArrow.y); axPoints[0] = endArrow.x; ayPoints[0] = endArrow.y; int unit = (endArrow.x - startArrow.x)/ Math.abs(endArrow.x - startArrow.x); axPoints[1] = endArrow.x - unit*2; ayPoints[1] = endArrow.y - 2; axPoints[2] = endArrow.x - unit*2; ayPoints[2] = endArrow.y + 2; g.fillPolygon(axPoints, ayPoints, 3); } else if (endArrow.x == startArrow.x) { g.drawLine(startArrow.x, startArrow.y, endArrow.x, endArrow.y); axPoints[0] = endArrow.x; ayPoints[0] = endArrow.y; int unit = (endArrow.y - startArrow.y)/ Math.abs(endArrow.y - startArrow.y); axPoints[1] = endArrow.x - 1; ayPoints[1] = endArrow.y - unit*2; axPoints[2] = endArrow.x + 2; ayPoints[2] = endArrow.y - unit*2; g.fillPolygon(axPoints, ayPoints, 3); } } else if (points.length == 3) { // draw a line for the first and second points then use // drawArrow to draw the remaining segment g.drawLine(points[0].x, points[0].y, points[1].x, points[1].y); Point[] line = new Point[2]; line[0] = points[1]; line[1] = points[2]; drawArrow(g, line); } } /** * @param node The root node of the tree * @return The width of the tree in the number of pixels. */ public int treeWidth(Node node) { return (rightMostPosn(node) - leftMostPosn(node)); } /** * @param node The root node of the tree * @return The left most position of the tree */ public int leftMostPosn(Node node) { if (node.isLeaf()) return node.x; else return leftMostPosn(node.getLeftNode()); } /** * @param node The root node of the tree * @return The right most position of the tree */ public int rightMostPosn(Node node) { if (node.isLeaf()) return (node.x + 20); else return rightMostPosn(node.getRightNode()); } /** * @param node The root node of the tree * @return The height of the tree in the number of pixels. */ public int treeHeight(Node node) { return (bottomMostPosn(node) - node.y); } /** * @param node The root node of the tree * @return The bottom most position of the tree */ public int bottomMostPosn(Node node) { if (node.isLeaf()) return (node.y + 30); else { int rightBottom = bottomMostPosn(node.getRightNode()); int leftBottom = bottomMostPosn(node.getLeftNode()); return (rightBottom > leftBottom ? rightBottom : leftBottom); } } /** * Drawing a leaf node of the heap. * @param g Graphical context * @param node The node to be drawn */ public void drawLeafNode(Graphics g, Node node) { int x = node.x; int y = node.y; int weight = node.getWeight(); if (node.highlight) g.setColor(Color.black); else g.setColor( Color.blue ); g.fillRect(x, y, 20, 30); g.setColor(Color.black); g.drawRect(x, y, 20, 30); g.setFont(hugeFont); g.setColor( Color.yellow ); g.drawString(""+weight, x+2, y+25); } /** * Draw a node of the array representation of the heap. * @param g Graphical context * @param x The x coordinate of the top left corner of the node * @param y The y coordinate of the top left corner of the node * @param node The node to be drawn */ public void drawArrayNode(Graphics g, int x, int y, Node node) { int weight = node.getWeight(); if (node.highlight) g.setColor(Color.black); else g.setColor( Color.red ); g.fillRect(x, y, 20, 30); g.setColor(Color.black); g.drawRect(x, y, 20, 30); g.setFont(hugeFont); g.setColor( Color.yellow ); g.drawString(""+weight, x+2, y+25); } /** * Drawing a node based on the input parameters. * @param g Graphical context * @param node The node to be drawn */ public void drawNode(Graphics g, Node node) { if (node.highlight) g.setColor(Color.black); else g.setColor( darkgreen ); g.fillRect(node.x, node.y, 20, 30); g.setColor(Color.black); g.drawRect(node.x, node.y, 20, 30); g.setColor( Color.white ); g.setFont( hugeFont ); g.drawString(""+node.getWeight(), node.x + 2, node.y+25); // draw links to children if (node.getLeftNode() != null) { g.setColor( Color.black ); g.drawLine(node.x + 6, node.y + 30, node.getLeftNode().x + 10, node.getLeftNode().y); if (node.highlightLeft) { g.drawLine(node.x + 5, node.y + 30, node.getLeftNode().x + 9, node.getLeftNode().y); g.drawLine(node.x + 4, node.y + 30, node.getLeftNode().x + 8, node.getLeftNode().y); } } if (node.getRightNode() != null) { g.drawLine(node.x + 14, node.y + 30, node.getRightNode().x + 10, node.getRightNode().y); if (node.highlightRight) { g.drawLine(node.x + 15, node.y + 30, node.getRightNode().x + 11, node.getRightNode().y); g.drawLine(node.x + 16, node.y + 30, node.getRightNode().x + 12, node.getRightNode().y); } } } /** * Move the tree starting with node dx pixels to the right and dy * pixels down. * @param node The root node of the tree to be moved. * @param dx The change in x direction. * @param dy The change in y direction. */ public void moveNode(Node node, int dx, int dy) { node.x += dx; node.y += dy; if (!node.isLeaf()) { moveNode(node.getLeftNode(), dx, dy); moveNode(node.getRightNode(), dx, dy); } } }