如何用 java 实现【二叉搜索树】

news2024/12/24 0:34:29

文章目录

  • 搜索树概念
    • 1. 查找操作
    • 2. 插入操作
    • 3. 删除操作
    • 4. 以上三种操作的测试
    • 5. 性能分析

搜索树概念


二叉搜索树 又称 二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的 子树 不为空,则 子树上所有节点的值都 小于 根节点的值
  • 若它的 子树 不为空,则 子树上所有节点的值都 大于 根节点的值
  • 它的左右子树也分别为二叉搜索树

如果现在有一个数组:

int[] array = {5, 3, 4, 1, 7, 8, 2, 6, 0, 9};


将这个数组变为 二叉搜索树 如下。



可以看到 左子树 上的所有节点都比根节点的值
右子树 上的所有节点都比根节点的值

1. 查找操作


查找思路

将下面一组数据变成二叉搜索树,再查找其中某个节点是否存在。

int[] array = {8, 5, 7, 2, 12, 25, 11};


变为二叉搜索树为以下图片


如果要查找 11 这个值,则根据二叉搜索树的特点,这个值一定是在根节点的右边。

1、定义一个 cur 指向根节点。



2、要找的 11 是在根节点的右边,所以 cur 会往右边走。



3、这是要找的值比此时的 cur 的值小,cur 往左边走。



此时的 cur 的值等于要查找的值,即找到了,返回 cur 即可。

我们会发现,每一次的查找都会直接去掉一半的节点,二叉搜索树 类似于二分查找的思路。(一次去掉一半的数据)
如果这不是一个 二叉搜索树 ,则需要遍历每一个结点才有可能找到目标值。
这时候效率明显就慢了不少。

查找的不同结果

在这一棵 二叉搜索树 中,有可能遍历结束后也不会出现要查找的值,即是搜索树没有要查找的值。



如果要在上面的 搜索树 中查找 18 这个值,一开始会因为 18 大于 根节点,
所以 cur 会往右边走。

此时的 cur 的值 还是小于 18 ,cur 还是会往右边走,cur 此时来到 25 的位置。

由于此时 cur 的值 大于 18,cur 会往左走,但此时 cur 的值为空并且没有找等于18的结点,
此时就是找不到的情况了,返回 null 即可。

关于代码的分析

因此就可以采取下面的代码实现多次的查找

while (cur != null) {}


可以通过比较 cur 与 val (查找的目标值)的值,来决定 cur 往哪边移动。

if (cur.val > val) {
    cur = cur.right; // cur 往右边走
} else if (cur.val < val) { // cur 往左边走
    cur = cur.left;
} else {
    // 此时即使找到目标中
    return cur;
}


如果循环结束了就说明搜索树当中不存在目标值,返回 null 即可。

return null;


完整代码的展示

// 查找操作
public ThreeNode search(int val) {
    // 定义 cur 指向根节点
    ThreeNode cur = root;
    while (cur != null) {
        // 此时 cur 往右边走
        if (cur.val > val) {
            cur = cur.right;
        } else if (cur.val < val) { // cur 往左边走
            cur = cur.left;
        } else {
            // 此时即使找到目标中
            return cur;
        }
    }
    // 若循环结束号未找到即为找不到了 - 返回 null
    return null;
}


最好与最坏的情况

最好的情况就是 完全二叉树,一次查找会省去一般的效率。

上面所讲的就是一个 完全二叉树 的情况。


最坏的情况 是一个 左单支 或者 右单支





这个时候不论是查找哪个值,都不能去掉一半的节点。
此时一定是每一个结点都要遍历才有可能找到 val 的值。

2. 插入操作


空与非空的插入情况

如果搜索树是空的,则直接将结点插入即可,新插入的结点就是 根节点 root。

如果不为空则按照 左边比根节点小右边比根节点大 的规则找到合适的位置,
然后将节点插入进去。

插入思路

下面还是以 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 这组序列来演示。

如果树是空的,新插入的结点就是根节点。



如果树不是空的则会按照下面的思路插入。

1、如何插入 3



把结点的值与根节点的值比较一下,如果大于就插到右边,小于就插到左边。

2、如何插入4



还是拿结点的值与根节点的值比较,发现要把结点插入到左边。
但是此时左边还有一个结点,要再次与这个结点的值(3)比较,
再根据规则插入到右边或者左边。

其他的节点都是按照这种思路来实现的,
只要找到需要插入到的位置即可。


有一点要注意的是,当前插入的数据 一直是在叶子结点上的

比如,当前插入的数据 3 的操作 ,这个 3 就是在当前的最后一个结点上的值。
插入数据 4 的操作,就是把 5 放在了当前的最后一个结点上。

结论:

只有第一次插入的结点是根节点,其他的每次插入都是在叶子结点的位置。

插入位置如何确定



假如要在上面搜索树中插入数据 0。

1、先定义一个 cur 指向根节点



2、将插入数据的值与 cur 的值比较,小的往左大的往右,cur 最终会到下面位置



当前的 cur 的值为空,就说明找到了插入的位置。
但也正是由于 cur 的值为空,无法通过 cur 来插入数据 0。

解决办法就是 重新定义一个变量记录 cur 的位置,让这个变量替 cur 移动

1、先定义一个指向空的 P 变量

2、在 cur 移动之前让 P 指向 cur 的位置



3、cur 移动了一次位置后,P 就指向 cur 移动前位置







当 cur 的值为空的时候,此时的 P 就是要插入的位置。

关于代码的分析

1、可以通过下面的代码判断树是不是空的。

if (root == null) {
    // 此时插入到根节点的位置
    root = new ThreeNode(key);
    return true;
}


2、创建 cur 和 parent

ThreeNode cur = root;
ThreeNode parent = null; 


3、cur 不为空说明还没有找到插入的位置

 while (cur != null) {}


4、判断 cur 往哪边移动

 if (cur.val > key) {}
 else if (cur.val < key) {}


5、新建一个结点以便可以插入一个数据

ThreeNode node = new ThreeNode(key);


6、判断结点数据是插在左边还是右边

if (key < parent.val) {} 
else {}


完整代码展示

 // 插入一个元素 key
 public boolean insert(int key) {
     // 为空的情况
     if (root == null) {
         // 此时插入到根节点的位置
         root = new ThreeNode(key);
         return true;
     }

     // 不为空的情况
     ThreeNode cur = root;
     ThreeNode parent = null; // 用来记录 cur 的位置

     // cur 不为空说明还未找到插入的位置
     while (cur != null) {
         // cur 的值大于 key 的值
         if (cur.val > key) {
             // parent 先指向 cur 的位置
             parent = cur;
             // cur 再向左边移动
             cur = cur.left;
         } else if (cur.val < key) {
             parent = cur;
             // cur 向右移动
             cur = cur.right;
         } else {
             // 插入的数据是相同的
             return false;
         }
     }

     // 此时 cur 的值为空,说明找到了插入的位置
     // 先创建一个结点
     ThreeNode node = new ThreeNode(key);

     //判断此时的 key 的值是在左边还是在右边
     if (key < parent.val) {
         // 插左边
         parent.left = node;
     } else {
         // 否则插右边
         parent.right = node;
     }
     // 插入完成后返回true
     return true;
 }

3. 删除操作




如果要删除 12 ,就要把 25 移动到删除前12的位置。

但此时有一个疑问,是应该把 8 或者 20 哪一个作为 25 的左边?

因此可以看出删除操作还是很有难度的。

搜索树删除操作分三种情况


1、cur.left == null 的情况


cur 是 root,则 root = cur.right


删除后



cur 指向的是要删除的结点,如果此时 cur 指向的是 root。
则只需要 root = cur.right,即可完成删除并且此时仍然是一个搜索树。

直接使用这个操作的前提是 cur 的左树为空。


cur 不是 root,cur 是 parent.left,则 parent.left = cur.right



此时 cur 指向的结点是要删除的结点并且要删除的结点不是根节点。
此时 p 左边的结点如果变成 p 右边的结点,即可完成删除,
而且此时仍然是搜索树。

删除后




cur 不是 root,cur 是 parent.right,则 parent.right = cur.right



此时 cur 指向的结点是要删除的结点并且要删除的结点不是根节点。
此时 p 右边的结点如果变成 cur 右边的结点,即可完成删除,
而且此时仍然是搜索树。

删除后




2、cur.right == null 的情况


cur 是 root,则 root = cur.left



cur 指向的是要删除的结点,如果此时 cur 指向的是 root。
则只需要 root = cur.left,即可完成删除并且此时仍然是一个搜索树。

删除后




cur 不是 root,cur 是 parent.right,则 parent.right = cur.left



此时 cur 指向的结点是要删除的结点并且要删除的结点不是根节点。
此时 p 右边的结点如果变成 cur 左边的结点,即可完成删除,
而且此时仍然是搜索树。

删除后




cur 不是 root,cur 是 parent.left,则 parent.left = cur.left



此时 cur 指向的结点是要删除的结点并且要删除的结点不是根节点。
此时 p 左边的结点如果变成 cur 左边的结点,即可完成删除,
而且此时仍然是搜索树。

删除后




3、cur 左右都不为空的情况



此时 cur 是要删除的结点,cur 的左右都不为空。

当删除了cur结点后,8 和 25 哪一个结点应该左为 p 的右边呢?
这是一个疑问。

可以采用 替罪羊 的方法来实现删除操作。



可以使用 9 来代替 12 ,这要搜索树的结构就不会乱了。



也可以使用 13 代替 12 ,这样也不会是搜索树的结构乱了。

那么如何才能找到 9 和 13 呢?

在当前要删除的 cur 结点的左边的最右边 找到 9 。
在当前要删除的 cur 结点的右边的最左边 找到 13 。

9 在 cur 左边的最右边,说明 9 是左边的最大值。
13 在 cur 右边的最左边,说明 13 是右边的最小值。

最后为问题就转变为如何删 9 或 13,而且 9 作为左边的最大值,它的右边一定为空
13 作为右边的最小值,它的左边一定为空



如果找到的是 13 ,就定义一个 t 来指向 13 ,定义一个 tp 指向它的父节点。
此时 t 的 左边一定为空,但是右边可能不是空的。

如果要删除 13 ,直接让 tp.left = t.right 即可。



如果找到但是 9 , t 来指向 13 , tp 指向它的父节点。
此时 t 的 右边一定为空,但是左边可能不是空的。

如果要删除 9 ,直接让 tp.right = t.left 即可。
\

关于删除代码的分析

1、要有 cur 结点 和 parent 结点

ThreeNode cur = root;
ThreeNode parent = null;  // 用来记录 cur 的位置


2、如果树是空的就肯定不存在要删除的结点。

while (cur != null) {}


3、首先要找到需要删除的结点

if (cur.val == key) {
  
} else if (cur.val < key) {
    // 往右边找
    parent = cur; 
    cur = cur.right;
} else {
    // 往左边找
    parent = cur;
    cur = cur.left;
}


4、删除操作的实现

这里可以写一个方法,需要删除的时候直接调用。

 // 这个方法中来判断当前的删除属于哪一种情况
 private void removeNode(ThreeNode parent, ThreeNode cur) {}


下面是针对第一种情况(cur.left == null)的删除操作

 if (cur.left == null) {
      // 如果要删除的结点是root结点
      if (cur == root) {
          root = cur.right;
      } else if (cur == parent.left) { // 要删除的cur结点在 p 的左边
          parent.left = cur.right;
      } else { // 要删除的cur结点在 p 的右边
          parent.right = cur.right;
      }
  }


这是针对于第二种情况(cur.right == null)的删除操作。

else if (cur.right == null) { // 情况2
  // 如果要删除的结点是 root结点
  if (cur == root) {
      root = cur.left;
  } else if (cur == parent.left) { // 要删除的 cur 结点在 p 的左边
      parent.left = cur.left;
  } else { // 要删除的cur结点在 p 的右边
      parent.right = cur.left;
  }


下面是针对第三种情况(cur 的左右都不为空)的删除操作。


假设是找 13 结点。

1、先要找到 13 这个结点,可以先定义 target 为目标

 ThreeNode target = cur.right;


此时 target 指向了 cur 结点的右边,13 就在 target 的最左边。



2、这个时候要定义 tp 指向 cur 的位置。

 ThreeNode targetParent = cur;




3、tp 要在 t 移动前先记录一下 t 的位置

 targetParent = target; // 提前记录 t 的位置
 target = target.left; // 找到 13 这个结点




4、此时 cur 结点的值就变成了 t 的值

 cur.val = target.val;


5、接下来就可以删除 13 了,也就是删除 t 。

 targetParent.left = target.right;

如果 t 的右边为空就直接为空。

6、接下来处理特殊情况



如果 cur 的右边就是 t (要找到结点)

根据上面的演示可以发现分为两种情况。

第一种情况 cur.left == null



第二种情况 cur.right == null



可以先一个 if 语句判断属于哪一种情况。

 // t 在 tp 的左边
 if (target == targetParent.left) {
     targetParent.left = target.right;
 } else {
     // t 在 tp 的右边
     targetParent.right = target.right;
 }


删除操作完整代码

// 删除一个结点操作
public void remove(int key) {
    ThreeNode cur = root;
    ThreeNode parent = null; // 用来记录 cur 的位置

    // 删除的第一步是先找到这个结点
    while (cur != null) {
        // 找到了
        if (cur.val == key) {
            removeNode(parent, cur);
            return;
        } else if (cur.val < key) {
            // 往右边找
            parent = cur;
            cur = cur.right;
        } else {
            // 往左边找
            parent = cur;
            cur = cur.left;
        }
    }
}

// 这是删除的具体实现
private void removeNode(ThreeNode parent, ThreeNode cur) {
    // 这个方法中来判断当前的删除属于哪一种情况
    // 情况1
    if (cur.left == null) {
        // 如果要删除的结点是root结点
        if (cur == root) {
            root = cur.right;
        } else if (cur == parent.left) { // 要删除的cur结点在 p 的左边
            parent.left = cur.right;
        } else { // 要删除的cur结点在 p 的右边
            parent.right = cur.right;
        }
    } else if (cur.right == null) { // 情况2
        // 如果要删除的结点是 root结点
        if (cur == root) {
            root = cur.left;
        } else if (cur == parent.left) { // 要删除的 cur 结点在 p 的左边
            parent.left = cur.left;
        } else { // 要删除的cur结点在 p 的右边
            parent.right = cur.left;
        }
    } else { // 情况3
        // 左右都不为空
        ThreeNode target = cur.right; // 要找到的目标
        ThreeNode targetParent = cur;
        while (target.left != null) { // 如果t的左边为空就说明这是属于 cur.left == null 的情况
            targetParent = target; // 提前记录 t 的位置
            target = target.left;
        }
        cur.val = target.val;
        // t 在 tp 的左边
        if (target == targetParent.left) {
            targetParent.left = target.right;
        } else {
            // t 在 tp 的右边
            targetParent.right = target.right;
        }
    }
}

4. 以上三种操作的测试


1、首先测试插入操作。

 int[] array = {5, 3, 4, 1, 7, 8, 2, 6, 0, 9};


把上面的数组插入到树中。

一个 for 循环实现。

for (int i = 0; i < array.length; i++) {
     // 插入数组元素到搜索树中
     binarySearchThree.insert(array[i]);
}


可以写一个中序遍历,根据打印出的中序遍历结果来判断插入是否正确。

中序遍历的写法:

public void inorder(ThreeNode root) {
    if (root == null) {
        return;
    }

    // 中序遍历 左根右
    inorder(root.left);
    System.out.print(root.val + " ");
    inorder(root.right);
}


最后使用下面代码调用中序遍历打印结果。

binarySearchThree.inorder(binarySearchThree.root);




可以看到结果是正确的。


2、查找操作测试

// 测试查找
System.out.println(binarySearchThree.search(5)); // 可以找到 5 会返回 5 的地址
System.out.println(binarySearchThree.search(11)); // 找不到 11 会返回 null




可以看到结果是正确的。


3、删除操作的测试

删除以后打印删除后的结果,一下以删除 8 结点为演示。

 // 测试删除操作
 binarySearchThree.remove(8);

 // 测试中序遍历
 binarySearchThree.inorder(binarySearchThree.root);




可以看到打印的结果没有 8 。

5. 性能分析


插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,
则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,
可能得到一下两种不同结构的二叉搜索树:





最优情况 下,二叉搜索树为完全二叉树,其平均比较次数为:


最差情况 下,二叉搜索树退化为单支树,其平均比较次数为:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126838.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自定义神经网络入门-----Pytorch

文章目录目标检测的相关评价指标IoUmAP正例和负例准确率P召回率R准确率ACCP-R曲线--APnn.Module类全连接层感知机类使用nn.Sequential进行构造使用randn函数进行简单测试损失函数nn.functionalnn.optim模型处理网络模型库torchvision.models模型Fine-tune和save参考目标检测的相…

【STM32F4系列】【HAL库】【自制库】模拟IIC从机

介绍 本项目是利用GPIO模拟I2C的从机 网上常见的是模拟I2C主机 本项目是作为一个两个单片机之间低速通信的用法 协议介绍请看,传送门 模拟主机请看这里 从机 功能 实现I2C从机端读写寄存器 编程思路 I2C的从机实现比起主机来麻烦一些 因为SCL的时序是由主机发送,从机需…

【nowcoder】笔试强训Day12

目录 一、选择题 二、编程题 2.1二进制插入 2.2 查找组成一个偶数最接近的两个素数 一、选择题 1.以下方法&#xff0c;哪个不是对add方法的重载? public class Test {public void add( int x,int y,int z){} } A. public int add(int x,int y,float z){return 0;} B.…

Go语言设计与实现 -- WaitGroup, Once, Cond

WaitGroup 我们可以通过 sync.WaitGroup 将原本顺序执行的代码在多个 Goroutine 中并发执行&#xff0c;加快程序处理的速度。 我们来看一下sync.WaitGroup的结构体&#xff1a; type WaitGroup struct {//保证WaitGroup不会被开发者通过再赋值的方式复制noCopy noCopy// 64-…

重学redux之Redux-Thunk高级使用(三)

这是第三篇了,哥们,如果没看过前两篇,可以去看看之前的两篇,有基础的可以直接看,不多说,直接开讲 默认情况下,Redux 的动作是同步调度的,对于任何需要与外部 API 通信或执行副作用的应用程序来说都是一个问题。 Redux 允许中间件位于被分派的动作和到达 reducer 的动…

抖音本地生活的蓬勃发展,离不开服务商的推波助澜

抖音本地生活&#xff0c;已经势不可挡01 抖音公布本地生活成绩单&#xff0c;交易额增长30倍抖音经过6年时间的演变&#xff0c;产品功能日益丰富&#xff0c;已经从内容消费&#xff0c;延续到线上购物、线下团购等领域&#xff0c;从最初的记录美好生活&#xff0c;成为一种…

统计分析工具-FineReport配置SQL Server外接数据库(2)

1. 配置外接数据库 1.1 外接数据库配置入口 外接数据库的配置入口&#xff0c;有三种形式&#xff1a; 1&#xff09;超级管理员第一次登录数据决策系统时&#xff0c;即可为系统配置外接数据库。如下图所示&#xff1a; 2&#xff09;对于使用内置数据库的系统&#xff0c;管…

站点能源低碳目标网,助力网络碳中和 | 华为发布站点能源十大趋势

2022年12月29日&#xff0c;华为今天举办站点能源十大趋势发布会并重磅发布白皮书。发布会上&#xff0c;华为站点能源领域总裁尧权全面解读了能源数字化、低碳网络、站点供电绿色化等站点能源十大趋势。 尧权表示&#xff0c;2022年是不平凡的一年&#xff0c;全球能源危机背…

十、通过网络服务将esp8266引脚状态显示在网页中

ESP8266在服务器模式运行时&#xff0c;我们可以使用浏览器来显示它的引脚状态。 1、实现目标 学习如何通过esp8266建立基本网站&#xff0c;在该网站上实时显示esp8266的引脚值。 2、原理图 FLASH按键与D3引脚连接&#xff0c;可以通过FLASH按键改变D3引脚的电平。当没有按…

中型企业适合用什么样的CRM管理软件,求推荐?

中型企业适合用什么样的CRM管理软件&#xff0c;求推荐&#xff1f; CRM管理软件是现代企业必不可少的管理软件之一&#xff0c;很多企业都会选择CRM管理软件来经营客户资源&#xff0c;但能够精准地选择到适合自己企业的CRM管理软件则是困难的。 中型企业需要与自己业务流程…

数据可视化之finebi和tableau电力系统分析实现对比

通过一个电力系统简单案例&#xff0c;尝试实际执行finebi和Tableau数据可视化设计的各项基本步骤&#xff0c;以熟悉Tableau和finebi数据可视化设计技巧&#xff0c;提高大数据可视化应用能力。 一、工具/准备工作 在开始本实验之前&#xff0c;请认真阅读课程的相关内容。 …

写给小白的TensorFlow的入门课

文章目录前言学习AI的必要性和业务的关系最简单的例子要做什么&#xff1f;数据图形化展示构建计算图形计算图形最小化误差MacOS 中配置运行环境安装验证安装简单模型训练识别数字图片的模型训练Softmax Regression算法大概步骤大致算法实现结语参考链接前言 深度学习就是从大…

抖音电商发布2023年食品健康行业8大趋势,新减负、新养生等成为关键词

2022抖音电商食品健康峰会暨年货盛典在杭州成功举行。抖音电商食品健康行业还联合欧睿共同发布了《2023年度食品健康行业趋势洞察报告》。图片来源&#xff1a;抖音电商抖音电商食品健康行业负责人白华在会上透露&#xff0c;过去一年&#xff0c;抖音电商食品健康行业呈现出有…

虚拟机数据库改密码ERROR 1396 (HY000): Operation ALTER USER failed for ‘root‘@‘localhost‘

注&#xff1a;原因为MySql 8.0.11 换了新的身份验证插件&#xff08;caching_sha2_password&#xff09;, 原来的身份验证插件为&#xff08;mysql_native_password&#xff09;。而客户端工具Navicat Premium12 中找不到新的身份验证插件&#xff08;caching_sha2_password&a…

Java实现多线程

目录 基本概念 1、程序、进程、线程 2、使用线程的优点 3、线程的分类 4、线程的生命周期 多线程的实现方法 1、继承Thread类 2、实现Runnable接口 3、实现Callable接口 4、使用线程池 线程同步 1、同步代码、同步方法 2、同步机制中的锁 3、锁&#xff08;Lock&…

【电商】电商后台---采购管理模块

从供应商的管理到合同的管理&#xff0c;再到商品系统的模块的介绍、商品价格与税率维护策略&#xff0c;不知不觉已经完成了几篇文章&#xff0c;前期的准备工作完成后&#xff0c;接下来就应该进入到采购管理模块了。 几天来一直在构思如何写&#xff0c;写的内容让大家看过觉…

使用天地图加载Geoserver的图层

一、写在前面 在项目中往往使用地图作为底图(比如 天地图卫星图等)&#xff0c;再其上覆盖你的通过geoserver发布自定义图层。本文记录了我的实现方法。 二、过程 2.1 我遇到的难题 遇到难题1&#xff1a;使用无人机拍摄制作的正射影像图有几百MB甚至1个G&#xff0c;直接展示图…

YOLO系列目标检测算法——PP-YOLOE

YOLO系列目标检测算法目录 - 文章链接 YOLO系列目标检测算法总结对比- 文章链接 YOLOv1- 文章链接 YOLOv2- 文章链接 YOLOv3- 文章链接 YOLOv4- 文章链接 Scaled-YOLOv4- 文章链接 YOLOv5- 文章链接 YOLOv6- 文章链接 YOLOv7- 文章链接 PP-YOLO- 文章链接 …

深入浅出面向对象设计模式(Java)

设计模式是什么 设计模式是面向对象的一种思想。 设计模式的基本原则&#xff1f; 单一职责原则开放封闭原则里氏替换原则接口隔离原则依赖翻转原则 基本分类和为什么分为3类&#xff1f; 创建型&#xff08;怎么优雅创建对象&#xff09; 结构性&#xff08;对象的结构&am…

巧用Hibernate 完成多数据库的DDL脚本创建

巧用Hibernate 完成多数据库的DDL脚本创建 spring boot jpa 默认的orm框架就是Hibernate。 由hibernate完成数据库的读写也是主流的方式之一。但是不同数据库之间&#xff0c;建表、建索引的方言语句都有较多差别&#xff0c;很难做到一套SQL在所有数据库上进行执行。 那么Hibe…