概叙

科普文:算法和数据结构系列【非线性数据结构:树Tree和堆Heap的原理、应用、以及java实现】-CSDN博客

科普文:算法和数据结构系列【非线性数据结构:图Graph的原理、应用、以及java实现】-CSDN博客

科普文:算法和数据结构系列【排序算法:常见10种排序算法的原理、应用、以及java实现】-CSDN博客

科普文:算法和数据结构系列【查找算法:常见7种查找算法的原理、应用、以及java实现】-CSDN博客

 

前面我们已经梳理了常见的数据结构、10大排序算法、7大查找算法。这里把树拿出来,再做一点补充。为啥要补充一下,主要原因是树在组织数据、表示层级关系以及提高数据操作效率方面具有独特优势,并且符合人类的学习习惯。

学习树结构对于提高数据处理效率、优化算法设计、增强问题解决能力以及符合人类学习习惯等方面都具有重要意义。特别是对递归、遍历、有序性的理解,了解完树,会理解的更深。

为了和二叉树做区分,我们这里就整两个不常见的树:4叉树、N叉树,来做示例讲解。

树的定义

树的定义:树是由n(n>=0)个元素节点组成的有限集合,当n=0时,称为空树。

对于非空树应满足以下要求:

(1)有且仅有一个根节点;

(2)当n>1时,其余节点可分成m(m>=0)个互不相交的有限集合,其中每一个集合本身又是一棵树,称为根的子树。

从定义中我们可以得到以下结论:

1)树是分支分层结构;

2)树中仅有根节点没有父节点;

3)除根节点外,其余节点有且仅有一个父节点;

4)树中每个节点,可以有零个或多个子节点;

5)根节点到任何除自身之外的节点,有且仅有一条路径;

学习树的意义

  • 首先,树结构非常适合表示自然界或信息系统中的分层结构,如家谱、公司组织架构、文件目录等。其根节点表示起点或最高层级,子节点逐级代表下层关系,这种层次性使得树结构在组织和表示复杂关系时非常直观和高效‌。
  • 其次,树结构在数据查找和更新方面具有显著优势。在大量数据的组织和查询中,线性结构(如数组、链表)需要遍历所有元素,时间复杂度较高,通常是O(n)。而树的层级结构,特别是平衡树,可以在O(log n)或O(h)时间内完成查找、插入、删除等操作(其中h为树的高度),大大提高了数据操作的效率‌。
  • 此外,树结构还符合人类的学习习惯和分类归纳的本能。树状结构就像树木一样,有主干、分支、枝叶,可以层层细分,帮助我们看清主次内容,理清思路逻辑。通过树状结构,我们可以更好地分类归纳知识,发现规律,深化认知‌。
  • 最后,树结构在计算机科学中有着广泛的应用,如数据库索引、网络路由、AI搜索等领域。掌握树结构有助于我们更好地理解和解决这些领域中的实际问题‌。

学习树的作用

学习树结构具有非常实际的用处‌。学习树结构不仅有助于我们更好地理解和组织知识,还能在提高数据操作效率、解决实际问题以及符合人类认知习惯等方面发挥重要作用。

以下是几个核心的应用场景和优势:

  1. 知识整理与结构化‌:在学习知识或做笔记时,树结构能够清晰地展示知识的层次和关系。例如,整理历史朝代的发展脉络,可以将朝代作为树干,政治、经济、文化等作为大分枝,进一步细化为小分枝,形成一目了然的知识体系‌。树形图或思维导图等树结构工具,有助于全面理解知识结构,快速掌握原理和思维方法,找到事物间的内在联系‌。

  2. 高效的数据操作‌:在数据结构中,树结构如平衡树(红黑树、AVL树等)能够保持相对平衡,使得查找、插入和删除操作的时间复杂度为O(log n),远高于数组的O(n)复杂度,提高了数据操作的效率‌。树结构支持快速的动态操作,特别适合频繁的动态插入和删除场景,同时提供高效的查询性能‌。

  3. 广泛的应用领域‌:树结构在图书管理、文件系统、组织架构、族谱管理等领域有广泛应用,能够高效地对数据进行分类分层管理‌。在计算机科学中,树结构是数据库索引(如B树、B+树)、网络路由、AI搜索等领域的基础数据结构,对于提高系统性能和优化算法设计具有重要意义‌。

  4. 符合人类认知习惯‌:树形层级结构是人类认知、思考、分析的主要结构,具有简洁、清晰、高效的特点。它符合人类分类、归纳、理解的本能,有助于我们更好地组织和记忆信息‌。

树的抽象定义

树的基类:

/**
 * @author zhouxx
 * @create 2025-01-11 14:49
 */
public class BaseTreeNode<T extends Comparable<T>> {
    T value;
    public BaseTreeNode(){
        this(null);
    }
    public BaseTreeNode(T value){
        this.value=value;
    }
}

树的接口:


/**
 * @author zhouxx
 * @create 2025-01-10 12:48
 */
public interface Tree<T extends Comparable<T>> {

    // 抽象方法,具体实现由子类提供
    public void buildTree(T[] array);
    public void add(T value);
    public void remove(T value);
    public void find(T value);

    /**
     * 遍历
     *
     * 深度优先遍历(Depth First Search, DFS)‌:深度优先遍历是一个递归的过程,从树的根节点开始,沿着树的深度方向一直遍历到叶节点,然后回溯到父节点,再继续遍历其他未访问的子节点‌。
     * 深度优先遍历有三种具体的遍历方式:
     * 前序遍历(先访问根节点,再访问左子树,最后访问右子树)、
     * 中序遍历(先访问左子树,再访问根节点,最后访问右子树)
     * 后序遍历(先访问左子树,再访问右子树,最后访问根节点)‌。
     *
     * ‌广度优先遍历(Breadth First Search, BFS)‌:广度优先遍历是一种逐层访问节点的遍历方式(层次遍历),从树的根节点开始,先访问所有相邻的节点(即同一层的节点),然后再逐层向下访问相邻的节点‌。
     *
     * */
    // 前序遍历(先访问根节点,再访问左子树,最后访问右子树)
    public void preOrder();

    // 中序遍历(先访问左子树,再访问根节点,最后访问右子树)
    public void inOrder();

    // 后序遍历(先访问左子树,再访问右子树,最后访问根节点)
    public void postOrder();

    // 层序遍历:根节点开始,逐层访问节点的遍历方式(层次遍历)
    public void levelOrder();
}

四叉树

四叉树的节点定义

/**
 * @author zhouxx
 * @create 2025-01-10 11:17
 */
public class _4TreeNode<T extends Comparable<T>> {
    T value;
    _4TreeNode<T> parentNode;
    _4TreeNode<T> leftNode; /**二叉树是最多只有左右两个子节点*/
    _4TreeNode<T> rightNode;
    _4TreeNode<T> leftNode1;//非二叉树
    _4TreeNode<T> rightNode1;//非二叉树

    public _4TreeNode(T value) {
        this.value = value;
        this.parentNode=null;
        this.leftNode=null;
        this.rightNode=null;
        this.leftNode1=null;
        this.rightNode1=null;
    }
    //是否是叶子节点
    public boolean isLeaf(){
        return (this.leftNode==null && this.rightNode==null&& this.leftNode1==null&& this.rightNode1==null);
    }
    //是否是叶子节点
    public boolean isRoot(){
        return (this.parentNode==null);
    }
    @Override
    public String toString() {
        StringBuilder stringBuilder=new StringBuilder("{");
        stringBuilder.append("value=").append(value).append(",");
        stringBuilder.append("isLeaf=").append(isLeaf()).append(",");
        stringBuilder.append("isRoot=").append(isRoot()).append(",");
        stringBuilder.append("parentNode=").append(parentNode==null?"":parentNode.value).append(",");
        stringBuilder.append("leftNode=").append(leftNode==null?"":leftNode.value).append(",");
        stringBuilder.append("rightNode=").append(rightNode==null?"":rightNode.value).append(",");
        stringBuilder.append("leftNode1=").append(leftNode1==null?"":leftNode1.value).append(",");
        stringBuilder.append("rightNode1=").append(rightNode1==null?"":rightNode1.value).append(",");
        stringBuilder.append("}");
        return stringBuilder.toString();
        //return value.toString();
    }
}

四叉树

package com.zxx.study.algorithm.datastruct.DataStructures.mtree.tree;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * @author zhouxx
 * @create 2025-01-10 16:18
 */
public class _4ChildTree<T extends Comparable<T>>  implements Tree<T> {
    _4TreeNode<T> root;
    int cnt;
    public _4ChildTree() {
        root = null;
    }

    //遍历打印
    public void printTree( _4TreeNode<T> node) {
        if (node == null) {
            //cnt=0;
            return;
        }
        cnt++;
        System.out.println(cnt + "打印:" + node.toString() + " ");
        if (node.leftNode != null) {
            printTree(node.leftNode);
        }
        if (node.rightNode != null) {
            printTree(node.rightNode);
        }
        if (node.leftNode1 != null) {
            printTree(node.leftNode1);
        }
        if (node.rightNode1 != null) {
            printTree(node.rightNode1);
        }
    }
    @Override
    public void buildTree(T[] array) {
        for (T tmp : array) {
            add(tmp);
//            cnt=0;
//            System.out.println("==="+tmp);
//            printTree(root);
        }
    }

    @Override
    public void add(T data) {
        _4TreeNode<T> newNode = new _4TreeNode<>(data);
        //维护root
        if (root == null) {
            root = newNode;
            return;
        }
        //维护下一层
        add(root, newNode);
    }

    public void add(_4TreeNode<T> current, _4TreeNode<T> newNode) {
        if (current != null ) {
            // 每次维护一层
            if (current.leftNode == null && newNode.parentNode == null) {
                newNode.parentNode = current;
                current.leftNode = newNode;
                return;
            }
            if (current.rightNode == null&& newNode.parentNode == null) {
                newNode.parentNode = current;
                current.rightNode = newNode;
                return;
            }
            if (current.leftNode1 == null&& newNode.parentNode == null) {
                newNode.parentNode = current;
                current.leftNode1 = newNode;
                return;
            }
            if (current.rightNode1 == null&& newNode.parentNode == null) {
                newNode.parentNode = current;
                current.rightNode1 = newNode;
                return;
            }
            //父节点为空,说明上一层已经挂满了
            if (newNode.parentNode == null) {
                // 维护下一层
                add(current.leftNode, newNode);
                add(current.rightNode, newNode);
                add(current.leftNode1, newNode);
                add(current.rightNode1, newNode);
            }
        }
    }

    @Override
    public void remove(T data) {
        // 叶子节点,直接删除
        //非叶子节点,其子节点要处理
        // 采用广度优先,逐层遍历

    }

    //遍历搜索
    //todo 因为是无序,所以查找必须遍历所有节点
    public void search( _4TreeNode<T> node,T data,List<_4TreeNode<T>> result ) {
        if (node == null) {
            //cnt=0;
            return;
        }
        if(node.value.compareTo(data)==0){
            result.add(node);
        }
        if (node.leftNode != null) {
            search(node.leftNode,data,result);
        }
        if (node.rightNode != null) {
            search(node.rightNode,data,result);
        }
        if (node.leftNode1 != null) {
            search(node.leftNode1,data,result);
        }
        if (node.rightNode1 != null) {
            search(node.rightNode1,data,result);
        }
    }
    @Override
    public void find(T data) {
        search(data);
    }
    public List<_4TreeNode<T>> search(T data) {
        List<_4TreeNode<T>> result = new ArrayList<>();
        search(root,data,result);
        return result;
    }

    // 前序遍历(先访问根节点,再访问左子树,最后访问右子树) todo 四叉树姑且按照二叉树的来遍历
    public void preOrder(_4TreeNode<T> node) {
        if (node == null) {return;}
        System.out.println( node.value); // 访问当前节点
        preOrder(node.leftNode);
        preOrder(node.rightNode);
        preOrder(node.leftNode1);
        preOrder(node.rightNode1);
    }

    public void inOrder(_4TreeNode<T> node) {
        if (node == null) {return;}
        preOrder(node.leftNode);
        System.out.println( node.value); // 访问当前节点
        preOrder(node.rightNode);
        preOrder(node.leftNode1);
        preOrder(node.rightNode1);
    }
    public void postOrder(_4TreeNode<T> node) {
        if (node == null) {return;}
        preOrder(node.leftNode);
        preOrder(node.rightNode);
        System.out.println( node.value); // 访问当前节点
        preOrder(node.leftNode1);
        preOrder(node.rightNode1);
    }

    public void levelOrder(_4TreeNode<T> node) {
        if (node == null) {return;}
        Queue<_4TreeNode<T>> queue = new LinkedList<>();
        queue.offer(node);
        while (!queue.isEmpty()) {
            //首先将根节点入队,然后在循环中不断地从队列中取出节点、打印节点的值,并将该节点的所有子节点入队。
            // 这样就能够按照层级顺序遍历整个树。
            _4TreeNode<T> currentNode = queue.poll();
            System.out.println( currentNode.value); // 访问当前节点

            if (currentNode.leftNode != null) {queue.offer(currentNode.leftNode);}
            if (currentNode.rightNode != null) {queue.offer(currentNode.rightNode);}
            if (currentNode.leftNode1 != null) {queue.offer(currentNode.leftNode1);}
            if (currentNode.rightNode1 != null) {queue.offer(currentNode.rightNode1);}
        }
    }

    @Override
    public void preOrder() {
        preOrder(root);
    }

    @Override
    public void inOrder() {
        inOrder(root);
    }

    @Override
    public void postOrder() {
        postOrder(root);
    }

    @Override
    public void levelOrder() {
        levelOrder(root);
    }
}

测试验证:

/**
 * @author zhouxx
 * @create 2025-01-10 18:19
 */
public class TestTree {
    public static void main(String[] args) {

        _4ChildTree<Integer> a4ChildTree =new _4ChildTree<>();
        Integer[] array=null;
        List<_4TreeNode<Integer>> result=null;
        array=new Integer[]{2,2,2,2,2,2,2,2,2};
        a4ChildTree.buildTree(array);
        a4ChildTree.printTree(a4ChildTree.root);//打印
        result=a4ChildTree.search(2);
        System.out.println(array.length+","+result.size()+" , "+result.toString());

        Integer[] array1={1,2,3,4,5,6,7,8,9,10};
        a4ChildTree =new _4ChildTree<>();
        a4ChildTree.buildTree(array1);
        a4ChildTree.printTree(a4ChildTree.root);//打印
        for(int data:array1) {
            result = a4ChildTree.search(data);
            System.out.println(array1.length + "," + result.size() + " , " + result.toString());
        }
    }
}

运行结果

示例一:在四叉树中插入了9个值为2的节点,搜索值为2的节点,打印搜索结果(刚好是9个值为2的节点),符合预期,四叉树结果无误。

示例二:在四叉树中插入1-10不重复的10个数,观察其结果,逐个搜索验证其搜索方法。

可以看到四叉树刚好是4层

符合预期

N叉树

N茶树的节点定义

import java.util.ArrayList;
import java.util.List;

/**
 * @author zhouxx
 * @create 2025-01-11 13:37
 */
public class _NaryTreeNode<T extends Comparable<T>> extends BaseTreeNode {
    List<_NaryTreeNode<T>> children;

    // 构造方法
    public _NaryTreeNode(T value) {
        super(value);
        this.value = value;
        this.children = new ArrayList<>();
    }

    // 添加子节点的方法
    public void addChild(_NaryTreeNode<T> child) {
        children.add(child);
    }
}

N叉树

package com.zxx.study.algorithm.datastruct.DataStructures.mtree.tree;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * @author zhouxx
 * @create 2025-01-11 13:38
 */
public class _NaryTree<T extends Comparable<T>>  implements Tree<T>  {
    _NaryTreeNode root;

    public _NaryTree() {
        this(null);
    }
    // 构造方法
    public _NaryTree(T value) {
        this.root = new _NaryTreeNode(value);
    }


    @Override
    public void buildTree(T[] array) {
        if (array == null || array.length == 0) {
            return;
        }

        // 假设数组的第一个元素是根节点的值
        this.root = new _NaryTreeNode<T>(array[0]);

        // 使用一个队列来按层次构建树
        Queue<_NaryTreeNode<T>> queue = new LinkedList<>();
        queue.offer(this.root);

        int index = 1;
        while (!queue.isEmpty() && index < array.length) {
            _NaryTreeNode<T> currentNode = queue.poll();

            // 为当前节点添加子节点
            while (index < array.length && canAddChild(currentNode, array[index])) {
                _NaryTreeNode<T> child = new _NaryTreeNode<>(array[index]);
                currentNode.addChild(child);
                queue.offer(child);
                index++;
            }
        }
    }
    // 辅助方法:判断是否可以将给定值作为当前节点的子节点
    // 这里可以根据具体需求来实现,比如限制子节点的数量或根据值的某种特性来决定
    private boolean canAddChild(_NaryTreeNode<T> parentNode, T childValue) {
        // 简单的实现:总是可以添加子节点
        return true;
    }

    @Override
    public void add(T data) {
        if (this.root == null) {
            this.root = new _NaryTreeNode<T>(data);
        } else {
            _NaryTreeNode<T> newNode = new _NaryTreeNode<>(data);
            this.root.addChild(newNode);
        }
    }

    @Override
    public void remove(T value) {
        boolean deleted = deleteRecursively(root,value);
        if (deleted) {
            System.out.println("Node with value "+value.toString()+" was deleted.");
        } else {
            System.out.println("Node with value "+value.toString()+" was not found or could not be deleted.");
        }
    }
    // 递归删除方法
    private boolean deleteRecursively(_NaryTreeNode<T> currentNode, T value) {
        if (currentNode == null) {
            return true;
        }

        // 如果当前节点的值就是要删除的值
        if (currentNode.value.compareTo(value)==0) {
            // 如果当前节点是叶子节点,直接返回true表示可以删除
            // 如果在子树中成功删除了节点,则从当前节点的子节点列表中移除该子节点
            currentNode.value=null;
            currentNode.children=null;
            // 如果当前节点不是叶子节点,则需要将其子节点全部“提升”到当前节点的父节点下
            // 这里为了简化,我们不实现子节点的提升,而是简单地返回false表示不能删除非叶子节点
            // 在实际应用中,你可能需要实现更复杂的逻辑来处理这种情况
            return true;
        }

        // 遍历子节点进行递归删除
        for (_NaryTreeNode<T>  child : currentNode.children) {
            if (!deleteRecursively(child, value)) {

                // 可以根据需要返回true或继续检查其他子节点
                // 这里我们假设只要删除了一个节点就返回true
                return false;
            }
        }

        // 没有在子节点中找到要删除的值
        return true;
    }
    public List<_NaryTreeNode<T>> search(T data) {
        List<_NaryTreeNode<T>> result=new ArrayList<>();
        search(root,data,result);
        return result;
    }
    public void search(_NaryTreeNode<T> node,T data,List<_NaryTreeNode<T>> result) {
        if (node != null) {
            if(node.value.compareTo(data)==0) {
                result.add(node);
            }
            for (_NaryTreeNode<T> child : node.children) {
                search(child,data,result); // 递归地访问每个子节点
            }
        }
    }
    @Override
    public void find(T data) {
        search(data);
    }

    @Override
    public void preOrder() {
        preOrder(root);
    }

    @Override
    public void inOrder() {
        inOrder(root);
    }

    @Override
    public void postOrder() {
        postOrder(root);
    }

    @Override
    public void levelOrder() {
        levelOrder(root);
    }

    public void preOrder(_NaryTreeNode<T> node) {
        if (node != null) {
            System.out.println( node.value); // 访问当前节点
            for (_NaryTreeNode<T> child : node.children) {
                preOrder(child); // 递归地访问每个子节点
            }
        }
    }

    //中序遍历(对于N叉树来说,中序遍历的定义可能不太直观,但我们可以理解为先遍历第一个子树,然后访问根节点,最后遍历剩余的子树)
    public void inOrder(_NaryTreeNode<T> node) {
        if (node != null) {
            // 先遍历第一个子节点
            if (!node.children.isEmpty()) {
                inOrder(node.children.get(0));
            }
            // 然后访问当前节点
            System.out.println(node.value);
            // 最后遍历剩余的子节点
            for (int i = 1; i < node.children.size(); i++) {
                inOrder(node.children.get(i));
            }
        }
    }

    //后序遍历(先遍历所有子节点,然后访问根节点)
    public void postOrder(_NaryTreeNode<T> node) {
        if (node != null) {
            // 先遍历所有子节点
            for (_NaryTreeNode<T> child : node.children) {
                postOrder(child);
            }
            // 然后访问当前节点
            System.out.println(node.value);
        }
    }
    //层次遍历(按层级从上到下、从左到右遍历节点)
    //层次遍历通常使用队列来实现,因为它需要按照层级顺序访问节点。
    public void levelOrder(_NaryTreeNode<T> node) {
        if (node == null) {
            return;
        }
        //队列来保存待访问的节点
        Queue<_NaryTreeNode<T>> queue = new LinkedList<>();
        queue.offer(node);

        while (!queue.isEmpty()) {
            //首先将根节点入队,然后在循环中不断地从队列中取出节点、打印节点的值,并将该节点的所有子节点入队。
            // 这样就能够按照层级顺序遍历整个树。
            _NaryTreeNode<T> currentNode = queue.poll();
            System.out.println(currentNode.value);
            for (_NaryTreeNode<T> child : currentNode.children) {
                //队列来保存待访问的节点
                queue.offer(child);
            }
        }
    }

    // 辅助方法:打印树的结构(参考前序遍历)
    public void printTree(_NaryTreeNode<T> node, String prefix) {
        if (node != null) {
            System.out.println(prefix + node.value);
            if(node.children!=null) {
                for (_NaryTreeNode<T> child : node.children) {
                    printTree(child, prefix + "--");
                }
            }
        }
    }
}

 测试验证

/**
 * @author zhouxx
 * @create 2025-01-11 13:46
 */
public class _NaryTreeTest {
    public static void main(String[] args) {
        _NaryTree<Integer> tree = new _NaryTree(1);
        _NaryTreeNode<Integer> node2 = new _NaryTreeNode(2);
        _NaryTreeNode<Integer> node3 = new _NaryTreeNode(3);
        _NaryTreeNode<Integer> node4 = new _NaryTreeNode(4);
        _NaryTreeNode<Integer> node5 = new _NaryTreeNode(5);
        _NaryTreeNode<Integer> node6 = new _NaryTreeNode(6);
        _NaryTreeNode<Integer> node7 = new _NaryTreeNode(7);
        tree.root.addChild(node2);
        tree.root.addChild(node3);
        node2.addChild(node4);
        node2.addChild(node5);
        node2.addChild(node6);
        node6.addChild(node7);

        System.out.println("Before deletion:");
        tree.printTree(tree.root, "");
        tree.remove(2);
        System.out.println("After deletion:");
        tree.printTree(tree.root, "");


        Integer[] array1={1,2,3,4,5,6,7,8,9,10};
        _NaryTree<Integer> tree2 = new _NaryTree();
        tree2.buildTree(array1);
        tree2.printTree(tree2.root, "-");
        tree2.root.children.add(new _NaryTreeNode(11));
        _NaryTreeNode<Integer> tmpNode= (_NaryTreeNode<Integer>) tree2.root.children.get(2);
        tmpNode.children.add(new _NaryTreeNode(12));
        tree2.root.children.add(tmpNode);
        _NaryTreeNode<Integer> node71 = new _NaryTreeNode(71);
        node71.addChild(tmpNode);
        tree2.root.children.add(node71);
        tree2.printTree(tree2.root, "-");
        tree2.root.addChild(node71);
        tree2.printTree(tree2.root, "-");

    }
}

找到值的节点,将其整个删除

N叉树添加节点

Before deletion:
1
--2
----4
----5
----6
------7
--3
Node with value 2 was deleted.
After deletion:
1
--null
--3
-1
---2
---3
---4
---5
---6
---7
---8
---9
---10
-1
---2
---3
---4
-----12
---5
---6
---7
---8
---9
---10
---11
---4
-----12
---71
-----4
-------12
-1
---2
---3
---4
-----12
---5
---6
---7
---8
---9
---10
---11
---4
-----12
---71
-----4
-------12
---71
-----4
-------12

Process finished with exit code 0

至此4叉树和N叉树都已经讲解完毕,重点是方法中的注解,可以帮助更好的理解数这个数据结构和其操作。不足的地方,欢迎留言交流指正。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐