[TOC]
binary search tree definition
The value of the head node of each subtree is greater than the value of all nodes in its left subtree, and it is also smaller than the value of all nodes in its right subtree.
The inorder traversal sequence of a binary search tree must be arranged from small to large.
Binary search tree node definition
/// <summary>
/// 二叉查找树节点
/// </summary>
public class Node
{
/// <summary>
/// 节点值
/// </summary>
public int Data { get; set; }
/// <summary>
/// 左子节点
/// </summary>
public Node Left { get; set; }
/// <summary>
/// 右子节点
/// </summary>
public Node Right { get; set; }
/// <summary>
/// 打印节点值
/// </summary>
public void DisplayNode()
{
Console.Write(Data + " ");
}
}
insert node
The operation of inserting a node in a binary search tree is relatively simple, and it is only necessary to find the position where the node to be inserted is placed.
The overall process of inserting a node:
- Set the parent node to the current node, the root node.
If the data value in the new node is less than the data value in the current node, then set the current node as the left child of the current node. If the data value in the new node is greater than the data value in the current node, then skip to step 4.
If the value of the left child of the current node is null, insert the new node here and exit the loop. Otherwise, skip to the next loop operation of the while loop.
- Sets the current node as the right child of the current node.
If the value of the right child of the current node is null, insert the new node here and exit the loop. Otherwise, skip to the next loop operation of the while loop.
Code:
public class BinarySearchTree
{
public Node root;
public BinarySearchTree()
{
root = null;
}
/// <summary>
/// 二叉查找树插入结点
/// </summary>
/// <param name="i"></param>
public void Insert(int i)
{
Node newNode = new Node
{
Data = i
};
if (root == null)
{
root = newNode;
}
else
{
Node current = root;
Node parent;
while (true)
{
parent = current;
if (i < current.Data)
{
current = current.Left;
if (current == null)
{
parent.Left = newNode;
break;
}
}
else
{
current = current.Right;
if (current == null)
{
parent.Right = newNode;
break;
}
}
}
}
}
}
Because the in-order traversal sequence of a binary search tree must be arranged from small to large, we can test the insertion operation of a binary search tree through in-order traversal. Regarding the binary tree traversal operation, you can move to my last blog [Graphic Data Structure] Binary tree traversal .
In-order traversal code implementation:
/// <summary>
/// 二叉查找树中序遍历
/// </summary>
/// <param name="node"></param>
public void InOrder(Node node)
{
if (node != null)
{
InOrder(node.Left);
node.DisplayNode();
InOrder(node.Right);
}
}
Test code:
class BinarySearchTreeTest
{
static void Main(string[] args)
{
BinarySearchTree bst = new BinarySearchTree();
bst.Insert(23);
bst.Insert(45);
bst.Insert(16);
bst.Insert(37);
bst.Insert(3);
bst.Insert(99);
bst.Insert(22);
Console.WriteLine("中序遍历: ");
bst.InOrder(bst.root);
Console.ReadKey();
}
}
Test Results:
The above test code forms a binary search tree like this:
find node
There are three easiest things to do with a binary search tree (BST): find a particular number, find the minimum value, and find the maximum value.
find the minimum value
According to the nature of the binary search tree, the minimum value of the binary search tree must be the leftmost child node of the left subtree.
So the implementation is very simple, that is, starting from the root node to find the leftmost child node of the left subtree of the binary search tree.
Code:
/// <summary>
/// 查找二叉查找树最小值
/// </summary>
/// <returns></returns>
public int FindMin()
{
Node current = root;
while (current.Left != null)
{
current = current.Left;
}
return current.Data;
}
Find the maximum value
According to the nature of the binary search tree, the maximum value of the binary search tree must be the rightmost child node of the right subtree.
So the implementation is very simple, that is, starting from the root node to find the rightmost child node of the right subtree of the binary search tree.
Code:
/// <summary>
/// 查找二叉查找树最大值
/// </summary>
/// <returns></returns>
public int FindMax()
{
Node current = root;
while (current.Right != null)
{
current = current.Right;
}
return current.Data;
}
Find a specific value
According to the nature of the binary search tree, starting from the root node, compare the size of the specific value and the value of the root node. If it is greater than the value of the root node, it means that the specific value is on the right subtree of the root node, and continue to perform this operation on the right child node; if it is smaller than the value of the root node, it means that the specific value is on the left subtree of the root node. , continue doing this on the left child. If no node value equal to the specified value is found until the execution is complete, then there is no node containing this specified value in the binary search tree.
Code:
/// <summary>
/// 查找二叉查找树特定值节点
/// </summary>
/// <param name="key">特定值</param>
/// <returns></returns>
public Node Find(int key)
{
Node current = root;
while (current.Data != key)
{
if (key < current.Data)
{
current = current.Left;
}
if (key > current.Data)
{
current = current.Right;
}
// 如果已到达 BST 的末尾
if (current == null)
{
return null;
}
}
return current;
}
delete node
Compared with the previous operation, the operation of deleting a node in a binary search tree is more complicated, because deleting a node will risk destroying the correct
hierarchical order of the BST.
We all know that nodes in a binary search tree can be divided into: nodes with no children, nodes with one child, and nodes with two children. Then we can simply split the delete node operation of the binary search tree to facilitate our understanding. As shown below:
delete leaf node
Deleting leaf nodes is the easiest thing to do. The only thing to do is to set a child node of the parent node of the target node to null.
Check whether the left child node and right child node of this node are empty (null), if both are empty (null), it indicates that it is a leaf node.
Then check if this node is the root node. If so, set it to null.
Otherwise, if isLeftChild is true, set the parent node's left child node to null (null); if isLeftChild is false, set the parent node's right child node to null (null).
Code:
//要删除的结点是叶子结点的处理
if (current.Left == null && current.Right == null)
{
if (current == root)
root = null;
else if (isLeftChild)
parent.Left = null;
else
{
parent.Right = null;
}
}
delete a node with one child
When the node to be deleted has a child node, four conditions need to be checked:
- The children of this node may be left children;
- The child node of this node may be the right child node;
- The node to be deleted may be the left child;
- The node to be deleted may be the right child.
Code:
//要删除的结点是带有一个子节点的节点的处理
//首先判断子结点是左子节点还是右子节点,然后再判断当前节点是左子节点还是右子节点
else if (current.Right == null)
if (current == root)
root = current.Left;
else if (isLeftChild)
parent.Left = current.Left;
else
parent.Right = current.Left;
else if (current.Left == null)
if (current == root)
root = current.Right;
else if (isLeftChild)
parent.Left = current.Right;
else
parent.Right = current.Right;
delete a node with two children
If you want to delete the node marked 52, you need to rebuild the tree. There is no way to replace it with a subtree starting at 54, because 54 already has a left child. The answer to this question is to move the in-order successor node to the position where the node is to be deleted. Of course, it is also necessary to distinguish whether the successor node itself has child nodes.
Here we need to understand the definition of successor nodes .
The successor of a node refers to the next node in the in-order traversal sequence of this node. Correspondingly, the predecessor node refers to the previous node of this node in the in-order traversal sequence.
For example, the in-order traversal sequence of the binary tree in the following figure is: DBEAFCG, then the successor node of A is F, and the predecessor node of A is E.
Knowing this, the operation of deleting a node with two child nodes can be transformed into finding the successor node of the node to be deleted and assigning the right subtree of the node to be deleted to the right child node of the successor node . It should be noted here that if If the successor node itself has child nodes, you need to assign the child node of the successor node to the left child node of the parent node of the successor node.
First, let's get the code of the subsequent node, and then give an example to illustrate:
/// <summary>
/// 获取后继结点
/// </summary>
/// <param name="delNode">要删除的结点</param>
/// <returns></returns>
public Node GetSuccessor(Node delNode)
{
//后继节点的父节点
Node successorParent = delNode;
//后继节点
Node successor = delNode.Right;
Node current = delNode.Right.Left;
while (current != null)
{
successorParent = successor;
successor = current;
current = current.Left;
}
//如果后继结点不是要删除结点的右子结点,
//则要将后继节点的子结点赋给后继节点父节点的左节点
//删除结点的右子结点赋给后继结点作为 后继结点的后继结点
if (successor != delNode.Right)
{
successorParent.Left = successor.Right;
successor.Right = delNode.Right;
}
return successor;
}
Code implementation to delete a node with two children:
//要删除的结点是带有两个子节点的节点的处理
else
{
Node successor = GetSuccessor(current);
if (current == root)
root = successor;
else if (isLeftChild)
parent.Left = successor;
else
parent.Right = successor;
//因为后继结点是要删除结点右子树的最左侧结点
//所以后继结点的左子树肯定是要删除结点左子树
successor.Left = current.Left;
}
We observe that the successor of the deleted node must be the leftmost node of the right subtree of the deleted node . There are 3 cases here:
Successor nodes are children of the deleted node
The node 37 is deleted, and the successor node 40 is a child node of the deleted node 37 . delNode
It is node 37, it successor
is node 40, it delNode.Right
is node 40, successor == delNode.Right
and the successor node is the child node of the deleted node. This situation is the simplest.
The successor node is not a child of the deleted node
Successor node 38 is the leftmost node of the right subtree of deletion node 37 . delNode
It's node 37, successor
it's node 38, successorParent
it's node 40, delNode.Right
it's node 40. successor != delNode.Right
, so to successorParent.Left = successor.Right;successor.Right = delNode.Right;
. Because successor.Right==null
, so successorParent.Left = null
. successor.Right = delNode.Right
, node 40 becomes the right child of node 38. Because the successor node of the deleted node must be the leftmost node of the right subtree of the deleted node, the successor node must have no left child node . After the deleted node is deleted, the successor node will be added to the position of the deleted node. successor.Left = current.Left;
, that is, the left child of the deleted node becomes the left child of the successor node.
The searched binary tree after finishing removing nodes becomes:
The successor node is not a child of the deleted node and has children
This case is similar to the previous case, the only difference is that the successor node has a child node (note that it must be the right child node). That is successorParent.Left = successor.Right;
, the right child of the successor node becomes the left child of the parent node of the successor node. Since it successor.Right
is node 39, the left child of node 40 becomes node 39. Other operations are exactly the same as in the previous case.
The searched binary tree after finishing removing nodes becomes:
The overall process of deleting a node operation:
- Assign the right child of the successor node as the left child of the parent node of the successor node.
- Assign the right child of the node to be deleted as the right child of the successor node.
- Remove the current node from the right child of the parent node and point it to the successor node.
- Removes the left child of the current node from the current node and points it to the left child of the successor node.
Combining the above three cases of deleting nodes, the complete code for deleting nodes is as follows:
/// <summary>
/// 二叉查找树删除节点
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool Delete(int key)
{
//要删除的当前结点
Node current = root;
//当前结点的父结点
Node parent = root;
//当前结点是否是左子树
bool isLeftChild = true;
//先通过二分查找找出要删除的结点
while (current.Data != key)
{
parent = current;
if (key < current.Data)
{
isLeftChild = true;
current = current.Left;
}
else
{
isLeftChild = false;
current = current.Right;
}
if (current == null)
return false;
}
//要删除的结点是叶子结点的处理
if (current.Left == null && current.Right == null)
{
if (current == root)
root = null;
else if (isLeftChild)
parent.Left = null;
else
{
parent.Right = null;
}
}
//要删除的结点是带有一个子节点的节点的处理
else if (current.Right == null)
if (current == root)
root = current.Left;
else if (isLeftChild)
parent.Left = current.Left;
else
parent.Right = current.Left;
else if (current.Left == null)
if (current == root)
root = current.Right;
else if (isLeftChild)
parent.Left = current.Right;
else
parent.Right = current.Right;
//要删除的结点是带有两个子节点的节点的处理
else
{
Node successor = GetSuccessor(current);
if (current == root)
root = successor;
else if (isLeftChild)
parent.Left = successor;
else
parent.Right = successor;
//因为后继结点是要删除结点右子树的最左侧结点
//所以后继结点的左子树肯定是要删除结点左子树
successor.Left = current.Left;
}
return true;
}
/// <summary>
/// 获取后继结点
/// </summary>
/// <param name="delNode">要删除的结点</param>
/// <returns></returns>
public Node GetSuccessor(Node delNode)
{
//后继节点的父节点
Node successorParent = delNode;
//后继节点
Node successor = delNode.Right;
Node current = delNode.Right.Left;
while (current != null)
{
successorParent = successor;
successor = current;
current = current.Left;
}
//如果后继结点不是要删除结点的右子结点,
//则要将后继节点的子结点赋给后继节点父节点的左节点
//删除结点的右子结点赋给后继结点作为 后继结点的后继结点
if (successor != delNode.Right)
{
successorParent.Left = successor.Right;
successor.Right = delNode.Right;
}
return successor;
}
delete node test
We still use in-order traversal for testing, first constructing a binary search tree:
static void Main(string[] args)
{
BinarySearchTree bst = new BinarySearchTree();
bst.Insert(23);
bst.Insert(45);
bst.Insert(16);
bst.Insert(37);
bst.Insert(3);
bst.Insert(99);
bst.Insert(22);
bst.Insert(40);
bst.Insert(35);
bst.Insert(38);
bst.Insert(44);
bst.Insert(39);
}
The constructed binary search tree:
The test is divided into three cases:
Test delete leaf nodes
delete leaf node 39
Console.Write("删除节点前: ");
bst.InOrder(bst.root);
bst.Delete(39);
Console.Write("删除节点后: ");
bst.InOrder(bst.root);
Test Results:
Test to delete a node with one child
delete node 38 with one child
Console.Write("删除节点前: ");
bst.InOrder(bst.root);
bst.Delete(38);
Console.Write("删除节点后: ");
bst.InOrder(bst.root);
Test Results:
Test deleting a node with two children
delete node 37 with two children
Console.Write("删除节点前: ");
bst.InOrder(bst.root);
bst.Delete(37);
Console.Write("删除节点后: ");
bst.InOrder(bst.root);
Test Results:
refer to:
"Data Structure and Algorithm C# Language Description"
"Dahua Data Structure"
"Data Structure and Algorithm Analysis C Language Description"
Everyone went out to be happy on May 1st, why do I have to code at home by myself, is it because of love? Is it because of responsibility? neither. It's because I only have learning in my heart (actually because of poverty ). Haha, I wish everyone a happy May Day in advance, and it’s fun to eat!