目录
简述
这是在CSDN的第一篇文章,如有错误或不足,请各位读者指正并给出宝贵建议。
本篇内容记录我阅读《剑指OFFER》的面试题36时所写代码和手稿。
遍历数组构建二叉搜索树
二叉树实体类定义:
/**
* 二叉树节点实体类
*
* @author cyx 2021/08/20 17:07
*/
public class BinaryTreeNode {
private int value; // 节点值
private BinaryTreeNode leftNode; // 左子节点
private BinaryTreeNode rightNode; // 右子节点
public BinaryTreeNode(int value) {
this.value = value;
this.leftNode = null;
this.rightNode = null;
}
// getter and setter ...
}
使用的二叉树示例:(遍历数组中不包含重复的数字)
前序遍历和中序遍历构建
遍历数组构建二叉树的思路中永远不变的一点就是,先找根节点!
那么根据前序遍历的定义,前序遍历数组中第一个值就是根节点的值。那我们就可以去中序遍历数组中找到根节点的位置,该位置左侧就是左子树的所有节点值,右侧就是右子树的所有节点值。
比如我给出的下图:
我们可以根据前序遍历数组确定[7]就是根节点,然后在中序遍历数组中找到[7]的位置。该位置左侧的[4、5、6]对应左子树所有节点值,长度为3;右侧的[8、9、10、11]对应右子树所有节点值,长度为4。既然知道了左右子树的中序遍历数组长度,那前序遍历数组自然也就得到了,分别是[5、4、6]和[8、10、9、11]。最后对左右子树也进行相同操作,二叉树则构建完毕。
代码如下:
private static BinaryTreeNode tree; // 二叉搜索树
/**
* 根据中序遍历int数组和前序遍历int数组创建二叉搜索树
*
* @param inOrder 中序遍历数组
* @param preOrder 前序遍历数组
*/
private static void createBinaryTreeByInOrderAndPreOrder(int[] inOrder, int[] preOrder) throws Exception {
if (ObjectUtils.isEmpty(inOrder) || ObjectUtils.isEmpty(preOrder)) throw new Exception("输入的遍历数组有误");
tree = createByInAndPre(inOrder, 0, inOrder.length - 1,
preOrder, 0, preOrder.length - 1);
}
/**
* 递归方法
*
* @param inOrder 中序遍历数组
* @param inStartIndex 开始下标
* @param inEndIndex 结尾下标
* @param preOrder 前序遍历数组
* @param preStartIndex 开始下标
* @param preEndIndex 结尾下标
* @return
*/
private static BinaryTreeNode createByInAndPre(int[] inOrder, int inStartIndex, int inEndIndex,
int[] preOrder, int preStartIndex, int preEndIndex) throws Exception {
BinaryTreeNode node = new BinaryTreeNode(preOrder[preStartIndex]);
int index = -1;
boolean isExist = false;
for (int i = 0; i < inOrder.length; i++) {
if (inOrder[i] == preOrder[preStartIndex]) {
index = i;
isExist = true;
}
}
if (!isExist) throw new Exception("输入的遍历数组有误");
if (inStartIndex < index) {
node.setLeftNode(createByInAndPre(inOrder, inStartIndex, index - 1, preOrder, preStartIndex + 1, preStartIndex + index - inStartIndex));
}
if (inEndIndex > index) {
node.setRightNode(createByInAndPre(inOrder, index + 1, inEndIndex, preOrder, preEndIndex - (inEndIndex - index) + 1, preEndIndex));
}
return node;
}
后序遍历和中序遍历构建
思路和用前中序遍历构建相同,唯一区别也就是根节点要去后续遍历数组中寻找了。
代码如下:
private static BinaryTreeNode tree; // 二叉搜索树
/**
* 根据中序遍历int数组和后序遍历int数组创建二叉搜索树
*
* @param inOrder 中序遍历数组
* @param postOrder 后续遍历数组
*/
private static void createBinaryTreeByInOrderAndPostOrder(int[] inOrder, int[] postOrder) throws Exception {
if (ObjectUtils.isEmpty(inOrder) || ObjectUtils.isEmpty(postOrder)) throw new Exception("输入的遍历数组有误");
tree = createByInAndPost(inOrder, 0, inOrder.length - 1,
postOrder, 0, postOrder.length - 1);
}
/**
* 递归方法
*
* @param inOrder 中序遍历数组
* @param inStartIndex 开始下标
* @param inEndIndex 结尾下标
* @param postOrder 后序遍历数组
* @param postStartIndex 开始下标
* @param postEndIndex 结尾下标
*/
private static BinaryTreeNode createByInAndPost(int[] inOrder, int inStartIndex, int inEndIndex,
int[] postOrder, int postStartIndex, int postEndIndex) throws Exception {
BinaryTreeNode node = new BinaryTreeNode(postOrder[postEndIndex]);
int index = -1;
boolean isExist = false;
for (int i = 0; i < inOrder.length; i++) {
if (inOrder[i] == postOrder[postEndIndex]) {
index = i;
isExist = true;
}
}
if (!isExist) throw new Exception("输入的遍历数组有误");
if (inStartIndex < index) {
node.setLeftNode(createByInAndPost(inOrder, inStartIndex, index - 1, postOrder, postStartIndex, postStartIndex + (index - inStartIndex - 1)));
}
if (inEndIndex > index) {
node.setRightNode(createByInAndPost(inOrder, index + 1, inEndIndex, postOrder, postEndIndex - (inEndIndex - index), postEndIndex - 1));
}
return node;
}
二叉搜索树转换成双向链表
我们已经根据前中序遍历数组或者中后序遍历数组得到了一颗二叉搜索树,接下来我们就将这颗二叉树转换成排序的双向链表。
那要怎么转换呢?没有具体思路时就该画图来解决。(本人觉得这是开发中很重要的一点!)
如上图所示,右侧则是我们要的排序双向链表。它是不是和中序遍历数组一样?对,转换的代码中就是使用了中序遍历的思想。
阅读代码之前,我们可以先根据图分析一下:
我们还是从根节点[7]入手,图中右侧排序双向链表中,节点[7]的前一个节点是[6],后一个节点是[8]。对应左侧二叉搜索树中,[6]是左子树中值最大的节点,[8]是右子树中值最小的节点。
我们可以发现这思路和中序遍历是一样的,即转换的过程和中序遍历的过程应该也是一样的。按照中序遍历的顺序,当我们遍历转换到根节点[7]时,它的左子树已经转换成了一个排序的链表,并且处在链表中的最后一个节点[6]是当前值最大的节点,我们把值为[6]的节点和根节点链接起来;然后再去遍历右子树......
是不是有点乱?我们先看代码,然后具体例子分析。
代码如下:
private static BinaryTreeNode tree; // 二叉搜索树
private static BinaryTreeNode lastNodeInList; // 辅助节点-二叉搜索树转化为双向链表的递归过程中,存储上一节点
/**
* 将二叉搜索树转化成双向链表,并输出表头
*/
private static void convert() {
if (ObjectUtils.isEmpty(tree)) return;
convertNode(tree); // 递归
System.out.println("双向链表尾节点值value==" + lastNodeInList.getValue()); // value == 11
while (!ObjectUtils.isEmpty(lastNodeInList.getLeftNode())) {
lastNodeInList = lastNodeInList.getLeftNode();
}
System.out.println("双向链表头节点值value==" + lastNodeInList.getValue()); // value == 4
}
/**
* 转化时的递归方法
*/
private static void convertNode(BinaryTreeNode node) {
if (ObjectUtils.isEmpty(node)) return;
BinaryTreeNode current = node; // a.记录当前节点
if (!ObjectUtils.isEmpty(current.getLeftNode())) { // b.如果当前节点有左节点,则先递归操作它的左节点
convertNode(current.getLeftNode());
}
current.setLeftNode(lastNodeInList); // c.将上一节点设置为当前节点的左节点
if (!ObjectUtils.isEmpty(lastNodeInList)) { // d.当遍历第一个节点时,还没有上一节点,所以不用设置上一节点的右节点
lastNodeInList.setRightNode(current); // e.将当前节点设置为上一节点的右节点
}
lastNodeInList = current; // f.记录当前节点,供下一层递归使用
if (!ObjectUtils.isEmpty(current.getRightNode())) { // g.如果当前节点有右节点,则先递归操作它的右节点(注意此时已经递归操作完了当前节点左子树所有节点了)
convertNode(current.getRightNode());
}
}
说明一下上面代码中的一些变量:
lastNodeInList:记录遍历转换中上一节点;current:记录当前节点。
以本文的二叉搜索树例子分析:
当第一次执行convertNode()时,node是节点[7],因为节点[7]有左节点[5],则在执行到步骤b时,进入到下一层递归,操作节点[5];
同样节点[5]有左节点[4],同样在执行到步骤b时,进入下一层递归,操作节点[4];
操作节点[4]时就不同了,节点[4]的左节点为null,跳过步骤b,执行步骤c将lastNodeInList赋给节点[4]的左节点(此时lastNodeInList是null,自然不会执行步骤d和步骤e),然后执行步骤f,lastNodeInList现在记录的就是节点[4],节点[4]没有右节点,则不走步骤g;
节点[4]这层递归走完,返回上一层递归,即current是节点[5]的这层。继续走后续逻辑,走到步骤c时设置当前节点的左节点,即节点[5]的左节点设置成节点[4];因为现在lastNodeInList不为空了,则会走步骤d和步骤e,即设置节点[4]的右节点为节点[5](现在就形成了第一节排序双向链表:4的下一节点是5,5的上一节点是4);然后执行步骤f,lastNodeInList现在记录的就是节点[5];步骤g也会执行,即又是下一层递归了,操作节点[6];
继续递归,递归......直到中序遍历完毕。
执行main方法
public static void main(String[] args) throws Exception {
/**
* 二叉树结构:
* 7
* / \
* 5 8
* / \ \
* 4 6 10
* / \
* 9 11
*/
int[] preOrder = {7, 5, 4, 6, 8, 10, 9, 11};
int[] inOrder = {4, 5, 6, 7, 8, 9, 10, 11};
int[] postOrder = {4, 6, 5, 9, 11, 10, 8, 7};
// 1.根据中后序(或中前序)遍历int值数组创建二叉搜索树
// createBinaryTreeByInOrderAndPostOrder(inOrder, postOrder);
createBinaryTreeByInOrderAndPreOrder(inOrder, preOrder);
// 2.根据二叉搜索树转换成一个排序的双向链表
convert();
}