二叉树它上线查找功能了?
多日不见,朋友们还记得什么是二叉树吗?
什么是二叉查找树
在树的任意一个节点,左子树中每个节点的值都要小于这个节点,右子树中每个节点的值都要大于这个节点,这就是二叉查找树。
查找操作
从根节点开始,如果比根节点的值小,就查询它的左子树;
如果比根节点的值大,就查询它的右子树;
如果等于我们要查找的数据,就返回这个节点元素;
按照这种逻辑,依次往下递归查询,最后到叶子节点都没有查到类似的数据,则表示数据不存在。
查找一个节点可以使用递归查询,也可以使用循环查询,一般建议使用循环操作,假设树变成一个链表,递归过深的时候,很容易出现stackoverflow 的异常。
public class BinarySearchTree {
// 根节点
private Node root;
/**
* 循环实现方法
*
* @param value 要查找的值
* @return 查找结果
*/
public Node find(int value) {
Node node = root;
while (root != null) {
if (node.value == value) {
return node;
}
if (node.value > value) {
node = node.right;
} else {
node = node.left;
}
}
return null;
}
/**
* 递归实现方法
*
* @param node 起始节点,一般为根节点
* @param value 要查找的值
* @return 查找结果
*/
public Node find2(Node node, int value) {
if (node == null) {
return null;
}
if (node.value > value) {
return find2(node.right, value);
} else if (node.value < value) {
return find2(node.left, value);
} else {
return node;
}
}
public class Node {
private int value;
private Node left;
private Node right;
public Node(int value) {
this.value = value;
}
}
}
插入操作
插入和查询的操作很类似,一般把新插入的节点放到叶子节点。类似查询的操作方式,查找元素应该插入的位置。
需要判断根节点是否为空,为空,则添加为根节点
如果根节点不为空,则与根节点对比大小
如果比根节点大,则从右子树继续查找
如果比根节点小,则从左子树继续查找
插入的条件:判断当前节点是否还有左/右节点,如果没有,则插入当前节点的左/右节点位置。
public void insert(int value) {
Node node = root;
if (node == null) {
node = new Node(value);
return;
}
while (node != null) {
if (node.value > value) {
if (node.right == null) {
node.right = new Node(value);
return;
}
node = node.right;
} else {
if (node.left == null) {
node.left = new Node(value);
return;
}
node = node.left;
}
}
删除操作
标记删除法:使用类似散列表中添加标记的删除方法,当一个数据删除以后,我们把它标记为已删除,数据并不会真的从内存中清理掉,这时候比较浪费内存。
还可以在查找的基础上进行改造,先查询节点所在位置,然后对其进行删除。
待删除节点为叶子节点,直接设置节点为null即可。
待删除节点只有一个子节点,让待删除节点的父节点指向待删除节点的子节点即可。
待删除节点有两个节点,删除节点以后,我们需要一个子节点填在删除的位置上,可以在它的左子树查找一个最大值,或者在右子树查找一个最小值。这个值一般在叶子节点,我们移动到删除节点位置即可。
编码思路
查询待删除的节点以及其父节点
判断节点是否为空,为空表示不存在
假设有两个节点,找出右子树中最小节点,替换待删除节点,然后把要删除的情况转化成叶子节点
假设删除的节点是叶子节点或者仅有一个子节点,或者待删除节点的子节点
使待删除节点的父节点指向待删除节点的子节点。
public void delete(int value) {
Node delNode = tree;//要删除的节点
Node pDelNode = null;// 要删除节点的父节点
// 1.查找要删除节点,以及其父节点
while (delNode != null && delNode.value != value) {
pDelNode = delNode;
if (delNode.value > value) {
delNode = delNode.left;
} else {
delNode = delNode.right;
}
}
// 要删除的节点不存在时
if (delNode == null) {
return;
}
// 2.假设待删除节点有两个子节点,查询出右子树最小节点
if (delNode.left != null && delNode.right != null) {
Node miniRight = delNode.right;
Node pMiniRight = miniRight;
while (miniRight.left != null) {
pMiniRight = miniRight;
miniRight = miniRight.left;
}
// 两个节点的情况转化成 待删除节点为叶子节点,父节点为叶子节点的父节点
delNode.value = miniRight.value;
delNode = miniRight;
pDelNode = pMiniRight;
}
// 3. 假设待删除节点只有一个子节点,获取删除节点的子节点
Node child;
if (delNode.left != null) {
child = delNode.left;
} else if (delNode.right != null) {
child = delNode.right;
} else {
// 如果两个子节点都是空,表示要删除的节点是叶子节点
child = null;
}
// 设置父节点的后继节点,删除节点
if (pDelNode == null) {
// 删除根节点,只有一个根节点的情况
tree = null;
} else if (pDelNode.right == delNode) {
pDelNode.right = child;
} else {
pDelNode.left = child;
}
}
其他
中序遍历二叉树,可以输出有序的数据序列,时间复杂度为O(n)
包含重复数据的二叉树
一、使用链表或者数组存储相同的数据,然后存放到节点中
二、把和某节点相同的值当做大于这个节点的值来处理。数据插入到这个节点的右子树。查找数据的时候,遇到值相同的节点,接着去右子树中查找数据,停止查找的条件变为遇到叶子节点。删除的时候也需要遍历右子树节点处理。
时间复杂度
二叉查找树退化成链表时,查找的时间复杂度为O(n)
如果二叉树是一个完全二叉树时,时间复杂度跟输的高度成正比,O(height)
,使用层数做计算,假设层数为 k 。第一层包含1个节点,第二层包含2个节点,第三层包含4个节点,下面一层节点个数是上一层的2倍,第 k 层包含的节点个数是 2^(k-1)
假设节点个数为n,最大层数为k
n >= 1+2+4+8+……+2^(k-2)
n <= 1+2+4+8+……+2^(k-2)+2^(k-1)
所以 k 的范围是 [log2(n+1), log2(n)+1]
,完全二叉树的层数小于等于log2(n)+1
,高度小于等于log2(n)
,最好的时间复杂度为O(logn)
。平衡二叉树的时间复杂度可以做到稳定的O(logn)