Java数据结构学习——排序二叉树

目录

前言

正文

排序二叉树的特点

插入节点

删除树节点

删除的节点是叶子节点

删除的节点存在左或者右子节点

删除的节点存在左右两子节点

遍历节点

先序遍历

中序遍历

后续遍历

全部代码展示

总结


前言

由于本人的数据结构和算法能力比较薄弱,不管是在面试还是对于代码的一个性能提升都是少不过数据结构和算法的支撑,所以有断断续续的学习,此专栏就记录一下自己的学习过程和成果把!对大家不一定有帮助,更希望如果有错误之处希望能给我提出来!

正文

排序二叉树的特点

  1. 一个父节点只能有左右两个子节点。
  2. 左节点的值比父节点要小。
  3. 右节点的值要比父节点要大。

插入节点

从排序二叉树的特点我们只要一个父节点只能有2个子节点,并且左小右大,所以我们来一串数据可以自己先手动排序理清思路。[6,1,4,2,10,-1,15,8];

手绘排序二叉树

 建议大家也写一串数字,然后自己手绘一把理清思绪好为写代码做铺垫

代码如下:

// 根节点
private TreeNode rootNode;

public TreeNode getRootNode() {
    return rootNode;
}

public void insertNode(TreeNode node){

    // 从根节点开始遍历新增的位置,
    // 左右节点有为null的就可以直接插入
    // // 判断根节点是否存在,不存在当前节点就是根节点
    if(rootNode==null){   
        rootNode = node;
        rootNode.setLefe(null);
        rootNode.setRight(null);

    // 这里就是代表根节点存在,就开始往下找
    }else{    

        // 获取到根节点作为循环最开始父节点
        // 后续在循环中一直作为父节点,可变参数
        TreeNode currentNode = rootNode;  
        
        // 循环判断新来的节点是小于还是大于父节点,看怎么走分支
        while(true){

            // 走当前父节点右边
            // 判断条件就是新来的节点的值是否小于当前父节点
            if(node.getValue()< currentNode.getValue()){

                // 为null就直接放
                if(currentNode.getLefe()==null){
                    currentNode.setLefe(node);
                    return;
                }

                // 不为null就代表还有子节点,继续把当前父节点的左节点作为下次循环的父节点
                currentNode = currentNode.getLefe();

            // 走当前父节点的右边
            // 判断条件就是新来的节点的值是否大于当前父节点
            }else if (node.getValue()> currentNode.getValue()){      

                // 为null就直接放
                if(currentNode.getRight()==null){
                    currentNode.setRight(node);
                    return;
                }

                // 不为null就代表还有子节点,继续把当前父节点的右节点作为下次循环的父节点
                currentNode = currentNode.getRight();

            // 大于和小于都判断过了,所以进入到这里就达标重复了
            // 重复的逻辑自己定义,可以使替换之类的...   我这里就不能重复了
            }else{
                System.out.println("当前节点已经存在于树中");
                return;
            }
        }
    }
}


// 树节点的结构
class TreeNode{
    private int value;
    private TreeNode lefe;
    private TreeNode right;


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

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public TreeNode getLefe() {
        return lefe;
    }

    public void setLefe(TreeNode lefe) {
        this.lefe = lefe;
    }

    public TreeNode getRight() {
        return right;
    }

    public void setRight(TreeNode right) {
        this.right = right;
    }
}

详细讲解看代码块的注释,每行的注释都是讲解的特别仔细。

插入的逻辑比较容易,就是新来的节点一直和当前的父节点做大小的比较走不同的分支,直到当前的父节点的左或右的子节点为null就可以直接添加。

 

删除树节点

对于排序二叉树来说删除节点大致可以分为

  1. 删除的节点是叶子节点,也就是删除的节点不存在左右子节点。
  2. 删除的节点存在左子节点。
  3. 删除的节点存在右子节点。
  4. 删除的节点存在左右两子节点。

删除的节点是叶子节点

什么是叶子节点? 

那我们该如何删除呢?

这里我们需要根据value值找到删除的叶子节点和叶子节点的父节点。

为什么要找到父节点呢?因为我们需要通过父节点来找到删除节点是在左边还是右边,并且将删除节点的父节点的left或者right指向为null,help GC来回收删除节点。

帮助删除方法——根据删除的value找到删除的节点

    // 删除方法的帮助方法——根据删除的value查找到删除的节点
    private TreeNode selectNode(int value){

        TreeNode currentNode = rootNode;

        // 如果root节点为null就直接返回null
        // 如果root节点的left和right都为null就直接返回null
        // 冗余代码,因为查询删除节点和查询删除节点的父节点都要查询。可以只在调用的地方使用一次
//        if (currentNode==null) {
//            return null;
//        }

        while(true) {

            if(currentNode.getLefe()==null && currentNode.getRight()==null){
                return null;
            }

            // 如果查找的值小于
            if(currentNode.getLefe()!=null && value < currentNode.getValue()) {

                if(currentNode.getLefe().getValue()==value){
                    return currentNode.getLefe();
                }
                currentNode = currentNode.getLefe();

            }else if(currentNode.getRight()!=null&& value>currentNode.getValue()){          // 如果查找的值大于

                if(currentNode.getRight().getValue()==value){
                    return currentNode.getRight();
                }
                currentNode = currentNode.getRight();
            }
        }
    }

帮助删除方法——根据删除的value找到删除节点的父节点 


 // 删除方法的帮助方法——根据删除的value查找到删除的节点的父节点
    private TreeNode selectParentNode(int value) {

        TreeNode currentNode = rootNode;

        // rootNode判空代码在调用此方法之前要做,这里不用判断可以避免没必要的冗余

        // 这种情况就是只有rootNode节点。
        if(currentNode.getLefe()==null && currentNode.getRight()==null){
            return currentNode;
        }

        while (true){
            // 不用考虑线程安全的问题的话
            // 先调用查找删除节点的方法就保证了有父节点,所以这里对节点的判空也可以避免
            if(currentNode.getLefe()!=null && value<currentNode.getValue()){
                if (currentNode.getLefe().getValue()==value) {
                    // 这里是返回父节点
                    return currentNode;
                }
                currentNode = currentNode.getLefe();
            }else if (currentNode.getRight()!=null && value>currentNode.getValue()){
                if (currentNode.getRight().getValue()==value) {
                    // 这里是返回父节点
                    return currentNode;
                }
                currentNode = currentNode.getRight();
            }
        }
    }

具体的删除代码在后面的删除的节点存在左右两字节点

删除的节点存在左或者右子节点

这种删除,我们就直接让左或者右子节点来代替删除节点就行了。

但是我们要考虑是左还是又子节点,并且还是考虑删除节点是在父节点的左子节点还是右子节点

具体的删除代码在后面的删除的节点存在左右两字节点

删除的节点存在左右两子节点

这种就比较复杂了,因为左右子节点都存在,这种情况,我们就要把当前删除节点的右子节点当做一个树,找到树中最小的节点,也就是最左边节点。找到后我们需要把最小节点来代替删除节点。并且这种情况也存在删除的右子节点的左子节点不存在,这种情况就直接把删除的右子节点来代替删除的节点,所以我们需要找到右子节点中最小节点和最小节点的父节点,找父节点的方法前面已经展示了。

帮助删除方法——根据删除的value找到删除节点的父节点 

    // 帮助删除——删除节点存在两个左右子节点的情况
    // 找右子树的最小节点
    private TreeNode selectRightMinimum(TreeNode node){


        TreeNode mixLeft = node.getRight().getLefe();

        // 冗余代码,此代码应该是调用此方法者处理。
//        if(mixLeft ==null){
//            return node.getRight();
//        }

        while(true){

            if(mixLeft.getLefe()==null){
                return mixLeft;
            }
            mixLeft = mixLeft.getLefe();
        }
    }

所以整体的删除代码如下:

    // 删除节点
    public void deleteNode(int value){

        // 因为selectNode和selectParentNode没有对root节点做判断,所以这里做,也减少了代码的冗余
        if(rootNode==null){
            System.out.println("删除失败,树还没任何节点");
            return;
        }

        // 这个代表只有根节点
        if (rootNode.getValue()==value && rootNode.getRight()==null && rootNode.getLefe()==null) {

            // 直接把根节点
            rootNode = null;
            return;
        }

        TreeNode targetNode = this.selectNode(value);

        // 这个代表删除的值在树中没得
        if (targetNode== null) {
            System.out.println("您删除的内容树中没有");
            return;
        }

        // 获取到删除节点的父节点,因为要靠他找到删除节点是左边还是右边,并且帮助GC回收删除节点
        TreeNode parentNode = this.selectParentNode(value);


        // 下面就是删除的具体逻辑代码了
        // 这里需要分情况讨论了
        // 可能是叶子节点,就直接把当前节点给删除,其他操作都不需要做
        if(targetNode.getLefe()==null && targetNode.getRight()==null){

            // 但是我们并不知道targetNode是parentNode的左还是右子节点,所以需要if判断
            if(parentNode.getLefe().getValue()==value){
                parentNode.setLefe(null);
            }else{
                parentNode.setRight(null);
            }

        // 这种情况就是处理删除节点存在左子节点,右子节点不存在
        // 但是我们并不知道删除节点是在父节点的左边还是右边, 所以下面还有对方法做判断
        }else if(targetNode.getLefe()!=null && targetNode.getRight()==null){

            // 删除节点在parent节点的左边
            if(parentNode.getLefe().getValue()==value){

                // 将删除节点的左边节点赋值给父节点的左边节点
                parentNode.setLefe(targetNode.getLefe());

              
            }else{  // 这里代表删除节点在父节点的右边    
                
                // 将删除节点的左边节点赋值给父节点的左边节点
                parentNode.setRight(targetNode.getLefe());
            }


        // 这种情况就是处理删除节点存在右子节点,左子节点不存在
        // 但是我们并不知道删除节点是在父节点的左边还是右边, 所以下面还有对方法做判断
        }else if(targetNode.getLefe()==null && targetNode.getRight()!=null){

            // 删除节点在parent节点的左边
            if(parentNode.getLefe().getValue()==value){
                parentNode.setLefe(targetNode.getRight());
            }else{       // 删除节点在parent节点的右边
                parentNode.setRight(targetNode.getRight());
            }

        // 这种情况代表删除节点同时存在左右子节点
        }else{          

            // 这里代表右边节点作为树不存在左节点,所以直接处理右节点,
            // 也就是右节点树最小的就是右边第一个节点
            if (targetNode.getRight().getLefe()==null) {

                // 将删除节点的父节点的指向改变
                parentNode.setRight(targetNode.getRight());

                // 这里的getRight()获取到的节点就是删除节点的右子节点了
                parentNode.getRight().setLefe(targetNode.getLefe());
                return;
            }

            // 执行到这里就代表删除节点的右子节树存在左子节点
            // 所以我们就要通过我们前面编写好的帮助方法找到最小值
            // 改变指向的代码我就不注释了,这个大家要根据自己手绘图自己思考。
            TreeNode minimumNode = this.selectRightMinimum(targetNode);
            TreeNode minimumNodeParent = this.selectParentNode(minimumNode.getValue());
            minimumNodeParent.setLefe(null);  // help GC
            parentNode.setRight(minimumNode);
            parentNode.getRight().setLefe(targetNode.getLefe());
            parentNode.getRight().setRight(targetNode.getRight());
        }

    }

上面代码块的注释已经特别的仔细了,不过这里建议大家根据博主的代码+自己手绘图来逐行理解

方法的前半段我们做一些特殊的判断,之后就根据删除节点的子节点做一些判断并做出不同的操作

遍历节点

对于排序二叉树来说,遍历分为三种遍历。

先序:根->左->右

中序:左->根->右

后续:左-> 右 ->根

先序遍历

// 亲忽略我的中文式命名(如果不是英文差,谁又愿意呢...)
// 根->左->又
public void xianXu(TreeNode treeNode){

    // 递归的退出条件
    if(treeNode!=null){

        System.out.print(treeNode.getValue()+"\t\t");

        // 左边
        xianXu(treeNode.getLefe());

        // 右边
        xianXu(treeNode.getRight());
    }
}

对于递归的操作也不是文字能描述明白的,更多的是大家根据代码和输出结构再根据自己手绘的图来做一个理解,不过我这里还是画个图来给大家理解一下吧。

排序二叉树的先序遍历

  注:如果图理解起来比较晕看不懂的话,就自己画图,因为每个人的思想和思路不一样,不要被我的图打乱思绪

中序遍历

// 左->根->右
public void zhongXu(TreeNode treeNode){

    if(treeNode!=null){

        zhongXu(treeNode.getLefe());
        System.out.print(treeNode.getValue()+"\t\t");
        zhongXu(treeNode.getRight());
    }
}

 如果理解了先序遍历的思路,这里也不需要任何解释,一个递归的先后顺序变了,但是有没有发现中序遍历时从小到大的。建议大家使用手绘图来理解递归!

后续遍历

// 左-> 右 ->根
public void houXu(TreeNode treeNode){

    if(treeNode!=null){

        zhongXu(treeNode.getLefe());
        zhongXu(treeNode.getRight());
        System.out.print(treeNode.getValue()+"\t\t");
    }
}

  如果理解了先序遍历的思路,这里也不需要任何解释,就是递归的先后顺序变了。建议大家使用手绘图来理解递归!

全部代码展示

package tree.erchaTree;

import sun.reflect.generics.tree.Tree;

/**
 * @author liha
 * @version 1.0
 * @date 2022/3/8 11:18
 */
public class BinaryTree {

    // 根节点
    private TreeNode rootNode;

    public TreeNode getRootNode() {
        return rootNode;
    }

    @Override
    public String toString() {
        return "BinaryTree{" +
                "rootNode=" + rootNode +
                '}';
    }


    /**
     *@description 新增节点

     *@params
     * @param node

     *@return

     *@create_time  2022/3/8 11:25
     *@update_time  2022/3/8 11:25
     */
    public void insertNode(TreeNode node){

        // 从根节点开始遍历新增的位置,并且左右节点有为null的就可以直接插入
        if(rootNode==null){   // 判断根节点是否存在,不存在当前节点就是根节点

            rootNode = node;
            rootNode.setLefe(null);
            rootNode.setRight(null);
        }else{    // 这里就是代表根节点存在,就开始往下找

            TreeNode currentNode = rootNode;  // 最开始获取到根节点

            // 循环判断新来的节点是小于还是大于当前节点,看怎么走分支
            while(true){
                // 走左边
                if(node.getValue()< currentNode.getValue()){
                    // 为null就直接放
                    if(currentNode.getLefe()==null){
                        currentNode.setLefe(node);
                        return;
                    }

                    currentNode = currentNode.getLefe();
                }else if (node.getValue()> currentNode.getValue()){      // 走右边

                    // 为null就直接放
                    if(currentNode.getRight()==null){
                        currentNode.setRight(node);
                        return;
                    }
                    // 不为null就赋值继续自旋
                    currentNode = currentNode.getRight();
                }else{
                    System.out.println("当前节点已经存在于树中");
                    return;
                }
            }
        }
    }

    // 删除节点
    public void deleteNode(int value){

        // 因为selectNode和selectParentNode没有对root节点做判断,所以这里做,也减少了代码的冗余
        if(rootNode==null){
            System.out.println("删除失败,树还没任何节点");
            return;
        }

        // 这个代表只有根节点
        if (rootNode.getValue()==value && rootNode.getRight()==null && rootNode.getLefe()==null) {

            // 直接把根节点
            rootNode = null;
            return;
        }

        TreeNode targetNode = this.selectNode(value);

        // 这个代表删除的值在树中没得
        if (targetNode== null) {
            System.out.println("您删除的内容树中没有");
            return;
        }

        TreeNode parentNode = this.selectParentNode(value);


        // 这里需要分情况讨论了
        // 可能是叶子节点,就直接把当前节点给删除,其他操作都不需要做
        if(targetNode.getLefe()==null && targetNode.getRight()==null){
            // 但是我们并不知道targetNode是parentNode的左还是右子节点,所以需要if判断
            if(parentNode.getLefe().getValue()==value){
                parentNode.setLefe(null);
            }else{
                parentNode.setRight(null);
            }
        }else if(targetNode.getLefe()!=null && targetNode.getRight()==null){

            // 删除节点在parent节点的左边
            if(parentNode.getLefe().getValue()==value){
                parentNode.setLefe(targetNode.getLefe());
            }else{       // 删除节点在parent节点的右边
                parentNode.setRight(targetNode.getLefe());
            }

        }else if(targetNode.getLefe()==null && targetNode.getRight()!=null){

            // 删除节点在parent节点的左边
            if(parentNode.getLefe().getValue()==value){
                parentNode.setLefe(targetNode.getRight());
            }else{       // 删除节点在parent节点的右边
                parentNode.setRight(targetNode.getRight());
            }

        }else{          // 删除存在两个节点

            // 这里代表右边树不存在左节点,所以直接处理右节点,也就是右树最小的就是右边第一个父节点
            if (targetNode.getRight().getLefe()==null) {


                parentNode.setRight(targetNode.getRight());
                parentNode.getRight().setLefe(targetNode.getLefe());
                return;
            }

            // 执行到这里代表target节点子树存在左边最小值。
            // 就找到最小值
            TreeNode minimumNode = this.selectRightMinimum(targetNode);
            TreeNode minimumNodeParent = this.selectParentNode(minimumNode.getValue());
            minimumNodeParent.setLefe(null);  // help GC
            parentNode.setRight(minimumNode);
            parentNode.getRight().setLefe(targetNode.getLefe());
            parentNode.getRight().setRight(targetNode.getRight());
        }

    }

    // 帮助删除——删除节点存在两个左右子节点的情况
    // 找右子树的最小节点
    private TreeNode selectRightMinimum(TreeNode node){


        TreeNode mixLeft = node.getRight().getLefe();

        // 冗余代码,调用此方法者处理。
//        if(mixLeft ==null){
//            return node.getRight();
//        }

        while(true){

            if(mixLeft.getLefe()==null){
                return mixLeft;
            }
            mixLeft = mixLeft.getLefe();
        }
    }

    // 删除方法的帮助方法——根据删除的value查找到删除的节点
    private TreeNode selectNode(int value){

        TreeNode currentNode = rootNode;

        // 如果root节点为null就直接返回null
        // 如果root节点的left和right都为null就直接返回null
        // 冗余代码,因为查询删除节点和查询删除节点的父节点都要查询。可以只在调用的地方使用一次
//        if (currentNode==null) {
//            return null;
//        }

        while(true) {

            if(currentNode.getLefe()==null && currentNode.getRight()==null){
                return null;
            }

            // 如果查找的值小于
            if(currentNode.getLefe()!=null && value < currentNode.getValue()) {

                if(currentNode.getLefe().getValue()==value){
                    return currentNode.getLefe();
                }
                currentNode = currentNode.getLefe();

            }else if(currentNode.getRight()!=null&& value>currentNode.getValue()){          // 如果查找的值大于

                if(currentNode.getRight().getValue()==value){
                    return currentNode.getRight();
                }
                currentNode = currentNode.getRight();
            }
        }
    }

    // 删除方法的帮助方法——根据删除的value查找到删除的节点的父节点
    private TreeNode selectParentNode(int value) {

        TreeNode currentNode = rootNode;

        // rootNode判空代码在调用此方法之前要做,这里不用判断可以避免没必要的冗余

        // 这种情况就是只有rootNode节点。
        if(currentNode.getLefe()==null && currentNode.getRight()==null){
            return currentNode;
        }

        while (true){
            // 不用考虑线程安全的问题的话
            // 先调用查找删除节点的方法就保证了有父节点,所以这里对节点的判空也可以避免
            if(currentNode.getLefe()!=null && value<currentNode.getValue()){
                if (currentNode.getLefe().getValue()==value) {
                    // 这里是返回父节点
                    return currentNode;
                }
                currentNode = currentNode.getLefe();
            }else if (currentNode.getRight()!=null && value>currentNode.getValue()){
                if (currentNode.getRight().getValue()==value) {
                    // 这里是返回父节点
                    return currentNode;
                }
                currentNode = currentNode.getRight();
            }
        }
    }



    // 遍历的几种方式
    // 根->左->右
    // 左->根->右
    // 左->右->根

    // 根->左->又
    public void xianXu(TreeNode treeNode){

        // 递归的退出条件
        if(treeNode!=null){

            System.out.print(treeNode.getValue()+"\t\t");

            // 左边
            xianXu(treeNode.getLefe());

            // 右边
            xianXu(treeNode.getRight());
        }
    }


    // 左->根->右
    public void zhongXu(TreeNode treeNode){

        if(treeNode!=null){

            zhongXu(treeNode.getLefe());
            System.out.print(treeNode.getValue()+"\t\t");
            zhongXu(treeNode.getRight());
        }
    }


    // 左-> 右 ->根
    public void houXu(TreeNode treeNode){

        if(treeNode!=null){

            zhongXu(treeNode.getLefe());
            zhongXu(treeNode.getRight());
            System.out.print(treeNode.getValue()+"\t\t");
        }
    }



}

class TreeNode{
    private int value;
    private TreeNode lefe;
    private TreeNode right;


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

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public TreeNode getLefe() {
        return lefe;
    }

    public void setLefe(TreeNode lefe) {
        this.lefe = lefe;
    }

    public TreeNode getRight() {
        return right;
    }

    public void setRight(TreeNode right) {
        this.right = right;
    }
}

测试代码

/**
 * @author liha
 * @version 1.0
 * @date 2022/3/8 14:24
 */
public class Test {

    public static void main(String[] args) {


        BinaryTree tree = new BinaryTree();

        tree.insertNode(new TreeNode(6));
        tree.insertNode(new TreeNode(1));
        tree.insertNode(new TreeNode(4));
        tree.insertNode(new TreeNode(2));
        tree.insertNode(new TreeNode(10));
        tree.insertNode(new TreeNode(-1));
        tree.insertNode(new TreeNode(15));
        tree.insertNode(new TreeNode(8));
        tree.insertNode(new TreeNode(13));
        tree.insertNode(new TreeNode(20));


        tree.zhongXu(tree.getRootNode());

        tree.deleteNode(10);

        System.out.println("删除过后");

        tree.zhongXu(tree.getRootNode());

    }
}

总结

难度并不大,但是得花时间画图来理解。

最后如果此贴有帮助到您,希望能点赞+关注,您的支持是我最大的努力!~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. 什么是二叉树? 二叉树是一种树形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉树通常用于搜索和排序。 2. 二叉树的遍历方法有哪些? 二叉树的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子树,最后访问右子树。中序遍历是从根节点开始遍历,先访问左子树,再访问根节点,最后访问右子树。后序遍历是从根节点开始遍历,先访问左子树,再访问右子树,最后访问根节点。 3. 二叉树的查找方法有哪些? 二叉树的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子树中查找;如果要查找的值比当前节点大,则继续在右子树中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉树的插入与删除操作如何实现? 二叉树的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子树中插入;如果大于当前节点的值,则继续在右子树中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子树中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉树,它保证左右子树的高度差不超过1。这种平衡可以确保二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉树包括红黑树和AVL树。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员李哈

创作不易,希望能给与支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值