二叉搜索树左旋转失败

问题描述 投票:0回答:1

我的二分搜索树旋转程序的所有测试都通过了,除非 Gradescope 运行左测试旋转。使用的数据正在创建一个级别顺序为:2,1,4,3,6,5,7的搜索树,围绕4和2旋转,预期的级别顺序结果为:4,2,6,1,3,5 , 7 但在我的 IDE 上我得到了 2, 1, 3,我假设将一些节点设置为 null。我认为我的左旋转算法有问题,但我不太确定它是什么。有什么想法吗?

`

// --== CS400 Rotation Implementation Activity File Header ==--
// Name: Arber Jonuzi
// CSL Username: arber
// Email: [email protected]
// Lecture #: 003 @2:25pm
// Notes to Grader: N/A



import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Stack;

/**
 * Red-Black Tree implementation with a Node inner class for representing
 * the nodes of the tree. Currently, this implements a Binary Search Tree that
 * we will turn into a red black tree by modifying the insert functionality.
 * In this activity, we will start with implementing rotations for the binary
 * search tree insert algorithm. You can use this class' insert method to build
 * a regular binary search tree, and its toString method to display a level-order
 * traversal of the tree.
 */
public class RedBlackTree<T extends Comparable<T>> {

    /**
     * This class represents a node holding a single value within a binary tree
     * the parent, left, and right child references are always maintained.
     */
    protected static class Node<T> {
        public T data;
        public Node<T> parent; // null for root node
        public Node<T> leftChild;
        public Node<T> rightChild;
        public Node(T data) { this.data = data; }
        /**
         * @return true when this node has a parent and is the left child of
         * that parent, otherwise return false
         */
        public boolean isLeftChild() {
            return parent != null && parent.leftChild == this;
        }
        
        public boolean isRightChild() {
            return parent != null && parent.rightChild == this;
        }

    }

    protected Node<T> root; // reference to root node of tree, null when empty
    protected int size = 0; // the number of values in the tree

    /**
     * Performs a naive insertion into a binary search tree: adding the input
     * data value to a new node in a leaf position within the tree. After  
     * this insertion, no attempt is made to restructure or balance the tree.
     * This tree will not hold null references, nor duplicate data values.
     * @param data to be added into this binary search tree
     * @return true if the value was inserted, false if not
     * @throws NullPointerException when the provided data argument is null
     * @throws IllegalArgumentException when the newNode and subtree contain
     *      equal data references
     */
    public boolean insert(T data) throws NullPointerException, IllegalArgumentException {
        // null references cannot be stored within this tree
        if(data == null) throw new NullPointerException(
                "This RedBlackTree cannot store null references.");

        Node<T> newNode = new Node<>(data);
        if(root == null) { root = newNode; size++; return true; } // add first node to an empty tree
        else{
            boolean returnValue = insertHelper(newNode,root); // recursively insert into subtree
            if (returnValue) size++;
            else throw new IllegalArgumentException(
                    "This RedBlackTree already contains that value.");
            return returnValue;
        }
    }

    /**
     * Recursive helper method to find the subtree with a null reference in the
     * position that the newNode should be inserted, and then extend this tree
     * by the newNode in that position.
     * @param newNode is the new node that is being added to this tree
     * @param subtree is the reference to a node within this tree which the 
     *      newNode should be inserted as a descenedent beneath
     * @return true is the value was inserted in subtree, false if not
     */
    private boolean insertHelper(Node<T> newNode, Node<T> subtree) {
        int compare = newNode.data.compareTo(subtree.data);
        // do not allow duplicate values to be stored within this tree
        if(compare == 0) return false;

            // store newNode within left subtree of subtree
        else if(compare < 0) {
            if(subtree.leftChild == null) { // left subtree empty, add here
                subtree.leftChild = newNode;
                newNode.parent = subtree;
                return true;
                // otherwise continue recursive search for location to insert
            } else return insertHelper(newNode, subtree.leftChild);
        }

        // store newNode within the right subtree of subtree
        else {
            if(subtree.rightChild == null) { // right subtree empty, add here
                subtree.rightChild = newNode;
                newNode.parent = subtree;
                return true;
                // otherwise continue recursive search for location to insert
            } else return insertHelper(newNode, subtree.rightChild);
        }
    }

    /**
     * Performs the rotation operation on the provided nodes within this tree.
     * When the provided child is a leftChild of the provided parent, this
     * method will perform a right rotation. When the provided child is a
     * rightChild of the provided parent, this method will perform a left rotation.
     * When the provided nodes are not related in one of these ways, this method
     * will throw an IllegalArgumentException.
     * @param child is the node being rotated from child to parent position
     *      (between these two node arguments)
     * @param parent is the node being rotated from parent to child position
     *      (between these two node arguments)
     * @throws IllegalArgumentException when the provided child and parent
     *      node references are not initially (pre-rotation) related that way
     */
    private void rotate(Node<T> child, Node<T> parent) throws IllegalArgumentException {
        
        //right rotation
        if(child == parent.leftChild) {
            
            Node<T> left = null;
            if(child.rightChild != null) {
            left = child.rightChild;}
            
            if(parent.isRightChild()) {
                parent.parent.rightChild=child;
            }
            
            parent.leftChild = left;
            
            parent.parent = child;
            child.parent = parent.parent;
            child.rightChild = parent;
            
            if(parent == root) {
                child.parent = null;
                root = child;
            }
            

        }
        
        //left rotation
        else if(child == parent.rightChild) {
            
            Node<T> left = null;
            if(child.leftChild!=null) {
            left = child.leftChild;}
            

            if(parent.isLeftChild()) {
                parent.parent.leftChild=child;
            }
            
            parent.rightChild = left;
            
            parent.parent = child;
            child.parent = parent.parent;
            child.leftChild = parent;
            
        }
        
        //exception
        else {
            throw new IllegalArgumentException("Wrong left and right child");
        }
        
    }
    
    /**
     * Get the size of the tree (its number of nodes).
     * @return the number of nodes in the tree
     */
    public int size() {
        return size;
    }

    /**
     * Method to check if the tree is empty (does not contain any node).
     * @return true of this.size() return 0, false if this.size() > 0
     */
    public boolean isEmpty() {
        return this.size() == 0;
    }

    /**
     * Checks whether the tree contains the value *data*.
     * @param data the data value to test for
     * @return true if *data* is in the tree, false if it is not in the tree
     */
    public boolean contains(T data) {
        // null references will not be stored within this tree
        if(data == null) throw new NullPointerException(
                "This RedBlackTree cannot store null references.");
        return this.containsHelper(data, root);
    }

    /**
     * Recursive helper method that recurses through the tree and looks
     * for the value *data*.
     * @param data the data value to look for
     * @param subtree the subtree to search through
     * @return true of the value is in the subtree, false if not
     */
    private boolean containsHelper(T data, Node<T> subtree) {
        if (subtree == null) {
            // we are at a null child, value is not in tree
            return false;
        } else {
            int compare = data.compareTo(subtree.data);
            if (compare < 0) {
                // go left in the tree
                return containsHelper(data, subtree.leftChild);
            } else if (compare > 0) {
                // go right in the tree
                return containsHelper(data, subtree.rightChild);
            } else {
                // we found it :)
                return true;
            }
        }
    }


    /**
     * This method performs an inorder traversal of the tree. The string 
     * representations of each data value within this tree are assembled into a
     * comma separated string within brackets (similar to many implementations 
     * of java.util.Collection, like java.util.ArrayList, LinkedList, etc).
     * Note that this RedBlackTree class implementation of toString generates an
     * inorder traversal. The toString of the Node class class above
     * produces a level order traversal of the nodes / values of the tree.
     * @return string containing the ordered values of this tree (in-order traversal)
     */
    public String toInOrderString() {
        // generate a string of all values of the tree in (ordered) in-order
        // traversal sequence
        StringBuffer sb = new StringBuffer();
        sb.append("[ ");
        sb.append(toInOrderStringHelper("", this.root));
        if (this.root != null) {
            sb.setLength(sb.length() - 2);
        }
        sb.append(" ]");
        return sb.toString();
    }

    private String toInOrderStringHelper(String str, Node<T> node){
        if (node == null) {
            return str;
        }
        str = toInOrderStringHelper(str, node.leftChild);
        str += (node.data.toString() + ", ");
        str = toInOrderStringHelper(str, node.rightChild);
        return str;
    }

    /**
     * This method performs a level order traversal of the tree rooted
     * at the current node. The string representations of each data value
     * within this tree are assembled into a comma separated string within
     * brackets (similar to many implementations of java.util.Collection).
     * Note that the Node's implementation of toString generates a level
     * order traversal. The toString of the RedBlackTree class below
     * produces an inorder traversal of the nodes / values of the tree.
     * This method will be helpful as a helper for the debugging and testing
     * of your rotation implementation.
     * @return string containing the values of this tree in level order
     */
    public String toLevelOrderString() {
        String output = "[ ";
        if (this.root != null) {
            LinkedList<Node<T>> q = new LinkedList<>();
            q.add(this.root);
            while(!q.isEmpty()) {
                Node<T> next = q.removeFirst();
                if(next.leftChild != null) q.add(next.leftChild);
                if(next.rightChild != null) q.add(next.rightChild);
                output += next.data.toString();
                if(!q.isEmpty()) output += ", ";
            }
        }
        return output + " ]";
    }

    public String toString() {
        return "level order: " + this.toLevelOrderString() +
                "\nin order: " + this.toInOrderString();
    }

    
    // Implement at least 3 boolean test methods by using the method signatures below,
    // removing the comments around them and addind your testing code to them. You can
    // use your notes from lecture for ideas on concrete examples of rotation to test for.
    // Make sure to include rotations within and at the root of a tree in your test cases.
    // If you are adding additional tests, then name the method similar to the ones given below.
    // Eg: public static boolean test4() {}
    // Do not change the method name or return type of the existing tests.
    // You can run your tests by commenting in the calls to the test methods 

    //pass
    public static boolean test1() {
        
        RedBlackTree test = new RedBlackTree();
        
        try {
            test.insert(1);
            test.insert(10);
            test.insert(16);
            test.insert(43);
            test.insert(6);
            test.insert(11);
            test.insert(18);
            test.rotate(test.root.rightChild.rightChild.leftChild, test.root.rightChild.rightChild);
            System.out.println(test.toString());
            
        }
        catch(IllegalArgumentException e) {
            return false;
        }
    
        return true;
    }
    

    //pass
    public static boolean test2() {
        RedBlackTree test = new RedBlackTree();
        
        try {
            test.insert(4);
            test.insert(2);
            test.insert(6);
            test.insert(1);
            test.insert(3);
            test.insert(5);
            test.insert(7);
            test.rotate(test.root.rightChild.leftChild, test.root.rightChild );
            System.out.println(test.toString());
            
        }
        catch(IllegalArgumentException e) {
            return false;
        }
         
        return true;
    }
    

    //tests  null exception
    public static boolean test3() {
    
        RedBlackTree test = new RedBlackTree();
        
        try {
            test.insert(14);
            test.insert(32);
            test.insert(46);
            test.insert(15);
            test.insert(34);
            test.insert(55);
            test.insert(37);
            test.rotate(test.root.leftChild.rightChild, test.root.leftChild );
            System.out.println(test.toString());
            
        }
        catch(IllegalArgumentException e) {
            return false;
        }
    
        return true;
    }
    
    //pass
    public static boolean test4() {
        RedBlackTree test = new RedBlackTree();
        
        try {
            test.insert(2);
            test.insert(1);
            test.insert(4);
            test.insert(3);
            test.insert(6);
            test.insert(5);
            test.insert(7);
            test.rotate(test.root.rightChild, test.root);
            System.out.println(test.toString());
            
        }
        catch(IllegalArgumentException e) {
            return false;
        }
         
        return true;
    }
    

    /**
     * Main method to run tests. Comment out the lines for each test
     * to run them.
     * @param args
     */
    public static void main(String[] args) {
        // System.out.println("Test 1 passed: " + test1());
        // System.out.println("Test 2 passed: " + test2());
        // System.out.println("Test 3 passed: " + test3());
         System.out.println("Test 4 passed: " + test4());
    }

}

`

我尝试创建其他测试用例来确认,但左旋转似乎仍然是问题。

java testing rotation binary-search-tree
1个回答
-2
投票

问题似乎出在你的左旋转逻辑上。做左旋转时,关键步骤是:

子节点的右子节点成为父节点的左子节点。 父节点成为子节点的右子节点。 相应地更新父指针。 处理特殊情况,例如父节点是根节点等。 查看您的代码,您似乎缺少第 1 步,您需要将孩子的右孩子设置为父母的左孩子。

左旋转逻辑如下所示:

//left rotation 
else if(child == parent.rightChild) {

  Node<T> left = child.leftChild;
  
  parent.rightChild = left;
  
  child.leftChild = parent;
  parent.parent = child;

  if(child.parent == null) {
    root = child;
  }
  else {
    // Update parent pointer of child
    if(child.isLeftChild()) {
      child.parent.leftChild = child;
    } else {
      child.parent.rightChild = child; 
    }
  }

  child.parent = parent. Parent;

}
© www.soinside.com 2019 - 2024. All rights reserved.