数据结构(链表-链栈-二叉排序树BST-平衡二叉树AVL-红黑树-B树)(C++)

引言

本文的主要内容为线性链表、链栈、二叉排序树、平衡二叉树等几种数据及C++实现,主要参考了李长河主编的《C++程序设计》教材,其中线性链表、链栈、二叉排序树的代码可从该主编的GitHub上的第8.3节处获取:https://github.com/Changhe160/book-cplusplus/tree/master/code

本文记录如有错误,万望指正。笔者的邮箱为:wuxiaofang555555@163.com 。本文的代码亦可以从笔者的GitHub上获取:https://github.com/wuerfang/data_structure

线性链表

线性链表

  • 也称为单链表(singly linked list),每个数据元素占用一个结点(node),链表(SList)由多个结点组成

  • 结点node)含一个数据域和一个指针域,其中指针域存放下一个结点的地址

  • 链表SList)含有一个头指针head)、尾指针tail)、多个结点;如下所示
    在这里插入图片描述

结点 :Node

template<typename T>
class Node {
	T m_data;
	Node *m_next = nullptr;
	friend class SList<T>;		//将SList声明为Node的友元
public:
	Node(const T &val) :m_data(val) {  }
	Node(const Node &) = delete;			//禁止复制
	Node& operator=(const Node &) = delete;	//禁止赋值
	const T& data() const { return m_data; }
	T& data() { return m_data; }
	Node* next() { return m_next; }
};

单链表C++代码:SList

template<typename T>
class SList {
	Node<T> *m_head = nullptr, *m_tail = nullptr;
public:
	SList() = default;
	~SList();
	SList(const SList &) = delete;			 //禁止复制
	SList& operator=(const SList &) = delete;//禁止赋值
	void clear();
	void push_back(const T &val);
	Node<T>* insert(Node<T> *pos, const T &val);
	void erase(const T &val);
	Node<T>* find(const T &val);
	friend std::ostream& operator<< <T>(std::ostream &os, const SList<T> &list);
};
template<typename T>
SList<T>::~SList() {
	clear();
}
template<typename T>
void SList<T>::clear() {
	Node<T> *p = nullptr;
	while (m_head != nullptr) {
		p = m_head;
		m_head = m_head->m_next;
		delete p;
	}
	m_tail = nullptr;
}
template<typename T>
std::ostream& operator<<(std::ostream &os, const SList<T> &list) {
	Node<T> *p = list.m_head;
	while (p != nullptr) {
		os << p->data() << " ";
		p = p->next();
	}
	return os;
}

插入结点

在这里插入图片描述

template<typename T>
void SList<T>::push_back(const T &val) {
	Node<T>* node = new Node<T>(val);
	if (m_head == nullptr) {
		m_head = m_tail = node;
	}
	else {
		m_tail->m_next = node;
		m_tail = node;
	}
}
template<typename T>
Node<T>* SList<T>::insert(Node<T> *pos, const T &val) {
	Node<T>* node = new Node<T>(val);
	if (pos == m_tail) {		//判断插入的位置是否为尾结点
		m_tail->m_next = node;	
		m_tail = node;
	}
	else {
		node->m_next = pos->m_next;	
		pos->m_next = node;			
	}
	return node;
}

删除结点

在这里插入图片描述

template<typename T>
void SList<T>::erase(const T &val) {
	Node<T> *p = m_head, *q = p;
	while (p != nullptr&&p->m_data != val) {
		q = p;
		p = p->m_next;
	}
	if (p)
		q->m_next = p->m_next;
	if (p == m_tail)
		m_tail = q;
	if (p == m_head && p)
		m_head = p->m_next;
	
	delete p;
}

查找元素

template<typename T>
Node<T>* SList<T>::find(const T &val) {
	Node<T> *p = m_head;
	while (p != nullptr && p->m_data != val) {
		p = p->m_next;
	}
	return p;
}

链栈

栈(Stack)的结构

在这里插入图片描述

  • Node : 链栈的结点与前述线性链表的相同

链栈C++代码:Stack

  • Stack

    template<typename T>
    class Stack {
    	Node<T> *m_top = nullptr;
    public:
    	Stack() = default;
    	~Stack();
    	Stack(const Stack &) = delete;				//禁止复制
    	Stack& operator=(const Stack&) = delete;	//禁止赋值
    	void clear();								//清除链栈
    	bool empty() { return m_top == nullptr; }
    	void push(const T &val);	//压栈
    	void pop();					//弹栈
    	const T& top(){ return m_top->m_data; }//获得栈顶元素
    };
    template<typename T>
    Stack<T>::~Stack() {
    	clear();
    }
    template<typename T>
    void Stack<T>::clear() {
    	Node<T> *p = nullptr;
    	while (m_top != nullptr) {
    		p = m_top;
    		m_top = m_top->m_next;
    		delete p;
    	}
    }
    template<typename T>
    void Stack<T>::push(const T &val) {
    	Node<T> *node = new Node<T>(val);
    	node->m_next = m_top;
    	m_top = node;
    }
    template<typename T>
    void Stack<T>::pop() {
    	Node<T> *p = m_top;
    	m_top = m_top->m_next;
    	delete p;
    }
    

二叉树

二叉树的结构

在这里插入图片描述

结点:Node

  • 包括一个数据域两个指针域
template<typename T>
class Node {
	T m_data;
	Node *m_left = nullptr, *m_right = nullptr;
public:
	Node(const T &val) :m_data(val) {  }
	Node(const Node &) = delete;
	Node& operator=(const Node &) = delete;
	const T& data() const { return m_data; }
	T& data() { return m_data; }
	Node* left() { return m_left; }
	Node* right() { return m_right; }
};

二叉排序树

二叉排序树

二叉排序树(Binary Sort Tree), 亦称为二叉搜索树(Binary Search Tree)、二叉查找树

  • 根结点的键值,大于左子树上的键值,小于右子树上的键值
  • 中序遍历可以获得从小到大的键值序列,利于查找

删除结点

在这里插入图片描述

  • 第一种情况:结点为叶子节点,直接删除

  • 第二种情况:结点只有左子树或只有右子树,删除该结点,将其左子树或右子树直接替代它的位置

  • 第三种情况:结点既有左子树又有右子树,找到它的中序遍历的前序或后继,用其替换该节点,然后删除前驱或是后继。

    • 找该结点的后继结点的方法

      • 第一种情况:该结点有右子树,则后继为右子树的最左结点
      • 第二种情况:该结点没有右子树,且其为其父亲结点的左结点,则后继为其父亲结点
      • 第三种情况:该结点没有右子树,且其为其父亲结点的右结点,沿着父节点寻找,直到找到 一个结点为其父亲结点的左结点,则所寻找的后继为该父亲结点,
    • 找该结点的前驱结点的方法

      • 第一种情况:该结点有左子树,则前驱为左子树的最右结点
      • 第二种情况:该结点没有左子树,且其为其父亲结点的右结点,则前驱为其父亲结点
      • 第三种情况:该结点没有左子树,且其为其父亲结点的左结点,沿着父节点寻找,直到找到 一个结点为其父亲结点的右结点,则所寻找的前驱为该父亲结点
        在这里插入图片描述

二叉树搜索C++代码:BSTree

  • BSTree

    template<typename T>
    class BSTree {
    	Node<T> *m_root;
    public:
    	BSTree() = default;
    	~BSTree() { destory(m_root); }
    	Node<T>* root() { return m_root; }
    	Node<T>* insert(const T &val) { return insert_(m_root, val); }
    	Node<T>* search(const T &val) { return search_(m_root, val); }
    	void inOrder() { inOrder_(m_root); }
    private:
    	void destory(Node<T> *p);
    	Node<T>* insert_(Node<T> * &p, const T &val);
    	Node<T>* search_(Node<T> *p, const T &val);	
    	void inOrder_(Node<T> *p);
    	void visit(T &val) { std::cout << val << " "; }
    };
    template<typename T>
    void BSTree<T>::destory(Node<T> *p) {
    	while (p != nullptr) {
    		destory(p->m_left);
    		destory(p->m_right);
    		delete p;
    	}	
    }
    template<typename T>
    Node<T>* BSTree<T>::insert_(Node<T> * &p, const T &val) {
    	if (p == nullptr)	//找到插入的位置,插入结点,若new失败,std::nothrow能够保证返回空指针
    		return p = new (std::nothrow) Node<T>(val);
    	else if (val < p->m_data)	//从左子树中查找
    		return insert_(p->m_left, val);
    	else                        //从左子树中查找
    		return insert_(p->m_right, val);
    }
    template<typename T>
    Node<T>* BSTree<T>::search_(Node<T> *p, const T &val) {
    	while (p != nullptr && val != p->m_data) {
    		if (val < p->m_data)
    			p = p->m_left;
    		else
    			p = p->m_right;
    	}
    	return p;
    }
    template<typename T>
    void BSTree<T>::inOrder_(Node<T> *p) {
    	if (p != nullptr) {
    		inOrder_(p->m_left);
    		visit(p->m_data);
    		inOrder_(p->m_right);
    	}
    }
    

平衡二叉树(AVL树)

平衡二叉树

  • 平衡二叉树(Self-Balancing Binary Search TreeHeight-Balancing Binary Search Tree
    • 是一种特殊的二叉排序树,其中每个节点的左子树和右子树的高度差至多为1,是高度平衡的
    • 平衡因子BFBalance Factor)指结点的左子树深度减去右子树深度的值,AVL树上的结点的平衡因子只可能为 1 ,    0 ,    − 1 1,\; 0,\; -1 1,0,1
  • 平衡二叉树查找的时间复杂度优于二叉排序树,其查找、删除和插入的时间复杂度均为 O ( l o g n ) O(logn) O(logn)

结点:Node

  • 多了一个平衡因子

    template<typename T>
    class Node {
    	T m_data;
    	Node *m_left = nullptr, *m_right = nullptr;
    	int m_bf;	//平衡因子
    friend class BalanceBinaryTree<T>;
    public:
    	Node(const T &val) :m_data(val), m_bf(0) {  }
    	Node(const Node &) = delete;
    	Node& operator=(const Node &) = delete;
    	const T& data() const { return m_data; }
    	T& data() { return m_data; }
    	const int& bf() const { return m_bf; }
    	int& bf() { return m_bf; }
    	Node* left() { return m_left; }
    	Node* right() { return m_right; }
    };
    

插入节点

  • 插入结点时,为了保持平衡性,需要旋转操作(平衡调整),共有以下四种旋转方式
    在这里插入图片描述

平衡二叉树C++代码

  • BanlanceBinaryTree(这里只给出了插入操作,即创建平衡二叉树的必要函数)
template<typename T>
class BalanceBinaryTree {
   Node<T> *m_root = nullptr;   //根结点
public:
   BalanceBinaryTree() = default;
   void insert(const T &val);
   void insert_(Node<T> *& root, Node<T> *node);   //将指针S所指节点插入二叉排序中
   int getDepth(Node <T> * T);                     //求树的高度
   int getNodeFactor(Node<T> *node);               //求树中节点的平衡因子
   void bfForTree(Node<T> *&root);                 //求树中的每个节点的平衡因子
   void bfIsTwo(Node<T> *&root, Node<T> *&f, Node<T>* &p);
   void LLAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f); //LL调整
   void LRAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f); //LR调整
   void RLAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f); //RL调整
   void RRAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f); //RR调整
   void allAdjust(Node<T> *&root);                       //集成四种调整,并实时更新平衡因子
};
template<typename T>
void BalanceBinaryTree<T>::insert(const T &val) {
   Node<T>* node = new Node<T>(val);
   insert_(m_root, node);
   allAdjust(m_root);
}
template<typename T>
void BalanceBinaryTree<T>::insert_(Node<T> *& root, Node<T> * node) {
   if (root == nullptr)
   	root = node;
   else if (node->m_data<root->m_data)
   	insert_(root->m_left, node);
   else
   	insert_(root->m_right, node);
}
template<typename T>
int BalanceBinaryTree<T>::getDepth(Node<T> * root) {	
   if (root == nullptr)
   	return 0;
   else {
   	int ldepth = getDepth(root->m_left);
   	int rdepth = getDepth(root->m_right);
   	int depth = ldepth > rdepth ? ldepth : rdepth;
   	depth += 1;
   	return depth;
   }
}
template<typename T>
int BalanceBinaryTree<T>::getNodeFactor(Node<T> *node) {
   int ldepth = 0, rdepth = 0;
   if (node) {
   	ldepth = getDepth(node->m_left);
   	rdepth = getDepth(node->m_right);
   }
   return ldepth - rdepth;
}
template<typename T>
void BalanceBinaryTree<T>::bfForTree(Node<T> *&root) {
   if (root) {
   	root->m_bf = getNodeFactor(root);
   	bfForTree(root->m_left);
   	bfForTree(root->m_right);
   }
}
template<typename T>
void BalanceBinaryTree<T>::bfIsTwo(Node<T> *&root, Node<T> *&f, Node<T>* &p) {
   if (root) {
   	if (root->m_left != nullptr) {
   		if (root->m_left->m_bf == 2 || root->m_left->m_bf == -2) {
   			f = root;
   			p = root->m_left;
   		}
   	}
   	if (root->m_right != nullptr) {
   		if (root->m_right->m_bf == 2 || root->m_right->m_bf == -2) {
   			f = root;
   			p = root->m_right;
   		}
   	}
   	bfIsTwo(root->m_left, f, p);
   	bfIsTwo(root->m_right, f, p);
   }
}
template<typename T>
void BalanceBinaryTree<T>::LLAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f) {
   Node<T> *r;
   if (root == p) {
   	root = p->m_left;		//将P的左孩子提升为新的根节点
   	r = root->m_right;
   	root->m_right = p;		//将p降为其左孩子的右孩子
   	p->m_left = r;			//将p原来的左孩子的右孩子连接其p的左孩子

   }
   else {
   	if (f->m_left == p) {			//f的左孩子是p			
   		f->m_left = p->m_left;		//将P的左孩子提升为新的根节点
   		r = f->m_left->m_right;
   		f->m_left->m_right = p;		//将p降为其左孩子的右孩子
   		p->m_left = r;				//将p原来的左孩子的右孩子连接其p的左孩子
   	}
   	if (f->m_right == p){			//f的左孩子是p
   		f->m_right = p->m_left;		//将P的左孩子提升为新的根节点
   		r = f->m_right->m_right;
   		f->m_right->m_right = p;	//将p降为其左孩子的右孩子
   		p->m_left = r;				//将p原来的左孩子的右孩子连接其p的左孩子
   	}
   }
}
template<typename T>
void BalanceBinaryTree<T>::LRAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f){
   Node<T> *l, *r;
   if (root == p) {           //->m_bf==2&&root->m_left->m_bf!=2
   	root = p->m_left->m_right;    //将P的左孩子的右孩子提升为新的根节点
   	r = root->m_right;
   	l = root->m_left;
   	root->m_right = p;
   	root->m_left = p->m_left;
   	root->m_left->m_right = l;
   	root->m_right->m_left = r;
   }
   else {
   	if (f->m_right == p) {    //f的左孩子是p
   		f->m_right = p->m_left->m_right;    //将P的左孩子的右孩子提升为新的根节点
   		r = f->m_right->m_right;
   		l = f->m_right->m_left;
   		f->m_right->m_right = p;
   		f->m_right->m_left = p->m_left;
   		f->m_right->m_left->m_right = l;
   		f->m_right->m_right->m_left = r;
   	}
   	if (f->m_left == p) {     //f的左孩子是p
   		f->m_left = p->m_left->m_right;    //将P的左孩子的右孩子提升为新的根节点
   		r = f->m_left->m_right;
   		l = f->m_left->m_left;
   		f->m_left->m_right = p;
   		f->m_left->m_left = p->m_left;
   		f->m_left->m_left->m_right = l;
   		f->m_left->m_right->m_left = r;
   	}
   }
}
template<typename T>
void BalanceBinaryTree<T>::RLAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f)
{
   Node<T> *l, *r;
   if (root == p) {          //->m_bf==-2&&root->m_right->m_bf!=-2
   	root = p->m_right->m_left;
   	r = root->m_right;
   	l = root->m_left;
   	root->m_left = p;
   	root->m_right = p->m_right;
   	root->m_left->m_right = l;
   	root->m_right->m_left = r;
   }
   else {
   	if (f->m_right == p){     //f的左孩子是p
   		f->m_right = p->m_right->m_left;
   		r = f->m_right->m_right;
   		l = f->m_right->m_left;
   		f->m_right->m_left = p;
   		f->m_right->m_right = p->m_right;
   		f->m_right->m_left->m_right = l;
   		f->m_right->m_right->m_left = r;
   	}
   	if (f->m_left == p) {    //f的左孩子是p
   		f->m_left = p->m_right->m_left;
   		r = f->m_left->m_right;
   		l = f->m_left->m_left;
   		f->m_left->m_left = p;
   		f->m_left->m_right = p->m_right;
   		f->m_left->m_left->m_right = l;
   		f->m_left->m_right->m_left = r;
   	}
   }
}
template<typename T>
void BalanceBinaryTree<T>::RRAdjust(Node<T> *&root, Node<T> *&p, Node<T> *&f) {
   Node<T> *l;
   if (root == p){				//->m_bf==-2&&root->m_right->m_bf!=-2
   	root = p->m_right;		//将P的右孩子提升为新的根节点
   	l = root->m_left;
   	root->m_left = p;		//将p降为其右孩子的左孩子
   	p->m_right = l;			//将p原来的右孩子的左孩子连接其p的右孩子
   							//注意:p->m_right->m_bf==0插入节点时用不上,删除节点时可用
   }
   else {
   	if (f->m_right == p){			//f的右孩子是p
   		f->m_right = p->m_right;	//将P的右孩子提升为新的根节点
   		l = f->m_right->m_left;
   		f->m_right->m_left = p;		//将p降为其右孩子的左孩子
   		p->m_right = l;				//将p原来的右孩子的左孩子连接其p的右孩子
   	}
   	if (f->m_left == p) {			//f的左孩子是p
   		f->m_left = p->m_right;		//将P的左孩子提升为新的根节点
   		l = f->m_left->m_left;
   		f->m_left->m_left = p;		//将p降为其左孩子的左孩子
   		p->m_right = l;				//将p原来的右孩子的左孩子连接其p的右孩子
   	}
   }
}
template<typename T>
void BalanceBinaryTree<T>::allAdjust(Node<T> *&root) {
   Node<T> *f = nullptr, *p = nullptr;
   bfForTree(root);
   bfIsTwo(root, f, p);
   while (p) {
   	bfForTree(root);
   	if (p->m_bf == 2 && (p->m_left->m_bf == 1 || p->m_left->m_bf == 0))	{
   		LLAdjust(root, p, f);
   		bfForTree(root);
   	}
   	else if (p->m_bf == 2 && p->m_left->m_bf == -1) {
   		LRAdjust(root, p, f);
   		bfForTree(root);
   	}
   	else if (p->m_bf == -2 && p->m_right->m_bf == 1) {
   		RLAdjust(root, p, f);
   		bfForTree(root);
   	}
   	else if (p->m_bf == -2 && (p->m_right->m_bf == -1 || p->m_right->m_bf == 0)) {
   		RRAdjust(root, p, f);
   	}
   	f = nullptr;
   	p = nullptr;
   	bfIsTwo(root, f, p);
   }
}
  • 测试代码
  int main() {
  	BalanceBinaryTree<int> tree;
  	std::vector<int> v = { 4,5,6,3,2,1,9,8};
  	for (int i = 0; i < v.size(); ++i) {
  		tree.insert(v[i]);
  	}
  	return 0;
  }

红黑树

红黑树概述

  • 是一种特殊的二叉排序树,每个结点增加一个存储位表示结点的颜色(红色或黑色),是一种弱平衡二叉树,相对于AVL树,它的旋转此=次数少,所以对于搜索、插入、删除操作较多的情况下,通常使用红黑树。如图即为一颗红黑树。
    在这里插入图片描述

红黑树性质

  • 1.结点是红色或是黑色(插入的结点默认为红色)
  • 2.根结点是黑色
  • 3.每个叶子的结点都是黑色的空结点(null)
  • 4.每个红色结点的两个子节点都是黑色的(即不会出现连续的红结点)
  • 5.从任意结点到其每个叶子的所有路径都包含相同的黑色结点(即每一条路径上的黑色结点的数量相同)

可推导出性质

  • 红黑树中最长路径中结点个数不会超过最短路径节点个数的两倍
  • 每一条路径上的黑色结点的数量相同

红黑树较AVL树的优点

  • AVL 树是高度平衡的,频繁的插入和删除,会引起频繁的保持平衡操作,导致效率下降;红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。
  • 红黑树在查找,插入删除的性能都是O(logn),且性能稳定,所以STL里面很多结构包括map底层实现都是使用的红黑树。

结点:RBTNode

  • RBTNode

    enum Color{RED,BLACK};//标记红黑树的颜色
    template<typename T>
    class RBTNode {
    	T m_data;
        RBTNode *m_left = nullptr;
        RBTNode *m_right = nullptr;
        RBTNode *m_parent = nullptr;
        Color m_color = RED;
    public:
    	RBTNode(const T &val) :m_data(val) {  }
    };
    

插入结点

(插入的位置根据BST方式获得)

  • 第一种情况:父为,叔为,祖父为变色
    在这里插入图片描述

  • 第二种情况:父为,叔为(或不存在),祖父为单旋转-变色,此处的旋转结点为祖父

    • 子和父均为左结点:右旋转-变色
    • 子和父均为右结点:左旋转-变色
      在这里插入图片描述
  • 第三种情况:为第二种情况的特殊情况,复合旋转-变色

    • 父为左结点,子为右结点:先左旋(旋转点为父结点)-再右旋(旋转点为祖父结点)-变色
    • 父为右结点,子为左结点:先右旋(旋转点为父结点)-再左旋(旋转点为祖父结点)-变色
      在这里插入图片描述

删除结点

  • 第一种情况:无子节点时,删除节点可能为红色或者黑色;

    • 如果为红色,直接删除即可,不会影响黑色节点的数量;
    • 如果为黑色,则需要进行删除平衡的操作了;
  • 第二种情况:只有一个子节点时,删除节点只能是黑色,其子节点为红色,否则无法满足红黑树的性质了。 此时用删除节点的子节点接到父节点,且将子节点颜色涂黑,保证黑色数量。

  • 第三种情况:有两个子节点时,与二叉搜索树一样,使用后继节点作为替换的删除节点,情形转至为前两种情况。

B树、B+树

B树(B-tree)

  • B树是一种平衡的多路查找树,2-3树和2-3-4树都是B树的特例。结点最大的孩子数目称为B树的阶(order),故2-3树的阶为3,2-3-4树的阶为4。
  • B树的数据结构是为内外存(内存和硬盘(磁盘))的数据的交互准备的
  • 一个m阶的B树具有如下属性
    • 如果根结点不是叶子叶子结点,则其至少有两颗子树
    • 每个非根的分支结点都有k-1个元素和k个孩子,每一个叶子结点都有k-1个元素,其中 ⌈ m / 2 ⌉ ≤ k ≤ m \lceil m/2 \rceil \leq k \leq m m/2km
    • 所有叶子结点都位于同一层次
    • 所有分支结点包含下列信息数据 ( n , A 0 , K 1 , A 1 , K 2 , A 2 , . . . , K n , A n ) (n,A_0,K_1,A_1,K_2,A_2,...,K_n,A_n) (n,A0,K1,A1,K2,A2,...,Kn,An) ,其中 n n n为关键字个数、 K i K_i Ki为关键字、 A i A_i Ai为指向子树根节点的指针
      在这里插入图片描述

B+树

  • B+树是为了解决所有元素遍历等问题,在B树原有基础上添加新的元素组织方式;B+树是应文件系统所需要而出的一种B树的变形树。
  • B+树和B树的差异
    • 有n个子孩子的结点中包含n个关键字
    • 所有的叶子结点包含全部关键字的信息,及指向含这些关键字机理的指针,叶子结点本身一关键字的大小自小到大顺序连接
    • 所有分支可以看成是索引,结点中仅含有其子树中的最大(或最小)关键字
      在这里插入图片描述
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值