【数据结构与算法】1.树、二叉树、字典树、红黑树

news2024/10/1 7:45:33

文章目录

  • 简介
  • 1.树 (Tree)
  • 2.二叉树(Binary Tree)
    • 2.1.二叉树数据结构
    • 2.2.二叉树的三种遍历方式
  • 3.二叉查找树(Binary Search Tree)
    • 3.1.二叉查找树的概念和定义
    • 3.2.二分查找算法
  • 4.字典树(Trie)
  • 5.红黑树(Red-Black Tree)

简介

      本章主要讲解一些树的基本概念,二叉树的三种打印方式,二叉查找树和字典树的应用,红黑树的概念。

1.树 (Tree)

      像这种递归结构数据类型叫做树,例如后台管理系统里菜单存储数据结构就是这种,树的节点包含: 根节点、父节点、子节点、兄弟节点、叶子节点。
      在下面这幅图,E节点是根节点,A节点就是B节点的父节点,B节点是A节点的子节点。B、C、D这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点。我们把没有父节点的节点叫作根节点,也就是图中的节点E。我们把没有子节点的节点叫作叶子节点或者叶节点,比如图中的G、H、I、J、K、L都是叶子节点
在这里插入图片描述
关于“树”,还有三个比较相似的概念:高度(Height)、深度(Depth)、层(Level)。

  • 节点的高度:节点到最底层子节点的最长路径
  • 节点的深度:根节点到这个节点所经历的的个数
  • 节点的高度:节点的深度+1

如上图节点对应高度、深度、层如下表

节点名称高度深度
E301
A212
B123
G034

      在我们的生活中,“高度”这个概念,其实就是从下往上度量,比如我们要度量第10层楼的高度、第13层楼的高度,起点都是地面。所以,树这种数据结构的高度也是一样,从最底层开始计数,并且计数的起点是0。
      “深度”这个概念在生活中是从上往下度量的,比如水中鱼的深度,是从水平面开始度量的。所以,树这种数据结构的深度也是类似的,从根结点开始度量,并且计数起点也是0。
      “层数”跟深度的计算类似,不过,计数起点是1,也就是说根节点的位于第1层。

2.二叉树(Binary Tree)

2.1.二叉树数据结构

      二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只有左子节点,有的节点只有右子节点。我画的这几个都是二叉树。以此类推,你可以想象一下四叉树、等长什么样子。
在这里插入图片描述
这上图中有两个比较特殊的二叉树,分别是编号2和编号3这两个。

  • 编号2的二叉树中,叶子节点全都在最底层,除了叶子节点之外,每个节点都有左右两个子节点,这种二叉树就叫作满二叉树
  • 编号3的二叉树中,叶子节点都在最底下两层,最后一层的叶子节点都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大,这种二叉树叫作完全二叉树

下图左边的树由于叶子节点靠右部不符合完全二叉树的特性要求,右图因为非叶子节点没有达到最大(2个)所以也不符合。

在这里插入图片描述
java定义树节点

@Data
public class TreeNode<T> {
        /*
         * 节点存储的值Val
         */
        private T data;
        /*
         * 子节点左边的数据
         */
        private TreeNode<T> left;
        /*
         * 子节点右边的数据
         */
        private TreeNode<T> right;
		
        public TreeNode(T data) {
            this.data = data;
        }
}

2.2.二叉树的三种遍历方式

      将所有节点都打印出来的方法有三种,前序遍历、中序遍历和后序遍历。其中,前、中、后序,表示的是节点与它的左右子树节点遍历打印的先后顺序。

  • 前序遍历: 对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。
  • 中序遍历: 对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。
  • 后序遍历: 对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身。

在这里插入图片描述
前、中、后序遍历的递推公式都写出来。

  • 前序遍历的递推公式:preOrderPrint( r ) = print r->preOrderPrint(r->left)->preOrderPrint(r->right)
  • 中序遍历的递推公式:inOrderPrint( r ) = inOrderPrint(r->left)->print r->inOrderPrint(r->right)
  • 后序遍历的递推公式:postOrderPrint( r ) = postOrderPrint(r->left)->postOrderPrint(r->right)->print r

以下是java实现对二叉树的三种打印实现

    /**
     * 前序遍历 对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树
     * 13 10 9 11 16 14 15
     */
    public static void preOrderPrint(TreeNode root) {
        if (root != null) {
            System.out.print(root.getData() + " ");
            preOrderPrint(root.getLeft());
            preOrderPrint(root.getRight());
        }
    }
    /**
     * 中序遍历 对于树中的任意节点来说,先打印它的左子树,然后再打印它的本身,最后打印它的右子树。
     * 9 10 11 13 14 16 15
     */
    public static void inOrderPrint(TreeNode root) {
        if (root != null) {
            inOrderPrint(root.getLeft());
            System.out.print(root.getData() + " ");
            inOrderPrint(root.getRight());
        }
    }

    /**
     * 后序遍历  对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印它本身。
     * 9 11 10 14 15 16 13 后序打印
     */
    public static void postOrderPrint(TreeNode root) {
        if (root != null) {
            postOrderPrint(root.getLeft());
            postOrderPrint(root.getRight());
            System.out.print(root.getData() + " ");
        }
    }
     	/**
     *        13
     *   10        16
     * 9   11    14   15
     */
    public static void main(String[] args) {
        System.out.println("             13\n" +
                "        10        16\n" +
                "      9   11    14   15");
        TreeNode<Integer> root = new TreeNode<>(13);
        TreeNode<Integer> left = new TreeNode(10);
        TreeNode<Integer> right = new TreeNode(16);
        root.setLeft(left);
        root.setRight(right);

        TreeNode<Integer> left1 = new TreeNode(9);
        TreeNode<Integer> right1 = new TreeNode(11);
        left.setLeft(left1);
        left.setRight(right1);

        TreeNode<Integer> left2 = new TreeNode(14);
        TreeNode<Integer> right2 = new TreeNode(15);
        right.setLeft(left2);
        right.setRight(right2);

        System.out.print("前序遍历:");
        preOrderPrint(root);
        System.out.println();
        System.out.print("中序遍历:");
        inOrderPrint(root);
        System.out.println();
        System.out.print("后续遍历:");
        postOrderPrint(root);
    }

3.二叉查找树(Binary Search Tree)

3.1.二叉查找树的概念和定义

      二叉查找树是二叉树中最常用的一种类型,也叫二叉搜索树。顾名思义,二叉查找树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。它是怎么做到这些的呢?
      这些都依赖于二叉查找树的特殊结构。二叉查找树特性要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。 二分查找算法就是通过这种特性在有序的数组实现快速查找某个数字。
从下图两个二叉查找树的例子就应该就清楚了。
在这里插入图片描述
      以下代码是Java对二叉查找树的基本功能实现,数据结构和2.1章节的二叉树一样,插入和查找都比较容易理解,只有删除节点涉及到调整数据结构所以会比较复杂。

@Data
public class BinarySearchTree {

    private TreeNode tree;

    public void preOrderedPrint(TreeNode root) {
        if (root != null) {
            System.out.print(root.data + " ");
            preOrderedPrint(root.left);
            preOrderedPrint(root.right);
        }
    }

    /**
     * 查找数据
     *
     * @param data
     * @return 返回node节点
     */
    public TreeNode search(int data) {
        TreeNode p = tree;
        while (p != null) {
            if (data < p.data) {
                p = p.left;
            } else if (data > p.data) {
                p = p.right;
            } else {
                return p;
            }
        }
        return null;
    }

    /**
     * 插入数据
     *
     * @param data
     */
    public void insert(int data) {
        //首次插入初始化树
        if (tree == null) {
            tree = new TreeNode(data);
            return;
        }
        //赋值给临时遍历
        TreeNode p = tree;
        while (p != null) {
            //判断插入的数据是否比根节点数据大,大则往右节点插入
            if (data > p.data) {
                if (p.right == null) {
                    //初始化新节点数据并赋值给当前节点的右索引
                    p.right = new TreeNode(data);
                    return;
                }
                //如果不为空,则遍历子树一直找到合适的位置位置
                p = p.right;
            } else {
                if (p.left == null) {
                    //初始化新节点数据并赋值给当前节点的左索引
                    p.left = new TreeNode(data);
                    return;
                }
                //如果不为空,则遍历子树一直找到合适的位置位置
                p = p.left;
            }
        }
    }


    /**
     * 删除数据
     *
     * @param data
     */
    public void delete(int data) {
        // curNode指向要删除的节点,初始化指向根节点
        TreeNode curTreeNode = tree;
        // parentNode记录的是p的父节点
        TreeNode parentTreeNode = null;

        //查找数据是否存在
        while (curTreeNode != null && curTreeNode.data != data) {
            parentTreeNode = curTreeNode;
            if (data > curTreeNode.data) {
                //如果查找的数据大于当前数据,则往右边查找
                curTreeNode = curTreeNode.right;
            } else {
                //如果查找的数据小于当前数据,则往左边查找
                curTreeNode = curTreeNode.left;
            }
        }

        if (curTreeNode == null) {
            // 没有找到
            return;
        }

        // 要删除的节点有两个子节点
        if (curTreeNode.left != null && curTreeNode.right != null) {
            // 查找右子树中最小节点
            TreeNode minTreeNode = curTreeNode.right;
            // minParent表示minP的父节点
            TreeNode minParent = curTreeNode;
            while (minTreeNode.left != null) {
                //调整节点位置
                minParent = minTreeNode;
                minTreeNode = minTreeNode.left;
            }
            // 将minNode的数据替换到curNode中
            curTreeNode.data = minTreeNode.data;
            // 下面就变成了删除minParent了
            curTreeNode = minTreeNode;
            parentTreeNode = minParent;
        }

        // 删除节点是叶子节点或者仅有一个子节点
        TreeNode child; // p的子节点
        if (curTreeNode.left != null) {
            child = curTreeNode.left;
        } else if (curTreeNode.right != null) {
            child = curTreeNode.right;
        } else {
            child = null;
        }
        if (parentTreeNode == null) {
            // 删除的是根节点
            tree = child;
        } else if (parentTreeNode.left == curTreeNode) {
            parentTreeNode.left = child;
        } else {
            parentTreeNode.right = child;
        }
    }

    @Data
    public class TreeNode {
        /*
         * 节点存储的值Val
         */
        private int data;

        /*
         * 子节点左边的数据
         */
        private TreeNode left;

        /*
         * 子节点右边的数据
         */
        private TreeNode right;

        public TreeNode(int data) {
            this.data = data;
        }

    }

3.2.二分查找算法

      二分查找是一种非常高效的查找算法,高效到什么程度呢?我们来分析一下它的时间复杂度。
我们假设数据大小是n,每次查找后数据都会缩小为原来的一半,也就是会除以2。最坏情况下,直到查找区间被缩小为空,才停止。
在这里插入图片描述
      可以看出来,这是一个等比数列。其中n/2k=1时,k的值就是总共缩小的次数。而每一次缩小操作只涉及两个数据的大小比较,所以,经过了k次区间缩小操作,时
间复杂度就是O(k)。通过n/2k=1,我们可以求得k=log2n,所以时间复杂度就是O(logn)。

由此可见章节3.1的二叉查找树的查询效率是非常高的,时间复杂度和二分查找算法一致。

 /**
     * 使用二分查找数据
     * @param nums 原数据
     * @param find 查找的数字
     */
    public static Integer search(int nums[], int find) {
        //设置标志位,区分是否查找成功
        int index = -1;
        //统计比较次数
        int count = 0;
        //右偏移的数
        int low = 0;
        //中位数
        int mid;
        //范围
        int high = nums.length;
        while (low <= high) {
            count++;
            mid = (low + high) / 2;
            //防止出现没找到这个数出现下标越界
            if (mid > nums.length) {
                break;
            }
            System.out.println("第" + count + "次查找,mid=" + mid + ",high" + high + ",low" + low);
            if (find == nums[mid]) {
                index = mid;
                System.out.println("找到了," + find + "在数组的第" + mid + "下标");
                break;
            } else if (find > nums[mid]) {
                //如果要找的数大于中位数,在中位数右边,则设置右偏移的值=中位数+1
                low = mid + 1;
            } else {
                //如果要找的数小于中位数,在中位数左边,则设置范围=中位数-1
                high = mid - 1;
            }
        }
        if (index != -1) {
            System.out.println("找到了,下标为" + index);
        } else {
            System.out.println("没有找到这个数" + find);
        }
        return index;
    }

调用示例如下:

    public static void main(String[] args) {
        //需要查找的数据
        int find = 10;
        int[] nums = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
        System.out.println(search(nums, find));
    }

在这里插入图片描述
基于递归方式的实现二分查找如下

    /**
     * 二分查找 递归实现版本
     */
    public static int dsearch(int[] nums, int find) {
        return dsearch(nums, 0, nums.length - 1, find);
    }

    /**
     * @param nums 数据
     * @param low 右偏移的数
     * @param high 查找的范围
     * @param find 查找的数字
     * @return
     */
    private static int dsearch(int[] nums, int low, int high, int find) {
        if (low > high) {
            //偏移量大于范围终止递归查询
            return -1;
        }
         //计算出中位数   >>1 相当于num除以2 
        int mid = low + ((high - low) >> 1);
        if (nums[mid] == find) {
            return mid;
        } else if (nums[mid] < find) {
            return dsearch(nums, mid + 1, high, find);
        } else {
            return dsearch(nums, low, mid - 1, find);
        }
    }

4.字典树(Trie)

      Trie树,也叫“字典树”。顾名思义,它是一个树形结构。它是一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题。
      举个简单的例子来说明一下。我们有6个字符串,它们分别是:how,hi,her,hello,so,see。我们希望在里面多次查找某个字符串是否存在。如果每次查找,都是拿要查找的字符串跟这6个字符串依次进行字符串匹配,那效率就比较低,有没有更高效的方法呢?

      这个时候,我们就可以先对这6个字符串做一下预处理,组织成Trie树的结构,之后每次查找,都是在Trie树中进行匹配查找。Trie树的本质,就是利用字符串之间的公共前缀,将重复的前缀合并在一起。最后构造出来的就是下面这个图中的样子。

在这里插入图片描述
字典树的数据存储过程如下:
在这里插入图片描述

像百度查询中输入前缀自动补全的功能就是基于字典树特性实现的。
在这里插入图片描述
Java中实现字典树

public class Trie {

    /**
     * 根节点
     */
    private TrieNode root = new TrieNode();

    /**
     * 往Trie树中插入一个字符串
     *
     * @param str 字符串
     */
    public void insert(String str) {
        char[] text = str.toCharArray();
        TrieNode node = root;
        for (int i = 0; i < text.length; ++i) {
            char index = text[i];
            if (!node.map.containsKey(index)) {
                node.map.put(index, new TrieNode());
            }
            node = node.map.get(index);
        }
        node.end = true;
    }

    /**
     * 查找字符
     *
     * @param patternStr 匹配字符
     * @return
     */
    public boolean search(String patternStr) {
        return find(patternStr, false);
    }

    /**
     * 根据前缀查找是否存在
     *
     * @param prefix 匹配字符前缀
     * @return
     */
    public boolean startsWith(String prefix) {
        return find(prefix, true);
    }

    /**
     * 查找字符串是否存在
     *
     * @param patternStr 匹配字符
     * @param prefix     是否前缀匹配
     * @return 结果
     */
    private boolean find(String patternStr, boolean prefix) {
        TrieNode node = root;
        char[] pattern = patternStr.toCharArray();
        for (int i = 0; i < pattern.length; ++i) {
            char index = pattern[i];
            if (!node.map.containsKey(index)) {
                return false;
            }
            node = node.map.get(index);
        }
        if (prefix) {
            return true;
        }
        return node.end;
    }

    /**
     * 单词补齐
     *
     * @param patternStr
     * @return
     */
    public List<String> wordFill(String patternStr) {
        TrieNode node = root;
        char[] pattern = patternStr.toCharArray();
        char index;
        List<String> list = new ArrayList();
        StringBuffer sb = new StringBuffer();

        // 找到字符串末字符在字典树中的位置
        for (int i = 0, j = pattern.length; i < j; i++) {
            index = pattern[i];
            if (node.map.containsKey(index)) {
                sb.append(index);
                node = node.map.get(index);
            } else {
                return null;
            }
        }
        scanFind(node, String.valueOf(sb), list);
        return list;
    }


    /**
     * 子树单词补全
     * @param node   当前节点
     * @param prefix 前缀词
     * @param list   补词后的数据
     */
    private void scanFind(TrieNode node, String prefix, List<String> list) {
        for (Map.Entry<Character, TrieNode> entry : node.map.entrySet()) {
            list.add(prefix + entry.getKey());
            scanFind(entry.getValue(), prefix, list);
        }
    }

    @Data
    public class TrieNode {
        /**
         * 存储字典树
         */
        Map<Character, TrieNode> map;
        /**
         * 是否结尾字符
         */
        boolean end;
        public TrieNode() {
            map = new HashMap<>();
        }
    }

测试字典树的前缀查找、完整查找、单词补全功能

    public static void main(String[] args) {
        Trie tree = new Trie();
        tree.insert("ILove");
        tree.insert("ILoveYou");
        //完整查找
        System.out.println(tree.search("ILoveYo"));
        //前缀查找
        System.out.println(tree.startsWith("ILove"));
        //单词补全
        System.out.println(JSONObject.toJSONString(tree.wordFill("ILove")));
    }

在这里插入图片描述

5.红黑树(Red-Black Tree)

      章节3讲的二叉查找树是最常用的一种二叉树,它支持快速插入、删除、查找操作,各个操作的时间复杂度跟树的高度成
正比,理想情况下,时间复杂度是O(logn)。不过,二叉查找树在频繁的动态更新过程中,可能会出现树的高度远大于log2n的情况,从而导致各个操作的效率下降。极端情况下,二叉树会退化为链表,时间复杂度会退化到O(n)。我上一节说了,要解决这个复杂度退化的问题,我们需要一种平衡二叉查找树,其典型的代表是红黑树,在Jdk1.8里的HashMap里put数据时候出现hash冲突的时候会把数据会存放在链表里,当链表长度大于8的时候会把数据转存储到红黑树里。
在这里插入图片描述

在这里插入图片描述

什么是“平衡二叉查找树”?
      平衡二叉树的严格定义是这样的:二叉树中任意一个节点的左右子树的高度相差不能大于1。从这个定义来看,完全二叉树、满二叉树其实都是平衡二叉树,但是非完全二叉树也有可能是平衡二叉树。
在这里插入图片描述
      平衡二叉查找树不仅满足上面平衡二叉树的定义,还满足二叉查找树的特点。最先被发明的平衡二叉查找树是AVL树,它严格符合我刚讲到的平衡二叉查找树的定义,即任何节点的左右子树高度相差不超过1,是一种高度平衡的二叉查找树。但是很多平衡二叉查找树其实并没有严格符合上面的定义(树中任意一个节点的左右子树的高度相差不能大于1),比如此章讲的红黑树,它从根节点到各个叶子节点的最长路径,有可能会比最短路径大一倍。

顾名思义,红黑树中的节点,一类被标记为黑色,一类被标记为红色。除此之外,一棵红黑树还需要满足这样几个要求:

  • 根节点是黑色的;
  • 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据;
  • 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的;
  • 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;

如下都符合红黑树的定义
在这里插入图片描述
Java中HashMap里定义的红黑树数据结构如下,可以看到有一个字段用来标识节点是红色还是黑色。
在这里插入图片描述

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

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

相关文章

windows10/11,傻瓜式安装pytorch(gpu),在虚拟环境anaconda

安装anaconda地址 &#xff1a;Anaconda | The Worlds Most Popular Data Science Platform安装选项全默认点击next就行。查看支持cuda版本cmd命令行输入nvidia-smi。下图右上角显示11.6为支持的cuda版本。要是显示没有nvidia-smi命令。得安装nvidia驱动&#xff0c;一般情况都…

字符串匹配 - Overview

字符串匹配(String Matchiing)也称字符串搜索(String Searching)是字符串算法中重要的一种&#xff0c;是指从一个大字符串或文本中找到模式串出现的位置。字符串匹配概念字符串匹配问题的形式定义&#xff1a;文本&#xff08;Text&#xff09;是一个长度为 n 的数组 T[1..n]&…

Nodejs的安装

1. Nodejs的真正用途 a. 一个javascirpt的运行环境 b. 运行在服务器&#xff0c;作为web server c. 运行在本地&#xff0c;作为打包&#xff0c;构建工具 2. Nodejs的下载和安装 a. 普通方式&#xff08;访问官网&#xff09; 下载对应系统版本即可&#xff08;个人学习可无需…

【数据结构与算法分析】介绍蛮力法以及相关程序案例

文章目录蛮力法之排序选择排序冒泡排序实际应用蛮力法之最近对和凸包问题最近对问题凸包问题蛮力法(brute force)&#xff0c;其本质跟咱常说的暴力法是一样的&#xff0c;都是一种简单直接地解决问题的方法&#xff0c;通常直接基于问题的描述和所涉及的概念定义进行求解。 蛮…

【嵌入式】HC32F460串口接收超时中断+DMA

一 项目背景 项目需要使用一款UART串口编码器&#xff0c;编码器的数据以波特率57600持续向外发送。但这组数据包没有固定的包头和校验尾&#xff0c;仅仅是由多圈圈数和单圈角度组成的六字节数据码&#xff0c;这样接收到的数组无法确定实际的下标&#xff0c;所以这边考虑用串…

8月起,《PMBOK®指南(第七版)》将被采用,考PMP的注意了!

PMP第七版教材采用时间定了&#xff01;&#xff01;&#xff01;2023年【8月开始】第一次使用第七版教材&#xff0c;通知明显指出&#xff0c;第六版的关键知识任然还是有效的。第七版做的调整还是蛮大的&#xff0c;首次提出了项目管理的 12 项原则和8个项目绩效域&#xff…

Java基础语法小结来啦

简单的来说&#xff0c;一个java的程序他是有一系列对象的集合组成&#xff0c;通过对这些对象相互间调用的方式协同工作&#xff0c;下面就是我有关于Java基础语法的一些小结。 一、return简单使用 下面来一个Java程序&#xff0c;表示的是在self1这个包中我们创建了一个名叫…

Skywalking ui页面功能介绍

菜单栏 仪表盘&#xff1a;查看被监控服务的运行状态&#xff1b; 拓扑图&#xff1a;以拓扑图的方式展现服务之间的关系&#xff0c;并以此为入口查看相关信息&#xff1b; 追踪&#xff1a;以接口列表的方式展现&#xff0c;追踪接口内部调用过程&#xff1b; 性能剖析&am…

GEE学习笔记 八十:批量下载影像

最近问如何批量导出集合的小伙伴非常多&#xff0c;一个一个回复太麻烦&#xff0c;我这里直接给一段例子代码吧&#xff1a; var l8 ee.ImageCollection("LANDSAT/LC08/C01/T1_SR"); var roi /* color: #d63000 */ee.Geometry.Polygon( [[[115.64960937…

从0到1一步一步玩转openEuler--17 openEuler DNF(YUM)检查更新

文章目录17.1 检查更新17.2 升级17.3 更新所有的包和它们的依赖DNF是一款Linux软件包管理工具&#xff0c;用于管理RPM软件包。DNF可以查询软件包信息&#xff0c;从指定软件库获取软件包&#xff0c;自动处理依赖关系以安装或卸载软件包&#xff0c;以及更新系统到最新可用版本…

Nacos框架服务注册发现和配置中心原理

文章目录1.简介2.整体架构和原理2.1 服务发现注册原理2.1.1 注册和拉取数据2.1.2 Server集群一致性2.1.3 健康检查2.2 配置中心原理2.2.1 支持功能和资源模型2.2.2 server集群数据一致性问题2.2.3 client和server的通信监听改动方式2.2.4 client拉取数据2.2.5 client请求server…

kubernetes教程 --Pod生命周期

Pod生命周期 pod创建过程运行初始化容器&#xff08;init container&#xff09;过程运行主容器&#xff08;main container&#xff09;过程 容器启动后钩子&#xff08;post start&#xff09;、容器终止前钩子&#xff08;pre stop&#xff09;容器的存活性探测&#xff08;…

利用设计模式、反射写代码

软件工程师和码农最大的区别就是平时写代码时习惯问题&#xff0c;码农很喜欢写重复代码而软件工程师会利用各种技巧去干掉重复的冗余代码。 业务同学抱怨业务开发没有技术含量&#xff0c;用不到设计模式、Java 高级特性、OOP&#xff0c;平时写代码都在堆 CRUD&#xff0c;个…

网站项目部署在k8s案例与Jenkins自动化发布项目(CI/CD)

在K8s平台部署项目流程 在K8s平台部署Java网站项目 制作镜像流程 第一步&#xff1a;制作镜像 使用镜像仓库&#xff08;私有仓库、公共仓库&#xff09;&#xff1a; 1、配置可信任&#xff08;如果仓库是HTTPS访问不用配置&#xff09; # vi /etc/docker/daemon.json { "…

matlab 简单的水轮机系统的模糊pid控制仿真

1、内容简介略641-可以交流、咨询、答疑2、内容说明模糊介绍&#xff1a;Matlab4.2以后的版本中推出的模糊工具箱(Fuzzy Toolbox)&#xff0c;为仿真模糊控制系统提供了很大的方便。 在Simulink环境下对PID控制系统进行建模是非常方便的&#xff0c;而模糊控制系统与PID控制系统…

DataFrame 循环处理效率的记录

几种工具的处理效率比较&#xff1a; 每次循环都使用复杂的操作尽可能拆分成向量化操作&#xff0c;也可转为numpy&#xff0c;再用numba加速。 对 DataFrame 中的数据做循环处理的效率&#xff1a; 方法一&#xff1a;下标循环 for i in range(len(df)): if df.iloc[i][…

GEE学习笔记 七十七:GEE学习方法简介

这是一篇关于学习方法的思考探索&#xff0c;当然我不会大篇文章介绍什么学习方法&#xff08;因为我也不是这方面的专家?&#xff09;&#xff0c;这个只是总结一下我是如何学习GEE以及在学习中遇到问题时如何解决问题的。我写这篇文章的目的就是在和一些学习GEE的新同学接触…

Stable diffusion扩散模型相关原理

时隔两年半&#xff08;2年4个月&#xff09;&#xff0c;我又回来研究生成技术了。以前学习研究GAN没结果&#xff0c;不管是技术上&#xff0c;还是应用产品上&#xff0c;结果就放弃了&#xff0c;现在基于diffusion的技术又把生成技术带上了一个新的高度。现在自己又来研究…

一款好的低代码开发平台应该是什么样?

一款好的低代码开发平台应该是什么样&#xff1f; 以企业级应用构建来讲&#xff0c;完成一个应用复杂度随着技术的进步、需求的细化、业务要求的变化并不是逐渐降低&#xff0c;而是逐渐提升。用户想要有更好的体验&#xff0c;复杂度更是成倍提升。 基于此&#xff0c;低代码…

【机器学习】Sklearn 集成学习-投票分类器(VoteClassifier)

前言 在【机器学习】集成学习基础概念介绍中有提到过&#xff0c;集成学习的结合策略包括&#xff1a; 平均法、投票法和学习法。sklearn.ensemble库中的包含投票分类器(Voting Classifier) 和投票回归器&#xff08;Voting Regressor)&#xff0c;分别对回归任务和分类任务的…