二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小,右节点的值要比父节点的值大。它的高度决定了它的查找效率。
在理想的情况下,二叉查找树增删查改的时间复杂度为O(logN)(其中N为节点数),最坏的情况下为O(N)。当它的高度为logN+1时,我们就说二叉查找树是平衡的。
首先定义二叉树节点
class TreeNode {
TreeNode(int value) {
this.value = value;
}
TreeNode left;
TreeNode right;
int value;
}
复制代码
本文中rootNode
为创建二叉搜索树的root节点,在类初始化阶段赋值。
private final TreeNode rootNode;
public BinarySearchTree(int value) {
rootNode = new TreeNode(value);
}
复制代码
插入操作:
首先从root节点开始循环遍历,
- 如果插入节点等于当前root节点,直接结束不做任何处理。
- 如果插入节点小于当前root节点,从左节点开始查找。 (root=root.left)
- 如果插入节点大于当前root节点,从右节点开始查找。 (root=root.right)
- 当当前节点的左节点或者右节点为空时,插入新节点,结束过程。
上代码:
public TreeNode insert(int value) {
TreeNode currentRoot = rootNode;
TreeNode newNode = new TreeNode(value);
while (true) {
if (value == currentRoot.value) {
return null;
}
else if (value < currentRoot.value) {
if (currentRoot.left != null) {
currentRoot = currentRoot.left;
}
else {
return currentRoot.left = newNode;
}
}
else {
if (currentRoot.right != null) {
currentRoot = currentRoot.right;
}
else {
return currentRoot.right = newNode;
}
}
}
}
复制代码
查找操作:
类似于插入操作:
- 如果插入节点等于root节点,查找完成。
- 如果插入节点小于root节点,从left节点开始查找。 (root=root.left)
- 如果插入节点大于root节点,从right节点开始查找。(root=root.right)
上代码:
public TreeNode get(int value) {
TreeNode currentRoot = rootNode;
while (true) {
if (currentRoot == null) {
return null;
}
if (value == currentRoot.value) {
return currentRoot;
}
else if (value < currentRoot.value) {
currentRoot = currentRoot.left;
}
else {
currentRoot = currentRoot.right;
}
}
}
复制代码
遍历操作:
遍历分为
- 前序:根节点放在左节点的左边 (根→左→右)
- 中序:根节点放在左节点和右节点的中间(左→根→右)
- 后序:根节点放在右节点的右边 (左→右→根)
中序遍历的代码:
public void getTreeByInOrder(List<TreeNode> list, TreeNode root) {
if (root != null) {
getTreeByInOrder(list, root.left);
list.add(root);
getTreeByInOrder(list, root.right);
}
}
复制代码
前序后序和其类似,就不贴代码了
层次遍历(也可以叫做广度优先遍历):
思路:
-
由根从上往下循环遍历,利用队列(queue)先进先出的原则,每访问一个节点之后,将其左右子节点入队。这样,同一层的所有节点肯定先于下一层的节点先访问
-
如果需要分层保存节点呢?(比如需要换行打印):问题的难点是如何知道本层已经遍历完了,可以使用两个指针last和nextLast,一个指向当前层的最后一个节点,一个指向下一层的最后一个节点,这样难点转换成了last和nextLast的更新。当节点入队时,更新nextLast(设置nextLast为队列的最后一个节点),因为队列中的最后一个节点一定是下一层的最后节点。
上代码:
public List<List<Integer>> printTreeByHierarchy() {
TreeNode currentRoot = rootNode;
Queue<TreeNode> queue = new LinkedList<>();
TreeNode last = currentRoot;
TreeNode nextLast = null;
queue.offer(currentRoot);
List<List<Integer>> lists = new ArrayList<>();
List<Integer> nodes = new ArrayList<>();
while (!queue.isEmpty()) {
currentRoot = queue.poll();
nodes.add(currentRoot.value);
if (currentRoot.left != null) {
queue.offer(currentRoot.left);
nextLast = currentRoot.left;
}
if (currentRoot.right != null) {
queue.offer(currentRoot.right);
nextLast = currentRoot.right;
}
if (last == currentRoot) {//因为last是当前层的最后一个节点,如果等式成立,说明这层已经遍历完
last = nextLast;
lists.add(nodes);
nodes = new ArrayList<>();
}
}
System.out.println(lists);
return lists;
}
复制代码
删除操作:
删除操作比较麻烦,需要分4种情况考虑。我们以上面这颗树作为参考:
- 如果删除节点含有左右子节点:需要用删除节点的后继节点(以中序遍历)取代删除节点的位置。----比如节点删除20,需要用26代替20的位置
- 如果删除节点只含有单个节点(或左或右):需要用删除节点的左(右)节点取代删除节点的位置。----比如节点删除27,需要用26代替27的位置
- 如果删除节点无子节点(是叶子节点):直接删除。
上代码:
public void remove(int value) {
TreeNode currentRoot = rootNode;
while (true) {
if (currentRoot == null) {
return;//没找到,返回或者抛异常
}
if (value < currentRoot.value) {
currentRoot = currentRoot.left;
}
else if (value > currentRoot.value) {
currentRoot = currentRoot.right;
}
else {
TreeNode replacement;//代替者
if (currentRoot.left == null && currentRoot.right == null) {//无双子节点
resetParentNode(currentRoot, null);
}
else if (currentRoot.left != null && currentRoot.right != null) {//双子节点
replacement = getSuccessor(currentRoot);
replacement.left = currentRoot.left;
replacement.right = currentRoot.right;
resetParentNode(replacement, null); //重设后继节点的父节点
resetParentNode(currentRoot, replacement);//重设正在移除节点的父节点
}
else if (currentRoot.left != null) {
resetParentNode(currentRoot, currentRoot.left); //
} //
else { //单节点
resetParentNode(currentRoot, currentRoot.right); //
} //
return;
}
}
}
复制代码
如果文章中有分析错误的地方或者值得改进的地方,欢迎大家指出。
参考文章: