数据结构——树

本文详细介绍了树的结构、专有名词,重点讲述了二叉树的实现、操作方法,包括节点数计算、层次遍历、搜索与重构,并通过实例展示了如何在Java中操作二叉树。同时,清晰比较了树与图的差异,涵盖了无序树、有序树类别和常见树的种类。
摘要由CSDN通过智能技术生成

树是什么?

树是由上往下具有层次结构的数据结构

树的特点

  • 每个节点都只有有限个子节点或无子节点
  • 没有父节点的节点称为根节点
  • 每一个非根节点有且只有一个父节点
  • 除了根节点外,每个子节点可以分为多个不相交的子树
  • 树里面没有环路

专有名词

  • 节点的度:一个节点含有的子树的个数称为该节点的度
  • 树的度:一棵树中,最大的节点度称为树的度
  • 叶节点或终端节点:度为零的节点
  • 非终端节点或分支节点:度不为零的节点
  • 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点
  • 子节点:一个节点含有的子树的根节点称为该节点的子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推
  • 深度:对于任意节点n,n的深度为从根到n的路径长度,根的深度为0
  • 高度:对于任意节点n,n的高度为从n到叶节点的路径长度,叶节点的高度为0
  • 堂兄弟节点:父节点在同一层的节点互为堂兄弟
  • 节点的祖先:从根到该节点所经分支上的所有节点
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙
  • 森林:由m(m>=0)棵互不相交的树的集合称为森林

树和图的区别

  • 树是图,图不一定是树,树是图的子集
  • 树有一个根节点,图没有
  • 树可以递归遍历,图要看情况
  • 树的非根节点必定有一个父节点,图不一定
  • 树是一种层次结构,图是网状结构

树的分类

  • 无序树:树中任意节点的子节点之间没有顺序关系
  • 有序树:树中任意节点的子节点之间有顺序关系

有序树又分为:

  • 二叉树:每个节点最多含有两个子树的树称为二叉树
  • 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树
  • 满二叉树:所有叶节点都在最底层的完全二叉树
  • 二叉搜索树:除叶节点外,其余节点中左节点小于父节点,右节点大于父节点
  • 平衡二叉树:当且仅当任何节点的两棵子树的高度差不大于1的二叉搜索树
  • B树:平衡多叉树,是平衡二叉树的一般化
  • 红黑树:用红、黑色节点简化B树的平衡二叉树
  • 左倾红黑树:红节点向左倾斜的红黑树
  • 霍夫曼树:带权路径最短的二叉树称为哈夫曼树

二叉树实现(基于链表)

二叉树可用链表和数组实现,但当树完全斜向一边时,基于数组的实现会造成大量的空间浪费,故普通的二叉树通常基于链表

节点数据

class TreeNode<T> {

    T value;
    TreeNode<T> leftChild;
    TreeNode<T> rightChild;

    TreeNode(T value) {
        this.value = value;
    }
}

对二叉树的操作

对二叉树的操作和树本身是无关的,即这些操作对于任何链表实现二叉树都是有效的,故它们都是静态方法,可封装为工具类

获取总节点数

递归获取左右节点的数量,直到叶节点,返回时进行计数

public static <T> int getTreeNum(TreeNode<T> root) {
    if (root == null) {
        return 0;
    }
    return getTreeNum(root.leftChild) + getTreeNum(root.rightChild) + 1;
}

获取指定层次节点数

若根节点为空或层次为不正确返回0,只有根节点返回1,其余返回该层次左右节点数量

public static <T> int getCurrentLevelNum(TreeNode<T> root, int level) {
    if (root == null || level < 1) {
        return 0;
    }
    if (level == 1) {
        return 1;
    }
    int leftNum = getCurrentLevelNum(root.leftChild, level - 1);
    int rightNum = getCurrentLevelNum(root.rightChild, level - 1);
    return leftNum + rightNum;
}

获取叶节点数

递归获取左右叶节点的数量,当某节点的左右节点都为空时则该节点是叶节点

public static <T> int getLeafNum(TreeNode<T> root) {
    if (root == null) {
        return 0;
    }
    if (root.leftChild == null && root.rightChild == null) {
        return 1;
    }
    int leftNum = getLeafNum(root.leftChild);
    int rightNum = getLeafNum(root.rightChild);
    return leftNum + rightNum;
}

获取高度/深度

高度为指定节点到叶节点的距离。获取左右节点高度,取最大值,递归到叶节点返回,返回时进行计数

public static <T> int getTreeHeight(TreeNode<T> root) {
    if (root == null) {
        return 0;
    }
    int leftDepth = getTreeHeight(root.leftChild) + 1;
    int rightDepth = getTreeHeight(root.rightChild) + 1;
    return Math.max(leftDepth, rightDepth);
}

深度为根节点到指定节点的距离,指定节点深度=根节点高度-指定节点高度

遍历

  • 前序遍历:根节点→左子树→右子树
  • 中序遍历:左子树→根节点→右子树
  • 后序遍历:左子树→右子树→根节点
  • 层次遍历:从上往下从左往右层层遍历
private static <T> void visitNode(TreeNode<T> node) {
    System.out.print(node.value + " ");
}

public static <T> void preOrderTraversal(TreeNode<T> root) {
    if (root == null) {
        return;
    }
    visitNode(root);
    preOrderTraversal(root.leftChild);
    preOrderTraversal(root.rightChild);
}

public static <T> void midOrderTraversal(TreeNode<T> root) {
    if (root == null) {
        return;
    }
    midOrderTraversal(root.leftChild);
    visitNode(root);
    midOrderTraversal(root.rightChild);
}

public static <T> void sufOrderTraversal(TreeNode<T> root) {
    if (root == null) {
        return;
    }
    sufOrderTraversal(root.leftChild);
    sufOrderTraversal(root.rightChild);
    visitNode(root);
}

public static <T> void LevelTraversal(TreeNode<T> root) {
    Queue<TreeNode<T>> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        TreeNode<T> node = queue.poll();
        visitNode(node);
        if (node.leftChild != null) {
            queue.offer(node.leftChild);
        }
        if (node.rightChild != null) {
            queue.offer(node.rightChild);
        }
    }
}

搜索

  • 广度优先搜索:利用队列先进先出的性质,依次对比根节点及其左右节点,从上往下从左往右层层搜索
  • 深度优先搜索:利用栈先进后出的性质,若右节点先入栈,则先遍历左子树,反之则先遍历右子树
public static <T> boolean BreadthFirstSearch(TreeNode<T> root, T value) {
    Queue<TreeNode<T>> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        TreeNode<T> node = queue.poll();
        visitNode(node);
        if (node.value == value) {
            return true;
        }
        if (node.leftChild != null) {
            queue.offer(node.leftChild);
        }
        if (node.rightChild != null) {
            queue.offer(node.rightChild);
        }
    }
    return false;
}

public static <T> boolean DepthFirstSearch(TreeNode<T> root, T value) {
    Stack<TreeNode<T>> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode<T> node = stack.pop();
        visitNode(node);
        if (node.value == value) {
            return true;
        }
        if (node.leftChild != null) {
            stack.push(node.leftChild);
        }
        if (node.rightChild != null) {
            stack.push(node.rightChild);
        }
    }
    return false;
}

重构树

根据前序和中序重构树:前序第一个为根节点,然后根据中序分为左右子树,获取左子树前序和中序,递归构建左子树,右子树同理

public static <T> TreeNode<T> getTreeByPreOrderAndMidOrder(List<T> pre, List<T> mid) {
    if (pre == null || mid == null || pre.size() == 0 || mid.size() == 0) {
        return null;
    }
    if (pre.size() == 1) {
        return new TreeNode<T>(pre.get(0));
    }
    TreeNode<T> root = new TreeNode<T>(pre.get(0));
    int index = mid.indexOf(pre.get(0));
    
    List<T> leftPre = pre.subList(1, index + 1);
    List<T> leftMid = mid.subList(0, index);
    root.leftChild = getTreeByPreOrderAndMidOrder(leftPre, leftMid);
    
    List<T> rightPre = pre.subList(index + 1, pre.size());
    List<T> rightMid = mid.subList(index + 1, mid.size());
    root.rightChild = getTreeByPreOrderAndMidOrder(rightPre, rightMid);
    
    return root;
}

根据后序和中序重构树:后序最后一个为根节点,然后根据中序分为左右子树,获取左子树的后序和中序,递归构建左子树,右子树同理


public static <T> TreeNode<T> getTreeBySufOrderAndMidOrder(List<T> suf, List<T> mid) {
    if (suf == null || mid == null || suf.size() == 0 || mid.size() == 0) {
        return null;
    }
    if (suf.size() == 1) {
        return new TreeNode<T>(suf.get(0));
    }
    
    TreeNode<T> root = new TreeNode<T>(suf.get(suf.size() - 1));
    int index = mid.indexOf(suf.get(suf.size() - 1));
    
    List<T> leftSuf = suf.subList(0, index);
    List<T> leftMid = mid.subList(0, index);
    root.leftChild = getTreeBySufOrderAndMidOrder(leftSuf, leftMid);
    
    List<T> rightSuf = suf.subList(index, suf.size() - 1);
    List<T> rightMid = mid.subList(index + 1, mid.size());
    root.rightChild = getTreeBySufOrderAndMidOrder(rightSuf, rightMid);
    
    return root;
}

Tips:

  • 在定位根节点时,若出现与根节点一样的元素,indexOf只会返回第一个对应元素的索引,会导致错误

测试代码

构造树

TreeNode<Integer> node1 = new TreeNode<>(1);
TreeNode<Integer> node2 = new TreeNode<>(2);
TreeNode<Integer> node3 = new TreeNode<>(3);
TreeNode<Integer> node4 = new TreeNode<>(4);
TreeNode<Integer> node5 = new TreeNode<>(5);
TreeNode<Integer> node6 = new TreeNode<>(6);
node1.leftChild = node2;
node1.rightChild = node3;
node2.leftChild = node4;
node2.rightChild = node5;
node3.leftChild = node6;

测试树

System.out.print("前序遍历:");
preOrderTraversal(node1);
System.out.println();

System.out.print("中序遍历:");
midOrderTraversal(node1);
System.out.println();

System.out.print("后序遍历:");
sufOrderTraversal(node1);
System.out.println();

System.out.print("层次遍历: ");
LevelTraversal(node1);
System.out.println();

System.out.print("广度优先搜索:");
boolean BreadthFirstResult = BreadthFirstSearch(node1, 5);
System.out.print(BreadthFirstResult ? "found" : "not found");
System.out.println();

System.out.print("深度优先搜索:");
boolean DepthFirstResult = DepthFirstSearch(node1, 5);
System.out.print(DepthFirstResult ? "found" : "not found");
System.out.println();

System.out.println("树高度:" + getTreeHeight(node1));
System.out.println("节点2深度:" + (getTreeHeight(node1) - getTreeHeight(node2)));
System.out.println("总节点数:" + getTreeNum(node1));
System.out.println("叶节点数:" + getLeafNum(node1));
System.out.println("第三层节点数:" + getCurrentLevelNum(node1, 3));

TreeNode<String> rebuildTree1 = getTreeByPreOrderAndMidOrder(Arrays.asList("1 2 4 5 3 6".split(" ")), Arrays.asList("4 2 5 1 6 3".split(" ")));
System.out.print("\n根据前序和中序重建树,其后序遍历:");
sufOrderTraversal(rebuildTree1);
System.out.println();

TreeNode<String> rebuildTree2 = getTreeBySufOrderAndMidOrder(Arrays.asList("4 5 2 6 3 1".split(" ")), Arrays.asList("4 2 5 1 6 3".split(" ")));
System.out.print("\n根据后序和中序重建树,其前序遍历:");
preOrderTraversal(rebuildTree2);
System.out.println();

如上打印

前序遍历:1 2 4 5 3 6 
中序遍历:4 2 5 1 6 3 
后序遍历:4 5 2 6 3 1 
层次遍历: 1 2 3 4 5 6 
广度优先搜索:1 2 3 4 5 found
深度优先搜索:1 3 6 2 5 found
树高度:3
节点2深度:1
总节点数:6
叶节点数:3
第三层节点数:3
根据前序和中序重建树,其后序遍历:4 5 2 6 3 1 
根据后序和中序重建树,其前序遍历:1 2 4 5 3 6 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值