簡體   English   中英

二進制搜索樹JavaScript實現 - 刪除功能

[英]Binary Search Tree JavaScript implementation - remove function

這是我在JavaScript中使用二進制搜索樹的實現。 除了remove功能外,所有功能似乎都正常工作。 具體來說,它似乎正在正確刪除節點,直到樹中剩下2個節點:

var binaryTreeNode = function (value) {
  return {
    value : value,
    left  : null,
    right : null
  };
};

var binarySearchTree = function () {
  var tree  = Object.create( binarySearchTreeMethods );
  tree.root = null;
  return tree;
};

var binarySearchTreeMethods = {

  insert: function (value, node) {
    var newNode = binaryTreeNode( value );

    // check if tree is empty
    if ( this.isEmpty() ) {
      this.root = newNode;
      return;
    }

    // initialize node
    if ( node === void 0 ) node = this.root;

    // compare value with node.value
    if ( value <= node.value ) {
      // check if left exists
      if ( node.left ) {
        this.insert( value, node.left );
      } else {
        node.left = newNode;
      }
    } else {
      if ( node.right ) {
        this.insert( value, node.right );
      } else {
        node.right = newNode;
      }
    }
  },

  remove: function (value, node) {
    var nextRightValue, nextLeftValue, minRight;

    if ( !this.isEmpty() ) {
      // initialize node
      if ( node === void 0 ) node = this.root;

      // compare the node's value with the value
      if ( value < node.value ) {
        // check if there is a left node
        if ( node.left ) {
          node.left = this.remove( value, node.left );
        }
      } else if ( value > node.value ) {
        // check if there is a right node
        if ( node.right ) {
          node.right = this.remove( value, node.right );
        }
      } else {
        // at this point, value === node.value
        // check if node is a leaf node
        if ( node.left === null && node.right === null ) {
          // edge case of single node in tree (i.e. root node)
          if ( this.getHeight() === 0 ) {
            this.root = null;
            return this.root;
          } else {
            node = null;
          }
        } else if ( node.left === null ) {
          node = node.right;
        } else if ( node.right === null ) {
          node = node.left;
        } else {
          // node has both left and right
          minRight   = this.findMinValue( node.right );
          node.value = minRight;
          node.right = this.remove( minRight, node.right );
        }
      }
      return node;
    }
  },

  contains: function (value, node) {
    if ( this.isEmpty() ) return false;
    // tree is not empty - initialize node
    if ( node === void 0 ) node = this.root;

    // check if node's value is the value
    if ( value === node.value ) return true;
    if ( value < node.value ) {
      // check if left node exists
      return node.left ? this.contains( value, node.left ) : false;
    } else {
      // check if right node exists
      return node.right ? this.contains( value, node.right ) : false;
    }
  },

  findMaxValue: function (node) {
    if ( !this.isEmpty() ) {
      if ( node === void 0 ) node = this.root;
      while ( node.right ) {
        node = node.right;
      }
      return node.value;
    }
  },

  findMinValue: function (node) {
    if ( !this.isEmpty() ) {
      if ( node === void 0 ) node = this.root;
      while ( node.left ) {
        node = node.left;
      }
      return node.value;
    }
  },

  getHeight: function (node) {
    if ( !this.isEmpty() ) {
      // initialize node
      if ( node === void 0 ) node = this.root;

      // base case
      if ( node.left  === null && node.right === null ) return 0;
      if ( node.left  === null ) return 1 + this.getHeight( node.right );
      if ( node.right === null ) return 1 + this.getHeight( node.left );
      return 1 + Math.max( this.getHeight( node.left ), this.getHeight( node.right ) );
    }
  },

  isEmpty: function () {
    return this.root === null;
  }

};

將值插入二叉搜索樹工作正常:

var bst = binarySearchTree();
bst.insert(10);
bst.insert(5);
bst.insert(20);
bst.insert(30);
bst.insert(22);
bst.insert(18);

每當我開始刪除root值時,我遇到了一個問題:

bst.remove(10); // this works fine and the resulting bst tree is structurally correct
bst.remove(18); // this works fine and the resulting bst tree is structurally correct
bst.remove(20); // this works fine and the resulting bst tree is structurally correct
bst.remove(22); // this works fine and the resulting bst tree is structurally correct
bst.remove(30); // THIS IS WHERE THE ISSUE OCCURS

在刪除30之前,樹只有兩個值:30作為根值,5作為root.left值。 我希望刪除30會給我一棵樹,其中有5根作為根。 但是,刪除30對樹沒有任何作用; 它保持不變。

進一步的測試表明,如果我先刪除5然后刪除30,那么一切都正常:

bst.remove(10); // this works fine and the resulting bst tree is structurally correct
bst.remove(18); // this works fine and the resulting bst tree is structurally correct
bst.remove(20); // this works fine and the resulting bst tree is structurally correct
bst.remove(22); // this works fine and the resulting bst tree is structurally correct
bst.remove(5);  // Results in a tree with 30 as the root value
bst.remove(30); // Results in the empty tree where root === null

任何人都可以幫助我理解為什么刪除30最初不起作用?

當找到的節點是根節點並且它是樹中的唯一節點,並且如果節點同時具有左子節點和右子節點時,您的代碼可以為此情況提供條件,則會覆蓋其值。 但是當要刪除的節點是根並且它只有一個子節點時,代碼中沒有任何內容覆蓋this.root ,並且您不會覆蓋根的值,因此不會刪除它並且樹保持不變。

您可以通過更改此設置來解決此問題

if ( node === void 0 ) node = this.root;

// compare the node's value with the value
if ( value < node.value ) {

對此:

if ( node === void 0 ) {
    this.root = this.remove(value, this.root);
// compare the node's value with the value
} else if ( value < node.value ) {

修復后,您可以簡化邏輯:

remove: function (value, node) {
    if (!this.isEmpty()) {
        // initialize node
        if (!node) {
            this.root = this.remove(value, this.root);
        } else if (value < node.value && node.left) {
            node.left = this.remove(value, node.left);
        } else if (value > node.value && node.right) {
            node.right = this.remove(value, node.right);
        } else if (value === node.value) {
            // check if node is a leaf node
            if (node.left && node.right) {
                // node has two children. change its value to the min
                // right value and remove the min right node
                node.value = this.findMinValue(node.right);
                node.right = this.remove(node.value, node.right);
            } else {
                // replace the node with whichever child it has
                node = node.left || node.right;
            }
        }
        return node;
    }
},

然后你可以通過將它分成兩個方法來進一步簡化它:

remove: function (value) {
    this.root = this._removeInner(value, this.root);
},

_removeInner: function (value, node) {
    if (node) {
        if (value < node.value) {
            node.left = this._removeInner(value, node.left);
        } else if (value > node.value) {
            node.right = this._removeInner(value, node.right);
        } else if (node.left && node.right) {
            node.value = this.findMinValue(node.right);
            node.right = this._removeInner(node.value, node.right);
        } else {
            node = node.left || node.right;
        }
    }
    return node;
},

演示


@wmock問我是如何解決這個問題的,所以我會詳細說明一下。

我做的第一件事是在調試器中執行代碼,重點關注bst.remove(30)部分。 我注意到30就是那個時候的根,並且在remove()完成后它仍然存在。 這讓我注意到代碼永遠不會修改特定情況下的根。

然后我研究了如何將this.remove()的返回值分配給node.leftnode.right ,以及對BST算法的一些回憶,認為對於根也是有意義的。 這確實是答案。

有一些事情促使將該方法分為兩種方法:

  • 我注意到該方法具有相當多的特殊功能,僅與初始調用bst.remove()
    • 檢查this.isEmpty()
    • 使用this.root的價值node ,如果node為空
    • 在樹高為0的某些情況下,將this.root重置為null

通過remove()在每次傳遞中做所有這些似乎很草率

  • 我也反復發現自己想要使用if (!node)來檢查我是否已到達樹的邊緣,但我不能,因為當node為null時,有一個特殊情況邏輯來使用this.root

將方法拆分為兩部分解決了上述所有問題。

請注意,在許多BST實現中, _removeInner()的功能將是binaryTreeNode類型的方法,並且樹將僅與根節點交互。 這消除了將節點從一個方法調用傳遞到下一個方法的需要:

binarySearchTree

remove: function (value) {
    if (value < this.value) {
        this.left = this.left && this.left.remove(value);
    } else if (value > this.value) {
        this.right = this.right && this.right.remove(value);
    } else if (this.left && this.right) {
        this.value = this.right.findMinValue();
        this.right = this.right.remove(this.value);
    } else {
        return this.left || this.right;
    }
    return this;
},

findMinValue: function () {
    return this.left ? this.left.findMinValue() : this.value;
}

binaryTreeNode

 remove: function (value) { if (value < this.value) { this.left = this.left && this.left.remove(value); } else if (value > this.value) { this.right = this.right && this.right.remove(value); } else if (this.left && this.right) { this.value = this.right.findMinValue(); this.right = this.right.remove(this.value); } else { return this.left || this.right; } return this; }, findMinValue: function () { return this.left ? this.left.findMinValue() : this.value; } 

演示

以下是具有插入刪除功能的二叉樹的完整示例

function Node(val) {
    this.data = val;
    this.right = null;
    this.left = null;
}

function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
    this.remove = remove;
    this.removeNode = removeNode;
    this.kthSmallestNode = kthSmallestNode;
}

function insert(val) {
    if (val == null || val == undefined)
        return;

    if (this.root == null) {
        this.root = new Node(val);
        return;
    }

    var current = this.root
    var newNode = new Node(val);

    while (true) {
        if (val < current.data) {
            if (current.left == null) {
                current.left = newNode;
                return;
            }
            current = current.left;
        } else {
            if (current.right == null) {
                current.right = newNode;
                return;
            }
            current = current.right;
        }
    }

}

function remove(val) {
    this.root = removeNode(this.root, val);
}

function removeNode(current, value) {
    if (value == null || value == undefined)
        return;

    if (value == current.data) {
        if (current.left == null && current.right == null) {
            return null;
        } else if (current.left == null)
            return current.right;
        else if (current.right == null)
            return current.left;
        else {
            var tempNode = kthSmallestNode(current.right);
            current.data = tempNode.data;
            current.right = removeNode(current.right, tempNode.data);
            return current;
        }


    } else if (value < current.data) {
        current.left = removeNode(current.left, value);
        return current;
    } else {
        current.right = removeNode(current.right, value);
        return current;
    }
}

function kthSmallestNode(node) {
    while (!(node.left == null))
        node = node.left;

    return node;
}

function inOrder(node) {
    if (!(node == null)) {
        inOrder(node.left);
        console.log(node.data + " ");
        inOrder(node.right);
    }
}


var tree = new BST();
tree.insert(25);
tree.insert(20);
tree.insert(30);
tree.insert(27);
tree.insert(21);
tree.insert(16);
tree.insert(26);
tree.insert(35);

tree.remove(30)

console.log("Inorder : ")
console.log(tree.inOrder(tree.root))

祝好運!!!

我有一個非常簡化的答案,我認為大多數人會理解,並考慮到兒童節點。 關鍵是如果你要刪除一個左右孩子的值,你先離開然后一直向右,因為這可以確保你沒有孩子並且更容易更新。

  removeNode(val) {
    let currentNode, parentNode, nextBiggestParentNode=null, found=false, base=[this.root];
    while(base.length > 0 && !found) {
      currentNode = base.pop();
      if(currentNode.value === val) {
        found=true;
        if(!currentNode.left && !currentNode.right) {
          parentNode.right === currentNode ? parentNode.right = null : parentNode.left = null;
        }
        else if(!currentNode.right && currentNode.left) {
          parentNode.right === currentNode ? parentNode.right = currentNode.left : parentNode.left = currentNode.left;
        }
        else if(!currentNode.left && currentNode.right) {
          parentNode.right === currentNode ? parentNode.right = currentNode.right : parentNode.left = currentNode.right;
        }
        else {
          let _traverse = node => {
            if (node.right) {
              nextBiggestParentNode = node;
              _traverse(node.right);
            }
            else {
              currentNode.value = node.value;
              nextBiggestParentNode ? nextBiggestParentNode.right = null : currentNode.left = null;
            }
          }
          _traverse(currentNode.left);
        }
      }
      else {
        parentNode = currentNode;
        val > currentNode.value && currentNode.right ? base.unshift(currentNode.right) : base.unshift(currentNode.left);
      }
    }
    return this;
  }

該代碼是類的一部分,如果有人感興趣,這是我的構造函數代碼的其余部分

let TreeNode = class  {
  constructor(value, left=null, right=null) {
    this.value = value;
    this.left = left;
    this.right = right;
  }
}

let BST = class {
  constructor(root=null) {
    this.root = root;
  }

  insert(nodeToInsert) {
    if (this.root === null) {
      this.root = nodeToInsert;
    } else {
      this._insert(this.root, nodeToInsert);
    }
  }

  _insert(root, nodeToInsert) {
    if (nodeToInsert.value < root.value) {
      if (!root.left) {
        root.left = nodeToInsert;
      } else {
        this._insert(root.left, nodeToInsert);
      }
    } else {
      if (!root.right) {
        root.right = nodeToInsert;
      } else {
        this._insert(root.right, nodeToInsert);
      }
    }
  }

這里有一些演示代碼來創建一個bst並刪除一個值

let bst = new BST();
const nums = [20,10,5,15,3,7,13,17,30,35,25,23,27,37,36,38];
function createBst() {
  for (let i of nums) {
    bst.insert(new TreeNode(i));
  }
  console.log(JSON.stringify(bst, null, 2));
  bst.removeNode(35);
}
createBst();
console.log(JSON.stringify(bst, null, 2));

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM