二分搜索树实现

1.1 基本介绍

二叉搜索树是二叉树的一种,是应用非常广泛的一种二叉树,英文简称为BST又被称为:二叉查找树、二叉排序树,任意一个节点的值都大于其左子树所有节点的值,任意一个节点的值都小于其右子树所有节点的值,它的左右子树也是一棵二叉搜索树。二叉搜索树可以大大提高搜索数据的效率,二叉搜索树存储的元素必须具备可比较性。

1.2 存储结构

1、顺序存储

二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现出之间的逻辑关系。

顺序存储结构在极端情况下浪费空间,只适合平衡树的存储。

2、链式存储

既然顺序表存储适用性性不强,则考虑链式存储结构,二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域,这种链表也叫作二叉链表。

其中data是数据域,lchildrchild都是指针域,分别存放指向左孩子和右孩子的指针。二叉链表示意图如下:

1.3 添加结点

1、思路分析

如果当前二分搜索树一个元素都没有,添加一个新元素【43】,则当前元素为根结点。

二次添加新元素比如添加【23】和【59】,直接让它跟根节点进行比较,大于插入根节点右边,小于插入根节点左边

依次添加元素

再次添加元素,添加【60】这个新节点,先跟根结点【41】比较,发现【60】比根结点大,插入根节点的右边,继续跟右边结点【59】比较,发现大于【59】,则插入到【59】的右边。

再次插入【28】这个新节点,先跟根结点【41】进行比较,发现小于根结点,往左边插入。一直比较,最终插入到结点【35】的左边。

1.4 删除结点

删除二分搜索树最小值

所谓二分搜索树的最小值,一定是一颗二叉树从根节点开始,不停的向左走,直到走不动为止,那个最左的节点一定是最小值

对于上面这颗二叉搜索树来说,删除最小节点,直接删除就好了。

特殊情况,此时二分搜索树最小节点是【23】,该节点存在叶子数。

此时只需要将【23】这个节点删除,然后将【23】整个右子树直接接上根结点的左子树就好了。

删除二分搜索树最大值

对于下面这颗二叉搜索树来说,删除最大节点【如果是叶子节点】,直接删除就好了。

删除叶子结点成功

如果不是叶子节点,存在左子树。先将结点【59】删除,然后将58的左子树整体当成41的右子树,接上即可。

删除二分搜索树任意节点

比如删除58这个节点,该节点左右都有孩子的节点,假设删除的结点为d

先找到删除节点的右子树的相应最小值的结点s = min(d->min)s 节点是d的后继,然后在d的右子树中,删除最小的值。

让S的右子树等于被删除节点的右子树,s->right = removeMin(d->right)

最后让s的左子树等价于d的左子树,最后删除dS是新的子树的根,s->left = d->left

1.5 深度优先遍历

深度优先遍历主要包括:先序遍历,中序遍历,后序遍历

1、先序遍历

规则是若二叉树为空,则空操作返回,否则先访问根节点,然后前序遍历左子树,再前序遍历右子树

2、中序遍历

规则是若树为空,则空操作返回,否则从根节点开始【注意并不是先访问根节点】,中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树

3、后序遍历

规则是若树为空,则空操作返回,否则从根结点开始,后序遍历根节点的左子树,然后后序遍历右子树,最后访问根节点

1.6 广度优先遍历

1、层序遍历

规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问

1.7 代码实现

接口类:List

package cn.binarysearch.demo;

public interface List<E> extends Iterable<E>{
    void add(E element);
    void add(int index, E element) ;
    void remove(E element);
    E remove(int index);
    E get(int index);
    E set(int index, E element) ;
    int size();
    int indexOf(E element) ;
    boolean contains(E element);
    boolean isEmpty();
    void clear();
}

链表类实现:LinkedList

package cn.binarysearch.demo;

import cn.set.demo.List;

import java.util.Iterator;

// 链表
public class LinkedList<E> implements List<E> {
    // 创建Node节点
    private class Node {
        // 数据域
        E data;
        // 指向直接前驱的指针
        Node prev;
        // 指向直接后继的指针
        Node next;

        // 构造函数
        public Node() {
            this(null, null, null);
        }

        public Node(E data) {
            this(data, null, null);
        }

        public Node(E data, Node prev, Node next) {
            this.data = data;
            this.prev = prev;
            this.next = next;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            if (prev != null) {
                sb.append(prev.data);
            } else {
                sb.append("null");
            }

            sb.append("->").append(data).append("->");

            if (next != null) {
                sb.append(next.data);
            } else {
                sb.append("null");
            }
            return sb.toString();
        }
    }

    // 链表元素的数量
    private int size;
    // 声明头结点
    private Node head;
    // 声明尾节点
    private Node tail;

    // 初始化头结点
    public LinkedList() {
        head = null;
        tail = null;
        size = 0;
    }

    public LinkedList(E[] arr) {
        for (E e : arr) {
            add(e);
        }
    }

    //默认向表尾添加元素
    @Override
    public void add(E element) {
        add(size, element);
    }

    //在链表当中指定索引index处添加一个元素
    @Override
    public void add(int index, E element) {
        if (index < 0|| index > size) {
            throw new ArrayIndexOutOfBoundsException("add index out of bounds");
        }
        // 创建新的结点对象
        Node node = new Node(element);

        // 链表为空
        if(isEmpty()){
            head = node;
            tail = node;
            tail.next = head;
            head.prev = tail;

        }else if(index == 0){ // 在链表头部添加元素
            // 头结点的上一跳指向新节点的上一跳
            node.prev = head.prev;
            node.next = head;
            head.prev = node;
            head = node;
            tail.next = head;
        }else if(index == size){ // 在链表尾部添加元素
            node.next = tail.next;
            tail.next = node;
            node.prev = tail;
            tail = node;
            head.prev = tail;
        }else{
            // 在链表中添加元素
            Node p,q; // 定义两个指针变量
            if(index <= size / 2){
                p = head;
                for(int i =0; i < index -1 ; i++){
                    p = p.next;
                }
                q = p.next;
                p.next = node;
                node.prev = p;
                q.prev = node;
                node.next = q;
            }else{
                p = tail;
                for(int i=size -1; i > index; i--){
                    p = p.prev;
                }
                q = p.prev;
                q.next = node;
                node.prev = q;
                p.prev = node;
                node.next = p;
            }
        }
        size++;
    }

    //删除链表中指定的元素element
    @Override
    public void remove(E element) {
        int index = indexOf(element);
        if(index != -1){
            remove(index);
        }
    }

    //删除链表中指定角标处index的元素
    @Override
    public E remove(int index) {
        if (index < 0|| index > size) {
            throw new ArrayIndexOutOfBoundsException("remove index out of bounds");
        }
        // 定义ret变量
        E ret = null;
        Node node;
        // 当链表只剩一个元素
        if(size ==1){
            ret = head.data;
            head = null;
            tail = null;
            // 删除表头
        }else if(index == 0){
            ret = head.data;
            node = head.next;
            head.next = null;
            node.prev = head.prev;
            head.prev = null;
            head = node;
            tail.next = head;
            // 删除表尾
        }else if(index == size -1){
            ret = tail.data;
            node = tail.prev;
            tail.prev = null;
            node.next = tail.next;
            tail.next = null;
            tail = node;
            head.prev = tail;
        }else{
            // 删除链表中间的某一个元素
            Node p, q, r;
            if(index <= size / 2){
                p = head;
                for(int i=0; i < index-1; i++){
                    p = p.next;
                }
                q = p.next;
                ret = q.data;
                r = q.next;
                p.next = r;
                r.prev = p;
                q.next = null;
                q.prev = null;
            }else{
               p = tail;
               for(int i = size -1; i > index + 1; i--){
                   p = p.prev;
               }
               q = p.prev;
               ret = q.data;
               r = q.prev;
               r.next = p;
               p.prev = r;
               q.next = null;
               q.prev = null;
            }
        }
        size --;
        return ret;
    }

    //获取链表中指定索引处的元素
    @Override
    public E get(int index) {
        if (index < 0|| index > size) {
            throw new ArrayIndexOutOfBoundsException("get index out of bounds");
        }
        // 获取头
        if(index == 0){
            return head.data;
        }else if(index == size -1){
            // 获取尾部
            return tail.data;
        }else{
            // 获取中间
            Node p = head;
            for (int i = 0; i < index; i++) {
                p = p.next;
            }
            return p.data;
        }
    }

    // 修改链表中指定索引index的元素为element
    @Override
    public E set(int index, E element) {
        if (index < 0|| index > size) {
            throw new ArrayIndexOutOfBoundsException("set index out of bounds");
        }

        E ret = null;
        // 获取头
        if(index == 0){
           // 修改头
            ret = head.data;
            head.data = element;
        }else if(index == size -1){
            // 修改尾部元素
            ret = tail.data;
            tail.data = element;
        }else{
            // 修改中间
            Node p = head;
            for (int i = 0; i < index; i++) {
                p = p.next;
            }
            ret = p.data;
            p.data = element;
        }

        return ret;
    }

    @Override
    public int size() {
        return size;
    }

    //查找元素在链表中第一次出现的索引
    @Override
    public int indexOf(E element) {
        if(isEmpty()){
            return -1;
        }
        Node p = head;
        int index = 0;
        while (!p.data.equals(element)){
            p = p.next;
            index++;
            if(p == head){
                return -1;
            }
        }
        return index;
    }

    //在链表中判断是否包含元素element
    @Override
    public boolean contains(E element) {
        return indexOf(element) != -1;
    }

    @Override
    public boolean isEmpty() {
        return size== 0 && head == null && tail == null;
    }

    @Override
    public void clear() {
        head = null;
        tail = null;
        size = 0;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("size=").append(size).append(", [");
        Node node = head;
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                res.append(", ");
            }
            res.append(node);
            node = node.next;
        }
        res.append("]");
        return res.toString();
    }

    @Override
    public Iterator<E> iterator() {
        return new DoubleCircleLinkedListIterator();
    }

    class  DoubleCircleLinkedListIterator implements Iterator<E>{
        private Node cur = head;
        private boolean flag = true;

        @Override
        public boolean hasNext() {
            if(isEmpty()){
                return false;
            }
            return flag;
        }

        @Override
        public E next() {
            E ret = cur.data;
            cur = cur.next;
            if(cur == head){
                flag = false;
            }
            return ret;
        }
    }
}

二分搜索树实现:BinarySearchTree

package cn.binarysearch.demo;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;

// 二分搜索树
public class BinarySearchTree<E extends Comparable<E>> implements Iterable<E>{
    // 二分搜索树结点定义
    private class Node {
        // 数据域
        public E e;
        // 左右指针域
        public Node left, right;
        // 构造方法
        public Node (E e){
            this.e = e;
            left = null;
            right = null;
        }
    }

    // 二分搜索树的根节点的指针
    private Node root;
    // 二分搜索树的元素的个数
    private int size;

    // 创建一颗空的二分搜索树
    public BinarySearchTree(){
        root = null;
        size = 0;
    }
    // 获取二分搜索树的元素个数
    public int size(){
        return size;
    }

    // 判断二分搜索树是否为空
    public boolean isEmpty(){
        return size == 0 && root == null;
    }

    /***
     * 向二分搜索树中添加一个元素e
     * @param e
     */
    public void add(E e){
        root = add(root, e);
    }

    /***
     * 向以node为根的二分搜索树中添加元素e
     * 并且返回新结点插入之后的二分搜索树新的根
     * @param node
     * @param e
     * @return
     */
    private Node add(Node node, E e){
        if(node == null){
            size ++;
            return new Node(e);
        }

        if(e.compareTo(node.e) < 0){
            // 以node左子树为根节点,再添加一个元素
            node.left = add(node.left, e);
        }else if(e.compareTo(node.e) > 0){
            // 以node右子树为根节点,再添加一个元素
            node.right = add(node.right, e);
        }

        return node;
    }

    /***
     * 判断二分搜索树中是否包含元素e
     * @param e
     * @return
     */
    public boolean contains(E e){
        return contains(root, e);
    }

    /***
     * 以node为根节点的树中,判断e是否存在
     * @param node
     * @param e
     * @return
     */
    private boolean contains(Node node, E e){
        if(node == null){
            return false;
        }
        if(e.compareTo(node.e) == 0){
            return true;
        }
        // 以当前节点的左孩子为根的左子树,去寻找元素e
        if(e.compareTo(node.e) < 0){
            return contains(node.left, e);
        }else {
            // 以当前节点的右孩子为根的右子树,去寻找元素e
            return contains(node.right, e);
        }
    }

    // 前序遍历
    public void preOrder(){
        preOrder(root);
    }

    /***
     * 前序遍历递归实现
     * @param node
     */
    public void preOrder(Node node){
        if(node == null){
            return;
        }

        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

    // 前序遍历的非递归实现方式
    public void preOrderNR(){
        // 先访问根节点,然后前序遍历左子树,再前序遍历右子树
        LinkedList<Node> stack = new LinkedList<>();
        // 入栈根节点
        stack.push(root);
        while (!stack.isEmpty()){
            // 先弹出一个元素
            Node cur = stack.pop();
            System.out.println(cur.e);
            // 判断左右孩子是否为空,然后执行入栈操作
            if(cur.right != null){
                stack.push(cur.right);
            }
            if(cur.left != null){
                stack.push(cur.left);
            }
        }
    }

    // 中序遍历
    public void inOrder(){
        inOrder(root);
    }

    /***
     * 中序遍历递归实现
     * @param node
     */
    private void inOrder(Node node){
        if(node == null){
            return;
        }
        inOrder(node.left);
        System.out.println(node.e);
        inOrder(node.right);
    }


    // 中序遍历非递归实现
    public void inOrderNR(){
        // 中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树
        LinkedList<Node> stack = new LinkedList<>();
        // 创建pre指针
        Node prev = root;
        // 将现将根节点和根节点的左边全部入栈
       while (prev != null){
           stack.push(prev);
           prev = prev.left;
       }
       while (!stack.isEmpty()){
           // 先弹出元素
           Node cur = stack.pop();
           System.out.println(cur.e);
           // 判断该结点右边是否为空
           if(cur.right != null){
               // 如果右孩子不为空,继续执行入栈操作
               prev = cur.right;
               while (prev != null){
                   // 将左孩子执行入栈操作
                   stack.push(prev);
                   prev = prev.left;
               }
           }
       }
    }

    // 后序遍历
    public void postOrder(){
        postOrder(root);
    }

    /***
     * 后序遍历递归实现
     * @param node
     */
    private void postOrder(Node node){
        if(node == null){
            return;
        }
        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e + " ");
    }

    // 后序遍历非递归实现
    public void postOrderNR(){
        if(root == null){
            return;
        }
        // 申请两个栈s1,s2,头节点入栈s1
        LinkedList<Node> s1 = new LinkedList<>();
        LinkedList<Node> s2 = new LinkedList<>(); // 辅助栈,存储根 -> 右节点 ->左节点 的结果

        // 先插入根节点
        s1.push(root);
        // 如果栈s1不为空,执行以下操作:弹出一个元素,入栈s2
        while (!s1.isEmpty()){
            Node cur = s1.pop();
            s2.push(cur);
            // 如果该节点左孩子不空入栈s1,如果该节点右孩子不空入栈s1.
            if(cur.left != null){
                s1.push(cur.left);
            }
            if(cur.right != null){
                s1.push(cur.right);
            }
        }

        // 将栈s2中的节点一次出栈,打印
        while (!s2.isEmpty()){
            Node cur = s2.pop();
            System.out.println(cur.e + " ");
        }
    }

    // 层序遍历
    public void levelOrder(){
        // 创建队列
        Queue<Node> queue = new LinkedList<>();
        // 先入队根节点
        queue.offer(root);
        while (!queue.isEmpty()){
            // 先出队一个元素
            Node cur = queue.poll();
            System.out.println(cur.e);
            // 分别入队左右子树
            if(cur.left != null){
                queue.offer(cur.left);
            }
            if(cur.right != null){
                queue.offer(cur.right);
            }
        }
    }

    // 返回二分搜索树的最小值【迭代实现】
    public E minimum2(){
        if (isEmpty()){
            throw new IllegalArgumentException("BST is empty!!!");
        }
        // 定义指针prev指向根节点
        Node prev = root;
        while (prev.left != null){
            prev = prev.left;
        }

        return prev.e;
    }

    // 返回二分搜索树的最小值【递归实现】
    public E minimum(){
        if(isEmpty()){
            throw new IllegalArgumentException("BST is empty!!!");
        }
        Node minNode = minimum(root);
        return minNode.e;
    }

    /***
     * 返回以node为根的二分 搜索树的最小值所在的节点
     * @param node
     * @return
     */
    private Node minimum(Node node){
        if(node.left == null){
            return node;
        }
        return minimum(node.left);
    }

    // 返回二分搜索树的最大值【递归实现】
    public E maximum(){
        if(isEmpty()){
            throw new IllegalArgumentException("BST is empty!!!");
        }
        Node maxNode = maximum(root);
        return maxNode.e;
    }

    /***
     * 返回以node为根的二分 搜索树的最大值所在的节点
     * @param node
     * @return
     */
    private Node maximum(Node node){
        if(node.right == null){
            return node;
        }
        return maximum(node.right);
    }

    // 从二分搜索树中删除最小值所在节点, 返回最小值
    public E removeMin(){
        // 调用函数
        E ret = minimum();
        root = removeMin(root);
        return ret;
    }

    /***
     * 删除掉以node为根的二分搜索树中的最小节点
     * 返回删除节点后新的二分搜索树的根
     * @param node
     * @return
     */
    private Node removeMin(Node node){
        // 向左走再也走不动了
        if(node.left == null){
            // 定义变量保存当前结点的右子树
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }
        // 以node的左边为根节点,删除当前左子树的最小值,然后返回给左边接上
        node.left = removeMin(node.left);
        return node;
    }

    // 从二分搜索树中删除最大值所在节点, 返回最大值
    public E removeMax(){
        // 调用函数
        E ret = maximum();
        root = removeMax(root);
        return ret;
    }

    /***
     * 删除掉以node为根的二分搜索树中的最大节点
     * 返回删除节点后新的二分搜索树的根
     * @param node
     * @return
     */
    private Node removeMax(Node node){
        if(node.right == null){
            // 定义变量保存当前结点的左子树
            Node leftNode = node.left;
            node.left = null;
            size --;
            return leftNode;
        }
        // 以node的右边为根节点,删除当前右子树的最大值,然后返回给右边接上
        node.right = removeMax(node.right);
        return node;
    }

    // 从二分搜索树中删除元素为e的节点
    public void remove(E e){
        root = remove(root, e);
    }

    /***
     * 删除掉以node为根的二分搜索树中值为e的节点,递归算法
     * 返回删除节点后新的二分搜索树的根
     * @param node
     * @param e
     * @return
     */
    private Node remove(Node node, E e){
        if(node == null){
            return null;
        }
        if(e.compareTo(node.e) < 0){
            node.left = remove(node.left, e);
            return node;
        }else if(e.compareTo(node.e) > 0){
            node.right = remove(node.right, e);
            return node;
        }else { // e.compareTo(node.e) == 0
            // 如果左子树为空
            if(node.left == null){
                Node rightNode = node.right;
                // 置空操作
                node.right = null;
                size --;
                return rightNode;
            }
            // 待删除节点右子树为空的情况
            if(node.right == null){
                Node leftNode = node.left;
                // 置空操作
                node.left = null;
                size --;
                return leftNode;
            }

            // 待删除节点左右子树均不为空的情况
            // 找到node结点的后继
            Node successor = minimum(node.right);
            // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
            successor.right = removeMin(node.right);
            // 用这个节点顶替待删除节点的位置
            successor.left = node.left;
            // 置空操作
            node.left = node.right = null;
            return successor;
        }
    }
    
   @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        // 前序遍历实现
        generateBST(root, 0, sb);
        return sb.toString();
    }

    // 生成以node为根节点,深度为depth的描述二叉树的字符串
    private void generateBST(Node node, int depth, StringBuilder sb){
        if(node == null){
            sb.append(generateDepth(depth) + "null\n");
            return;
        }

        // 当前结点存储的元素
        sb.append(generateDepth(depth) + node.e + "\n");
        // 访问当前结点的左子树
        generateBST(node.left, depth + 1, sb);
        // 访问当前结点的右子树
        generateBST(node.right, depth + 1, sb);
    }

    private String generateDepth(int depth){
        StringBuilder sb = new StringBuilder();
        for(int i = 0 ; i < depth ; i ++)
            sb.append("--");
        return sb.toString();
    }

    @Override
    public Iterator<E> iterator() {
        return new BinarySearchTreeIterator();
    }

    private class BinarySearchTreeIterator implements Iterator<E>{
        private LinkedList<E> list = new LinkedList<>();
        public BinarySearchTreeIterator(){
            LinkedList<Node> stack = new LinkedList<>();
            // 创建pre指针
            Node prev = root;
            // 将现将根节点和根节点的左边全部入栈
            while (prev != null){
                stack.push(prev);
                prev = prev.left;
            }
            while (!stack.isEmpty()){
                // 先弹出元素
                Node cur = stack.pop();
                list.add(cur.e);
                // 判断该结点右边是否为空
                if(cur.right != null){
                    // 如果右孩子不为空,继续执行入栈操作
                    prev = cur.right;
                    while (prev != null){
                        // 将左孩子执行入栈操作
                        stack.push(prev);
                        prev = prev.left;
                    }
                }
            }
        }
        @Override
        public boolean hasNext() {
            return !list.isEmpty();
        }

        @Override
        public E next() {
            return list.removeFirst();
        }
    }
}

测试类:BinarySearchTreeDemo

package cn.binarysearch.demo;

public class BinarySearchTreeDemo {
    public static void main(String[] args) {
        BinarySearchTree<Integer> tree = new BinarySearchTree<>();
        // 向二叉树中添加节点
        tree.add(6);
        tree.add(9);
        tree.add(1);
        tree.add(3);
        tree.add(11);
        tree.add(5);
        // 判断元素是否存在
        System.out.println(tree.contains(11));
        System.out.println(tree.contains(10));

        // 调用前序遍历
        System.out.println("===前序遍历【递归】===");
        tree.preOrder();
        System.out.println("===前序遍历【非递归】===");
        tree.preOrderNR();

        System.out.println("==========");
        System.out.println(tree);
        // 调用中序遍历
        /*
        System.out.println("===中序遍历【递归】===");
        tree.inOrder();
        System.out.println("===中序遍历【非递归】===");
        tree.inOrderNR();

        */
        // 调用后序遍历
        /*System.out.println("===后序遍历【递归】===");
        tree.postOrder();
        System.out.println("===后序遍历【非递归】===");
        tree.postOrderNR();*/

        // 层序遍历
       /* System.out.println("===层序遍历===");
        tree.levelOrder();*/

        // 返回最小值
        /*System.out.println("二叉搜索树最小节点:" + tree.minimum1());
        System.out.println("二叉搜索树最小节点:" + tree.minimum2());
        System.out.println("二叉搜索树最大节点:" + tree.maximum());*/

        // 删除二叉搜索树中的最小值
        /*System.out.println("二叉树最小节点[删除前]:" + tree.minimum());
        System.out.println(tree.removeMin());
        System.out.println("二叉树最小节点[删除后]:" + tree.minimum());
        System.out.println("===========");
        // 删除二叉搜索树中的最大值
        System.out.println("二叉树最大节点[删除前]:" + tree.maximum());
        System.out.println(tree.removeMax());
        System.out.println("二叉树最大节点[删除后]:" + tree.maximum());*/

        // 中序遍历
        // tree.inOrderNR();
        //tree.remove(3);
        //System.out.println(tree);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值