1、二叉查找树概述
二叉查找数(也称二叉排序树),首先是一颗二叉树,具有所有二叉树的特性。
左子树以及所有左子树的子树均小于根节点,右子树以及所有右子树的子树均大于根节点。
非叶子节点的节点也是一颗二叉排序树。
2.二叉查找树的作用
二叉查找树,即具有链表快速增删的特性也具有数组快速查找的特性,例如文件系统
3.构造一颗二叉查找树
3.1首先声明一个类BinarySearchTree<T>
3.2在上述类中定义一个内部静态类用于定义节点类Node<T>,并声明一个根节点
与上一节二叉树相比较,多了一个索引字段,代码如下:
// 二叉排序树的根节点
Node<T> root = null;
@AllArgsConstructor
private static class Node<T> {
private T t;
private int order;
private Node<T> left;
private Node<T> right;
@Override
public String toString() {
return t.toString();
}
}
3.3实现一个构建二叉树的函数
多年以前,学习java的时候定义一个函数的步骤是什么来着?
对,现在还是这样的,先明确三要素:函数名(做什么) 参数列表(需要什么) 返回值(产出什么)
于是我们构建二叉查找树的方法就写出来了,如下:
public Node<T> buildBinarySearchTree(List<Node<T>> list) {
return null;
}
接下来,实现该函数,因为二叉查找树有添加操作,所以,我们可以循环把多个节点添加进去,先假定已经有了一个添加方法,那么我们的buildBinarySearchTree函数如下:
// 构建二叉排序树
public Node<T> buildBinarySearchTree(List<Node<T>> list) {
for (Node<T> node : list) {
this.addRecursion(node);
}
return root;
}
3.4添加算法稍微复杂,先思考这个节点列表list怎么构建一个(因为当年高考理综一上去就挑分数最高的和第二高的两道物理题盯了一个多小时,也没解出来,于是乎理综的生物和化学部分几乎交了白卷,从那以后尤其到工作中,我从来都是先干容易的,越难的越往后)
为了使得构建的节点随机分布,考虑使用一个随机数,构建节点列表代码如下:
// 构建一个node集合
public static List<Node<String>> buildNodes(int coefficient) {
List<Integer> numbers = new ArrayList<>();
Random random = new Random();
for(int i = coefficient; i <= 100 + coefficient; i += coefficient) {
int o = random.nextInt(100) + i;
if(!numbers.contains(o)) {
numbers.add(o);
}
}
List<Node<String>> list = new ArrayList<>();
for (Integer number : numbers) {
list.add(new Node<String>("str" + number, number, null, null));
}
return list;
}
3.5 实现二叉搜索树的添加
根据二叉查找树的左孩子永远小于自己,右孩子永远大于自己的特性,我们大致理出以下逻辑
1.如果根节点为null,那么赋值到根节点
2.如果添加节点大于当前节点,则改变当前节点为其右孩子,递归往复
直到当前节点的右孩子为null,则把添加节点赋值给当前节点的右孩子
3.如果添加节点小于当前节点,则改变当前节点为其左孩子,递归往复
直到当前节点的左孩子为null,则把添加节点赋值给当前节点的左孩子
4.由于二叉查找树不可重复的特性,所以当相等的时候什么也不做。
于是有如下代码:
// 当前节点
Node<T> flag = null;
public void addRecursion(Node<T> node) {
if (root == null) {
root = node;
return;
}
if (node.order > flag.order) {
if(flag.right == null) {
flag.right = node;
return;
}else {
flag = flag.right;
}
} else if (node.order < flag.order) {
if(flag.left == null) {
flag.left = node;
return;
}else {
flag = flag.left;
}
}
addRecursion(node);
}
看似完美了,但是经过测试发现:
这里每次调用添加方法时,flag总不能归到根节点,导致丢失一些数据 因此修复该bug后,代码如下:
// 递归方式添加节点
Node<T> flag = null;
public void addRecursion(Node<T> node) {
if (root == null) {
root = node;
flag = node;
return;
}
if (node.order > flag.order) {
if (flag.right == null) {
flag.right = node;
flag = root;
return;
} else {
flag = flag.right;
}
} else if (node.order < flag.order) {
if (flag.left == null) {
flag.left = node;
flag = root;
return;
} else {
flag = flag.left;
}
}
addRecursion(node);
}
3.6循环方式添加node
以上完成了二叉查找树的递归方式添加node,那么我们思考,如何把该递归改造成循环方式呢?
其实通过3.5节的思路整理,这个递归改造循环并不复杂,就当我们不知道递归终止的地方在哪,也可以快速拉出如下代码:
public void addLoop(Node<T> node) {
Node<T> flag = null;
if (root == null) {
root = node;
return;
}
while (true) {
if (node.order > flag.order) {
}
if (node.order < flag.order) {
}
}
}
上面的代码,把架子搭起来了,差的就是找到什么时候退出循环,很显然,还是3.5节的思路:
如果添加节点大于当前节点
那么当当前节点的右孩子为null时
将添加节点赋值给当前节点的右孩子,并且退出循环
否则改变当前节点为其右孩子
如果添加节点小于当前节点
那么当当前节点的左孩子为null时
将添加节点赋值给当前节点的左孩子,并且退出循环
否则改变当前节点为其左孩子
稍加修改和优化得到最终代码如下:
// 非递归方式添加节点
public void addLoop(Node<T> node) {
Node<T> flag = null;
if (root == null) {
root = node;
return;
} else {
flag = root;
}
while (true) {
if (node.order > flag.order) {
if (flag.right != null) {
flag = flag.right;
} else {
flag.right = node;
break;
}
}
if (node.order < flag.order) {
if (flag.left != null) {
flag = flag.left;
} else {
flag.left = node;
break;
}
}
}
}
3.7经过以上的梳理,我们实现了如何构建一个二叉查找树以及其添加方法
4.二叉查找树的获取(查)
二叉查找树查询操作包含:迭代,根据索引获取、根据索引获取父节点、根据索引获取前驱节点、根据索引获取后驱节点
以上几种操作都有一个共同的逻辑就是根据索引查询,根据索引查询的过程都包含迭代的步骤,因此我们首先抽象一个通用私有方法实现迭代逻辑,再根据以上操作定义不同的函数来调用该私有通用方法。
4.1定义通用的迭代逻辑
private void get(Node<T> node, int order) {
if (node != null) {
//此处可以做点什么
get(node.left, order);
get(node.right, order);
}
}
观察上述函数,就是一个普通的前序二叉树遍历的代码,上述的做点什么,就可以用来完成根据索引获取等等不同的操作。
既然是不同的操作,那么我们可以联想到的实现方式有以下几种,代理模式、面向过程、函数式编程。
经过一些试探性的编码,决定此处使用 函数式编程 来实现。
有如下代码:
private void get(Executor<T> executor, Node<T> node, int order) {
if (node != null) {
executor.execute(node, order);
get(executor, node.left, order);
get(executor, node.right, order);
}
}
此处的Executor是自定义的一个函数式接口,用于调用方来自定义处理node和order的逻辑:
// 自定义的执行器,用于执行需要处理node和order的逻辑
@FunctionalInterface
interface Executor<T> {
/**
* 用于执行需要处理node和order的逻辑
*
* @author quxl
* @param node 节点
* @param order 索引
* @throws StopRecursionException
*/
void execute(Node<T> node, int order) throws StopRecursionException;
}
4.2根据索引查询(试着使用上述中封装的私有通用迭代处理器来实现查询方法)
public Node<T> get(int order) {
get((node, b) -> {
if (order == node.order) {
return node;
}
}, root, order);
return null;
}
查询方法很简单,也没什么好说的,但是经过测试发现,我们忽略了一个严重的问题,那就是我们此处的私有通用迭代器使用的是递归的方式,return只能退出到当前递归到的那一层栈,也就是说,最后拿不到符合order == node.order这个条件的node。
那怎么办呢?思考中......
在java中唯一能退出递归的方式就是异常,(其实异常也是一层一层的向上抛出来的)。
所以我们可以把符合条件的node裹挟在异常中带出来,于是代码很快就有了:
// 查找某个节点
public Node<T> get(int order) {
try {
get((node, b) -> {
if (order == node.order) {
throw new StopRecursionException(node);
}
}, root, order);
} catch (StopRecursionException e) {
@SuppressWarnings("unchecked")
Node<T> node = (Node<T>) e.getNode();
return node;
}
return null;
}
// 停止递归的异常类
@Data
@EqualsAndHashCode(callSuper = false)
private static class StopRecursionException extends Exception {
private Node<?> node;
public StopRecursionException(Node<?> node) {
super();
this.node = node;
}
private static final long serialVersionUID = -2196803774824414166L;
}
StopRecursionException是我们自定义的一个检查型异常类用于捕捉node
4.3根据索引获取父节点(经过步骤二我们验证了自定义的私有迭代器是可用的)
获取父节点的思路是:
如果当前节点的左孩子的索引匹配则返回当前节点
如果当前节点的右孩子的索引匹配则返回当前节点
否则递归至结束
代码如下:
// 查找某个节点的parent节点
public Node<T> getParent(int order) {
try {
get((node, b) -> {
if (node.left != null && node.left.order == b) {
throw new StopRecursionException(node);
}
if (node.right != null && node.right.order == b) {
throw new StopRecursionException(node);
}
}, root, order);
} catch (StopRecursionException e) {
@SuppressWarnings("unchecked")
Node<T> node = (Node<T>) e.getNode();
return node;
}
return null;
}
4.4根据索引获取前驱、后驱节点
首先我们搞清楚什么是前驱、后驱?
前驱就是比当前节点小的所有节点的最大的一个
后驱就是比当前节点大的所有节点的最大的一个
算法有2种方式:
方式1:
一个节点的前驱节点是其左子树中的最大值,若无左子树,其前驱节点在从根节点到key的路径上,比key小的最大值。
一个节点的后继节点是右子树的最小值,若无右子树,其后继节点在从根节点到key的路径上,比key大的最小值。
方式2:
遍历二叉查找树拿到所有节点组成一个有序列表,那么order-1就是前驱,order+1就是后驱
这里先用方式2来实现以下。
1.首先得遍历二叉查找树拿到所有节点组成的一个有序列表,代码如下:
再次定义一个迭代器方法,为了使该方法通用性强,我们把对node的操作抽象成一个函数(也是函数式编程的思路)
// 迭代并对元素做一次操作
private void iteratorNode(Node<T> node, Consumer<Node<T>> consumer) {
if (node == null) {
return;
}
iteratorNode(node.left, consumer);
consumer.accept(node);
iteratorNode(node.right, consumer);
}
Consumer<T>是java8提供的一个函数式接口,用于对T的一次性消费。
然后再定义获取二叉查找树所有节点的方法
// 获取二叉排序树中的所有元素
public List<Node<T>> getNodes() {
final List<Node<T>> list = new ArrayList<>();
iteratorNode(root, node -> list.add(node));
return list;
}
2.完成前驱order-1、后驱order+1的两个方法
// 获取前驱节点
public Node<T> getBefore(int order) {
return getNodes().get(order - 1);
}
// 获取后驱节点
public Node<T> getBefore(int order) {
return getNodes().get(order + 1);
}
5.二叉查找树的添加(增)
参考3.5、3.6
6.二叉查找树的删除(删)
删除是最复杂的,所以放到最后来做
6.1删除需要大致考虑以下三种情况:
1.叶子节点,需要获取被删元素的父节点,并移除父节点对应指针即可
图6.1.1
如上图中1、3、5、7节点
2.左右子树都不为null。
首先用前驱节点替换自己,即被删节点的父节点对应指针指向被删节点的前驱节点
然后判断前驱节点是否为自己左孩子,
如果是,把自己的右孩子赋值给前驱节点的右孩子(参考图6.2)
如果否,把自己的左右孩子都赋值给前驱节点的左右孩子(参考图6.2)
图6.2.2
3.只有一个子树不为null
图6.3.3
如果左子树不为null,则父节点指向左子树,如上图删除3
如果右子树不为null,则父节点指向右子树,如上图删除7
6.2实现上述三种情况
基于6.1整理的思路,我们分别实现以上三种情况
1.被删除节点为叶子节点
if (node.left == null && node.right == null) { // 叶子节点
if (parent.left.order == node.order) { //如果被删节点是左孩子
parent.left = null;
}
if (parent.right.order == node.order) { //如果被删节点是右孩子
parent.right = null;
}
}
2.被删节点左右子树都不为null
首先获取前驱节点,并替换自己
如果前驱节点==自己的left节点, 把自己的右子树赋值给前驱节点的右子树
如果前驱节点!=自己的left节点, 把自己的左、右子树赋值给前驱节点的左、右子树
if (node.left != null && node.right != null) { // 左右子树都不为null
// 用前驱节点替换自己
Node<T> before = getBefore(order);
if (parent.left == node) {
parent.left = before;
} else {
parent.right = before;
}
// 如果前驱节点==自己的left节点, 把自己的右子树赋值给前驱节点的右子树
if (before == node.left) {
before.right = node.right;
}
// 如果前驱节点!=自己的left节点, 把自己的左、右子树赋值给前驱节点的左、右子树
if (before != node.left) {
before.left = node.left;
before.right = node.right;
}
}
3.只有一个子树不为null
// 如果左子树不为null,父节点指向左子树
if (node.left != null) {
// 如果当前节点是左子树
if (parent.left.order == node.order) {
parent.left = node.left;
}
// 如果当前节点是右子树
if (parent.right.order == node.order) {
parent.right = node.left;
}
} else if (node.right != null) { // 如果右子树不为null,父节点指向右子树
// 如果当前节点是左子树
if (parent.left.order == node.order) {
parent.left = node.right;
}
// 如果当前节点是右子树
if (parent.right.order == node.order) {
parent.right = node.right;
}
}
4.代码整理如下:
public void remove(int order) {
Node<T> node = get(order);
if (node != null) {
// 获取父节点,并移除父节点中的指针
Node<T> parent = getParent(order);
if (node.left == null && node.right == null) { // 叶子节点
if (parent.left.order == node.order) {
parent.left = null;
}
if (parent.right.order == node.order) {
parent.right = null;
}
} else if (node.left != null && node.right != null) { // 左右子树都不为null
Node<T> before = getBefore(order);
// 用前驱节点替换自己
if (parent.left == node) {
parent.left = before;
} else {
parent.right = before;
}
// 如果前驱节点==自己的left节点, 把自己的右子树赋值给前驱节点的右子树
if (before == node.left) {
before.right = node.right;
}
// 如果前驱节点!=自己的left节点, 把自己的左、右子树赋值给前驱节点的左、右子树
if (before != node.left) {
before.left = node.left;
before.right = node.right;
}
// 并删除前驱节点
node = null;
} else { // 有一个子树为null
// 如果左子树不为null
if (node.left != null) {
// 如果当前节点是左子树
if (parent.left.order == node.order) {
parent.left = node.left;
}
// 如果当前节点是右子树
if (parent.right.order == node.order) {
parent.right = node.left;
}
} else if (node.right != null) { // 如果右子树不为null
// 如果当前节点是左子树
if (parent.left.order == node.order) {
parent.left = node.right;
}
// 如果当前节点是右子树
if (parent.right.order == node.order) {
parent.right = node.right;
}
}
}
}
}
经过测试发现2个问题:
问题1,当某个节点为null时,获取其order时报错空指针
所以所有的xx.order==yy.order,直接改为xx==yy
问题2,当被删除节点的父节点为null时,即根节点,报错空指针。
所以删除的三种情况都需要考虑删除根节点的情况
1.叶子节点,即只有一个根节点的二叉树,则直接删除根节点
2.有一个子树为null时,即只有左子树或右子树的二叉树,则直接替换
3.左右子树都不为null时,则用前驱节点替换根节点,并把前驱节点的原父节点的指针删除
5.二叉查找树的最终版本代码如下:
如有测出bug,可以留言,一起讨论,这个方法太繁琐,后续抽空再进行一次优化
public void remove(int order) {
try {
Node<T> node = get(order);
if (node != null) {
// 获取父节点,并移除父节点中的指针
Node<T> parent = getParent(order);
if (node.left == null && node.right == null) { // 叶子节点
if (parent != null) {
if (parent.left == node) {
parent.left = null;
}
if (parent.right == node) {
parent.right = null;
}
} else { // 如果是根节点
root = null;
}
} else if (node.left != null && node.right != null) { // 左右子树都不为null
Node<T> before = getBefore(order);
// 用前驱节点替换自己
if (parent != null) {
if (parent.left == node) {
parent.left = before;
} else {
parent.right = before;
}
} else { // 如果是根节点
root = before;
}
// 如果前驱节点==自己的left节点, 把自己的右子树赋值给前驱节点的右子树
if (before == node.left) {
before.right = node.right;
}else { // 如果前驱节点!=自己的left节点, 把自己的左、右子树赋值给前驱节点的左、右子树
before.left = node.left;
before.right = node.right;
}
//最后取消前驱节点的父节点的指针
Node<T> beforeParent = getParent(before.order);
if(beforeParent != null) {
if(beforeParent.left == before) {
beforeParent.left = null;
}else if(beforeParent.right == before) {
beforeParent.right = null;
}
}
} else { // 有一个子树为null
// 如果左子树不为null,父节点指向左子树
if (node.left != null) {
if (parent != null) {
// 如果当前节点是左子树
if (parent.left == node) {
parent.left = node.left;
}
// 如果当前节点是右子树
if (parent.right == node) {
parent.right = node.left;
}
} else { // 如果是根节点
root = node.left;
}
} else if (node.right != null) { // 如果右子树不为null,父节点指向右子树
if (parent != null) {
// 如果当前节点是左子树
if (parent.left == node) {
parent.left = node.right;
}
// 如果当前节点是右子树
if (parent.right == node) {
parent.right = node.right;
}
} else { // 如果是根节点
root = node.right;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
7.完整示例代码参考如下:
package cn.qu.data.structure;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
public class BinarySearchTree<T> {
public static void main(String[] args) {
for(int i = 0; i < 1000; i ++) {
BinarySearchTree<String> tree = new BinarySearchTree<>();
tree.buildBinarySearchTree(buildNodes(5));
List<Node<String>> nodes = tree.getNodes();
System.out.println("删除前:" + nodes);
tree.remove(tree.root.order);
System.out.println("删除根节点前:" + tree.getNodes());
}
}
// 构建一个node集合
public static List<Node<String>> buildNodes(int coefficient) {
List<Integer> numbers = new ArrayList<>();
Random random = new Random();
for (int i = coefficient; i <= 100 + coefficient; i += coefficient) {
int o = random.nextInt(100) + i;
if (!numbers.contains(o)) {
numbers.add(o);
}
}
List<Node<String>> list = new ArrayList<>();
for (Integer number : numbers) {
list.add(new Node<String>("str" + number, number, null, null));
}
return list;
}
// 二叉排序树的根节点
Node<T> root = null;
// 根据索引删除某个节点,待验证
public void remove(int order) {
try {
Node<T> node = get(order);
if (node != null) {
// 获取父节点,并移除父节点中的指针
Node<T> parent = getParent(order);
if (node.left == null && node.right == null) { // 叶子节点
if (parent != null) {
if (parent.left == node) {
parent.left = null;
}
if (parent.right == node) {
parent.right = null;
}
} else { // 如果是根节点
root = null;
}
} else if (node.left != null && node.right != null) { // 左右子树都不为null
Node<T> before = getBefore(order);
// 用前驱节点替换自己
if (parent != null) {
if (parent.left == node) {
parent.left = before;
} else {
parent.right = before;
}
} else { // 如果是根节点
root = before;
}
// 如果前驱节点==自己的left节点, 把自己的右子树赋值给前驱节点的右子树
if (before == node.left) {
before.right = node.right;
}else { // 如果前驱节点!=自己的left节点, 把自己的左、右子树赋值给前驱节点的左、右子树
before.left = node.left;
before.right = node.right;
}
//最后取消前驱节点的父节点的指针
Node<T> beforeParent = getParent(before.order);
if(beforeParent != null) {
if(beforeParent.left == before) {
beforeParent.left = null;
}else if(beforeParent.right == before) {
beforeParent.right = null;
}
}
} else { // 有一个子树为null
// 如果左子树不为null,父节点指向左子树
if (node.left != null) {
if (parent != null) {
// 如果当前节点是左子树
if (parent.left == node) {
parent.left = node.left;
}
// 如果当前节点是右子树
if (parent.right == node) {
parent.right = node.left;
}
} else { // 如果是根节点
root = node.left;
}
} else if (node.right != null) { // 如果右子树不为null,父节点指向右子树
if (parent != null) {
// 如果当前节点是左子树
if (parent.left == node) {
parent.left = node.right;
}
// 如果当前节点是右子树
if (parent.right == node) {
parent.right = node.right;
}
} else { // 如果是根节点
root = node.right;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取前驱节点
public Node<T> getBefore(int order) {
return get(order, i -> i - 1);
}
// 获取后驱节点
public Node<T> getAfter(int order) {
return get(order, i -> i + 1);
}
// 查找某个节点的parent节点
public Node<T> getParent(int order) {
try {
get((node, b) -> {
if (node.left != null && node.left.order == b) {
throw new StopRecursionException(node);
}
if (node.right != null && node.right.order == b) {
throw new StopRecursionException(node);
}
}, root, order);
} catch (StopRecursionException e) {
@SuppressWarnings("unchecked")
Node<T> node = (Node<T>) e.getNode();
return node;
}
return null;
}
// 查找某个节点
public Node<T> get(int order) {
try {
get((node, b) -> {
if (order == node.order) {
throw new StopRecursionException(node);
}
}, root, order);
} catch (StopRecursionException e) {
@SuppressWarnings("unchecked")
Node<T> node = (Node<T>) e.getNode();
return node;
}
return null;
}
// 构建二叉排序树
public Node<T> buildBinarySearchTree(List<Node<T>> list) {
for (Node<T> node : list) {
this.addRecursion(node);
}
return root;
}
// 非递归方式添加节点
public void addLoop(Node<T> node) {
Node<T> flag = null;
if (root == null) {
root = node;
return;
} else {
flag = root;
}
while (true) {
if (node.order > flag.order) {
if (flag.right != null) {
flag = flag.right;
} else {
flag.right = node;
break;
}
}
if (node.order < flag.order) {
if (flag.left != null) {
flag = flag.left;
} else {
flag.left = node;
break;
}
}
}
}
// 递归方式添加节点
Node<T> flag = null;
public void addRecursion(Node<T> node) {
if (root == null) {
root = node;
flag = node;
return;
}
if (node.order > flag.order) {
if (flag.right == null) {
flag.right = node;
flag = root;
return;
} else {
flag = flag.right;
}
} else if (node.order < flag.order) {
if (flag.left == null) {
flag.left = node;
flag = root;
return;
} else {
flag = flag.left;
}
}
addRecursion(node);
}
// 获取二叉排序树中的所有元素
public List<Node<T>> getNodes() {
final List<Node<T>> list = new ArrayList<>();
iteratorNode(root, node -> list.add(node));
return list;
}
private Node<T> get(int order, UnaryOperator<Integer> func) {
List<Node<T>> nodes = getNodes();
for (int i = 0; i < nodes.size(); i++) {
if (nodes.get(i).order == order) {
return nodes.get(func.apply(i));
}
}
return null;
}
// 迭代并对元素做一次操作
private void iteratorNode(Node<T> node, Consumer<Node<T>> consumer) {
if (node == null) {
return;
}
iteratorNode(node.left, consumer);
consumer.accept(node);
iteratorNode(node.right, consumer);
}
// 递归遍历二叉排序树,通过异常捕捉到索引为order的节点
private void get(Executor<T> executor, Node<T> node, int order) throws StopRecursionException {
if (node != null) {
executor.execute(node, order);
get(executor, node.left, order);
get(executor, node.right, order);
}
}
// 自定义的执行器,用于执行需要处理node和order的逻辑
@FunctionalInterface
interface Executor<T> {
/**
* 用于执行需要处理node和order的逻辑
*
* @author quxl
* @param node 节点
* @param order 索引
* @throws StopRecursionException
*/
void execute(Node<T> node, int order) throws StopRecursionException;
}
// 节点类
@AllArgsConstructor
private static class Node<T> {
private T t;
private int order;
private Node<T> left;
private Node<T> right;
@Override
public String toString() {
return t.toString();
}
}
// 停止递归的异常类
@Data
@EqualsAndHashCode(callSuper = false)
private static class StopRecursionException extends Exception {
private Node<?> node;
public StopRecursionException(Node<?> node) {
super();
this.node = node;
}
private static final long serialVersionUID = -2196803774824414166L;
}
}