数据结构之图(邻接表实现)(C++)

一、图的邻接表实现

1.实现了以顶点顺序表、边链表为存储结构的邻接表;

2.实现了图的创建(有向/无向/图/网)、边的增删操作、深度优先递归/非递归遍历、广度优先遍历的算法;

3.采用顶点对象列表、边(弧)对象列表的方式,对图的创建进行初始化;引用 "ObjArrayList.h"头文件,头文件可参看之前博文“数据结构之顺序列表(支持对象元素)”代码;

4.深度优先遍历分别采用递归/非递归算法;非递归中用到的栈,引用"LinkStack.h"头文件,头文件可参看之前博文“数据结构之栈”代码;

5.广度优先遍历采用队列方式实现;用到的队列,引用 "LinkQueue.h"头文件,头文件可参看之前博文“数据结构之队列”代码;

6.测试代码中以有向网的所有带权边作为边的初始化数据,选择图类型(DG, UDG, DN, UDN)可创建成不同类型的图。

7.优劣分析:

7.1.(优势)邻接表存储结构,相比邻接矩阵,消除了无邻接关系顶点的边存储空间;

7.2.(优势)邻接表比邻接矩阵更容易访问某顶点的邻接顶点;

7.3.(优势)邻接表比邻接矩阵更易于统计边总数,无需逐行逐列遍历;

7.4.(劣势)在确定两顶点间是否有边的搜索过程中,邻接表不如邻接矩阵可直接寻址快,反而需要顶点到边链的遍历;

7.5.(劣势)邻接矩阵在删除顶点时,只需清除对应行列数据即可;而邻接表在清除顶点指向的边链后,还需遍历整个边表,清除所有邻接于此顶点的边结点;

7.6.(不足)邻接表在统计有向图出度时容易,只需遍历依附此顶点的边链;却在统计其入度时,需要遍历整个边表,比较麻烦;可采用十字链表(邻接表与逆邻接表的结合)解决这个问题;

7.7.(不足)邻接表在无向图的存储中,属于行列对称矩阵形式,因此会有一半重复的边数据,故可采用邻接多重表,只存储一半边,来优化存储。

 

二、测试代码中的图结构

深度优先遍历序列(从 v1 顶点开始):

1.无向图/网:v1-v2-v3-v5-v4-v6-v7

2.有向图/网:v1-v2-v5-v3-v4-v6-v7

广度优先遍历序列(从 v2 顶点开始):

1.无向图/网:v2-v1-v3-v5-v4-v6-v7

2.有向图/网:v2-v5 后序无法遍历

注:有向图的遍历 是遵循出度方向遍历的,若无出度方向,则遍历终止。

 

三、代码

//文件名:"GraphAdjList.h"
#pragma once
#ifndef GRAPHADJLISL_H_
#define GRAPHADJLISL_H_

#include <string>
#include "ObjArrayList.h"
using namespace std;

/*
.	图(邻接表实现) Graph Adjacency List
.	相关术语:
.		顶点 Vertex ; 边 Arc ;权 Weight ;
.		有向图 Digraph ;无向图 Undigraph ;
.		有向网 Directed Network ;无向网 Undirected Network ;
.	存储结构:
.		1.顶点表只能采用顺序结构。(因为若采用链式结构,顶点结点定义与边表结点定义就相互引用,无法定义)
.		2.边表采用链表结构。
*/

class GraphAdjList
{
	/*
	.	边表(链表)结点
	*/
	struct ArcNode
	{
		int adjVex;			//邻接顶点所在表中下标
		int weight;			//边权重
		ArcNode * next;		//下一条边
	};
	/*
	.	顶点表(顺序表)结点
	*/
	struct VNode
	{
		string name;		//顶点名
		ArcNode * first;	//指向的第一个依附该顶点的顶点边结点
	};
public:
	/*
	.	图 种类
	*/
	enum GraphType
	{
		DG,			//有向图,默认 0
		UDG,		//无向图,默认 1
		DN,			//有向网,默认 2
		UDN			//无向网,默认 3
	};
	/*
	.	边(弧)数据,注:供外部初始化边数据使用
	*/
	struct ArcData
	{
		string Tail;	//弧尾
		string Head;	//弧头
		int Weight;		//权重
	};
	
private:
	static const int _MAX_VERTEX_NUM = 10;		//支持最大顶点数
	
	VNode vexs[_MAX_VERTEX_NUM];				//顶点表
	int vexs_visited[_MAX_VERTEX_NUM];			//顶点访问标记数组:0|未访问 1|已访问
	int vexNum;			//顶点数
	int arcNum;			//边数
	int type;			//图种类

	void _CreateVexSet(ObjArrayList<string> * vexs);			//创建顶点集合
	void _CreateDG(ObjArrayList<ArcData> * arcsList);			//创建有向图
	void _CreateUDG(ObjArrayList<ArcData> * arcsList);			//创建无向图
	void _CreateDN(ObjArrayList<ArcData> * arcsList);			//创建有向网
	void _CreateUDN(ObjArrayList<ArcData> * arcsList);			//创建无向网

	int _Locate(string vertex);									//定位顶点元素位置
	void _InsertArc(int tail, int head, int weight);			//插入边(元操作,不分有向/无向)
	void _DeleteArc(int tail, int head);						//删除边(元操作,不分有向/无向)
	void _DFS_R(int index);										//深度优先遍历 递归
	void _DFS(int index);										//深度优先遍历 非递归

public:
	GraphAdjList(int type);				//构造函数:初始化图种类
	~GraphAdjList();					//析构函数
	void Init(ObjArrayList<string> * vexs, ObjArrayList<ArcData> * arcsList);		//初始化顶点、边数据为 图|网
	void InsertArc(ArcData * arcData);			//插入边(含有向/无向操作)
	void DeleteArc(ArcData * arcData);			//删除边(含有向/无向操作)
	void Display();								//显示 图|网
	void Display_DFS_R(string *vertex);		 	//从指定顶点开始,深度优先 递归 遍历
	void Display_DFS(string *vertex);			//从指定顶点开始,深度优先 非递归 遍历
	void Display_BFS(string *vertex);			//从指定顶点开始,广度优先遍历
	
};

 

//文件名:"GraphAdjList.cpp"
#include "stdafx.h"
#include <string>
#include "ObjArrayList.h"
#include "LinkQueue.h"
#include "LinkStack.h"
#include "GraphAdjList.h"
using namespace std;

GraphAdjList::GraphAdjList(int type)
{
	/*
	.	构造函数:初始化图类型
	*/
	this->type = type;
	this->vexNum = 0;
	this->arcNum = 0;
}

GraphAdjList::~GraphAdjList()
{
	/*
	.	析构函数:销毁图
	*/
}

void GraphAdjList::Init(ObjArrayList<string> * vexs, ObjArrayList<ArcData> * arcsList)
{
	/*
	.	初始化顶点、边数据,并构建 图|网
	.	入参:
	.		vexs: 顶点 列表
	.		arcsList: 边数据 列表
	*/
	//1.创建顶点集
	_CreateVexSet(vexs);
	//2.根据图类型,创建指定的图
	switch (this->type)
	{
	case DG:
		_CreateDG(arcsList); break;
	case UDG:
		_CreateUDG(arcsList); break;
	case DN:
		_CreateDN(arcsList); break;
	case UDN:
		_CreateUDN(arcsList); break;
	default:
		break;
	}
}

void GraphAdjList::_CreateVexSet(ObjArrayList<string> * vexs)
{
	/*
	.	创建顶点集合
	*/
	string vertex = "";
	//顶点最大数校验
	if (vexs->Length() > this->_MAX_VERTEX_NUM)
	{
		return;
	}
	//遍历顶点表,无重复插入顶点,并计数顶点数
	for (int i = 0; i < vexs->Length(); i++)
	{
		vertex = *vexs->Get(i);
		if (_Locate(vertex) == -1)
		{
			this->vexs[this->vexNum].name = vertex;
			this->vexs[this->vexNum].first = NULL;
			this->vexNum++;
		}
	}
}

void GraphAdjList::_CreateDG(ObjArrayList<ArcData> * arcsList)
{
	/*
	.	创建有向图
	.	邻接矩阵为 非对称边
	*/
	//初始化临时 边对象
	ArcData * arcData = NULL;
	//初始化 Tail Head 顶点下标索引
	int tail = 0, head = 0;
	//遍历边数据列表
	for (int i = 0; i < arcsList->Length(); i++)
	{
		//按序获取边(弧)
		arcData = arcsList->Get(i);
		//定位(或设置)边的两端顶点位置
		tail = _Locate(arcData->Tail);
		head = _Locate(arcData->Head);
		//插入边
		_InsertArc(tail, head, 0);
	}
}

void GraphAdjList::_CreateUDG(ObjArrayList<ArcData> * arcsList)
{
	/*
	.	创建无向图
	.	邻接矩阵为 对称边
	*/
	//初始化临时 边对象
	ArcData * arcData = NULL;
	//初始化 Tail Head 顶点下标索引
	int tail = 0, head = 0;
	//遍历边数据列表
	for (int i = 0; i < arcsList->Length(); i++)
	{
		//按序获取边(弧)
		arcData = arcsList->Get(i);
		//定位(或设置)边的两端顶点位置
		tail = _Locate(arcData->Tail);
		head = _Locate(arcData->Head);
		//插入对称边
		_InsertArc(tail, head, 0);
		_InsertArc(head, tail, 0);
	}
}

void GraphAdjList::_CreateDN(ObjArrayList<ArcData> * arcsList)
{
	/*
	.	创建有向网
	.	邻接矩阵为 非对称矩阵
	*/
	//初始化临时 边对象
	ArcData * arcData = NULL;
	//初始化 Tail Head 顶点下标索引
	int tail = 0, head = 0;
	//遍历边数据列表
	for (int i = 0; i < arcsList->Length(); i++)
	{
		//按序获取边(弧)
		arcData = arcsList->Get(i);
		//定位(或设置)边的两端顶点位置
		tail = _Locate(arcData->Tail);
		head = _Locate(arcData->Head);
		//插入边
		_InsertArc(tail, head, arcData->Weight);
	}
}

void GraphAdjList::_CreateUDN(ObjArrayList<ArcData> * arcsList)
{
	/*
	.	创建无向网
	.	邻接矩阵为 对称矩阵
	*/
	//初始化临时 边对象
	ArcData * arcData = NULL;
	//初始化 Tail Head 顶点下标索引
	int tail = 0, head = 0;
	//遍历边数据列表
	for (int i = 0; i < arcsList->Length(); i++)
	{
		//按序获取边(弧)
		arcData = arcsList->Get(i);
		//定位(或设置)边的两端顶点位置
		tail = _Locate(arcData->Tail);
		head = _Locate(arcData->Head);
		//插入对称边
		_InsertArc(tail, head, arcData->Weight);
		_InsertArc(head, tail, arcData->Weight);
	}
}

int GraphAdjList::_Locate(string vertex)
{
	/*
	.	定位顶点元素位置
	.		后期可改成【字典树】,顶点数超过100个后定位顶点位置可更快
	*/
	//遍历定位顶点位置
	for (int i = 0; i < this->_MAX_VERTEX_NUM; i++)
	{
		if (vertex == this->vexs[i].name)
		{
			return i;
		}
	}
	//cout << endl << "顶点[" << vertex << "]不存在。" << endl;
	return -1;
}

void GraphAdjList::_InsertArc(int tail, int head, int weight)
{
	/*
	.	插入边(元操作,不分有向/无向)
	*/
	//边结点指针:初始化为 弧尾 指向的第一个边
	ArcNode * p = this->vexs[tail].first;
	//初始化 前一边结点的指针
	ArcNode * q = NULL;
	//重复边布尔值
	bool exist = false;
	//1.边的重复性校验
	while (p != NULL)
	{
		//若已存在该边,则标记为 存在 true
		if (p->adjVex == head)
		{
			exist = true;
			break;
		}
		//若不是该边,继续下一个边校验
		q = p;
		p = p->next;
	}
	//2.1.如果边存在,则跳过,不做插入
	if (exist)
		return;
	//2.2.边不存在时,创建边
	ArcNode * newArc = new ArcNode();
	newArc->adjVex = head;
	newArc->weight = weight;
	newArc->next = NULL;
	//3.1.插入第一条边
	if (q == NULL)
	{
		this->vexs[tail].first = newArc;
	}
	//3.2.插入后序边
	else
	{
		q->next = newArc;
	}
	//4.边 计数
	this->arcNum++;
}

void GraphAdjList::InsertArc(ArcData * arcData)
{
	/*
	.	插入边(含有向/无向操作)
	*/
	//初始化 Tail Head 顶点下标索引
	int tail = 0, head = 0;
	tail = _Locate(arcData->Tail);
	head = _Locate(arcData->Head);
	//根据图类型,插入边
	switch (this->type)
	{
	case DG:
		_InsertArc(tail, head, 0);
		break;
	case UDG:
		_InsertArc(tail, head, 0);
		_InsertArc(head, tail, 0);
		break;
	case DN:
		_InsertArc(tail, head, arcData->Weight);
		break;
	case UDN:
		_InsertArc(tail, head, arcData->Weight);
		_InsertArc(head, tail, arcData->Weight);
		break;
	default:
		break;
	}
}

void GraphAdjList::_DeleteArc(int tail, int head)
{
	/*
	.	删除边(元操作,不分有向/无向)
	*/
	//边结点指针:初始化为 弧尾 指向的第一个边
	ArcNode * p = this->vexs[tail].first;
	//初始化 前一边结点的指针
	ArcNode * q = NULL;
	//1.遍历查找边
	while (p != NULL)
	{
		//若存在该边,则结束循环
		if (p->adjVex == head)
		{
			break;
		}
		//若不是该边,继续下一个边
		q = p;
		p = p->next;
	}
	//2.1.边不存在
	if (p == NULL)
	{
		cout << endl << "边[" << this->vexs[head].name << "->" << this->vexs[head].name << "]不存在。" << endl;
		return;
	}
	//2.2.边存在,删除边
	//2.2.1.若为第一条边
	if (q == NULL)
	{
		this->vexs[tail].first = p->next;
	}
	//2.2.2.非第一条边
	else
	{
		q->next = p->next;
	}
	//3.释放 p
	delete p;
}

void GraphAdjList::DeleteArc(ArcData * arcData)
{
	/*
	.	删除边(含有向/无向操作)
	*/
	//初始化 Tail Head 顶点下标索引
	int tail = 0, head = 0;
	tail = _Locate(arcData->Tail);
	head = _Locate(arcData->Head);
	//根据图类型,删除边
	switch (this->type)
	{
	case DG:
		_DeleteArc(tail, head);
		break;
	case UDG:
		_DeleteArc(tail, head);
		_DeleteArc(head, tail);
		break;
	case DN:
		_DeleteArc(tail, head);
		break;
	case UDN:
		_DeleteArc(tail, head);
		_DeleteArc(head, tail);
		break;
	default:
		break;
	}
}

void GraphAdjList::Display()
{
	/*
	.	显示 图|网
	*/
	//初始化边表结点指针
	ArcNode * p = NULL;
	cout << endl << "邻接表:" << endl;
	//遍历顶点表
	for (int i = 0; i < this->_MAX_VERTEX_NUM; i++)
	{
		//空顶点(在删除顶点的操作后会出现此类情况)
		if (this->vexs[i].name == "")
		{
			continue;
		}
		//输出顶点
		cout << "[" << i << "]" << this->vexs[i].name << " ";
		//遍历输出边顶点
		p = this->vexs[i].first;
		while (p != NULL)
		{
			cout << "[" << p->adjVex << "," << p->weight << "] ";
			p = p->next;
		}
		cout << endl;
	}
}

void GraphAdjList::_DFS_R(int index)
{
	/*
	.	深度优先遍历 递归
	*/
	//1.访问顶点,并标记已访问
	cout << this->vexs[index].name << " ";
	this->vexs_visited[index] = 1;
	//2.遍历访问其相邻顶点
	ArcNode * p = this->vexs[index].first;
	int adjVex = 0;
	while (p != NULL)
	{
		adjVex = p->adjVex;
		//当顶点未被访问过时,可访问
		if (this->vexs_visited[adjVex] != 1)
		{
			_DFS_R(adjVex);
		}
		p = p->next;
	}
}

void GraphAdjList::Display_DFS_R(string *vertex)
{
	/*
	.	从指定顶点开始,深度优先 递归 遍历
	*/
	//1.判断顶点是否存在
	int index = _Locate(*vertex);
	if (index == -1)
		return;
	//2.初始化顶点访问数组
	for (int i = 0; i < this->_MAX_VERTEX_NUM; i++)
	{
		this->vexs_visited[i] = 0;
	}
	//3.深度优先遍历 递归
	cout << "深度优先遍历(递归):(从顶点" << *vertex << "开始)" << endl;
	_DFS_R(index);
}

void GraphAdjList::_DFS(int index)
{
	/*
	.	深度优先遍历 非递归
	*/
	//1.访问第一个结点,并标记为 已访问
	cout << this->vexs[index].name << " ";
	vexs_visited[index] = 1;
	//初始化 边结点 栈
	LinkStack<ArcNode> * s = new LinkStack<ArcNode>();
	//初始化边结点 指针
	ArcNode * p = this->vexs[index].first;
	//2.寻找下一个(未访问的)邻接结点
	while (!s->Empty() || p != NULL)
	{
		//2.1.未访问过,则访问 (纵向遍历)
		if (vexs_visited[p->adjVex] != 1)
		{
			//访问结点,标记为访问,并将其入栈
			cout << this->vexs[p->adjVex].name << " ";
			vexs_visited[p->adjVex] = 1;
			s->Push(p);
			//指针 p 移向 此结点的第一个邻接结点
			p = this->vexs[p->adjVex].first;
		}
		//2.2.已访问过,移向下一个边结点 (横向遍历)
		else
			p = p->next;
		//3.若无邻接点,则返回上一结点层,并出栈边结点
		if (p == NULL)
		{
			p = s->Pop();
		}
	}
	//释放 栈
	delete s;
}

void GraphAdjList::Display_DFS(string *vertex)
{
	/*
	.	从指定顶点开始,深度优先 非递归 遍历
	*/
	//1.判断顶点是否存在
	int index = _Locate(*vertex);
	if (index == -1)
		return;
	//2.初始化顶点访问数组
	for (int i = 0; i < this->_MAX_VERTEX_NUM; i++)
	{
		this->vexs_visited[i] = 0;
	}
	//3.深度优先遍历 递归
	cout << "深度优先遍历(非递归):(从顶点" << *vertex << "开始)" << endl;
	_DFS(index);
}

void GraphAdjList::Display_BFS(string *vertex)
{
	/*
	.	从指定顶点开始,广度优先遍历
	*/
	//1.判断顶点是否存在
	int index = _Locate(*vertex);
	if (index == -1)
		return;
	//2.初始化顶点访问数组
	for (int i = 0; i < this->_MAX_VERTEX_NUM; i++)
	{
		this->vexs_visited[i] = 0;
	}
	//3.广度优先遍历
	cout << "广度优先遍历:(从顶点" << *vertex << "开始)" << endl;
	//3.1.初始化队列
	LinkQueue<int> * vexQ = new LinkQueue<int>();
	//3.2.访问开始顶点,并标记访问、入队
	cout << this->vexs[index].name << " ";
	this->vexs_visited[index] = 1;
	vexQ->EnQueue(new int(index));
	//3.3.出队,并遍历邻接顶点(下一层次),访问后入队
	ArcNode * p = NULL;
	int adjVex = 0;
	while (vexQ->GetHead() != NULL)
	{
		index = *vexQ->DeQueue();
		p = this->vexs[index].first;
		//遍历邻接顶点
		while (p != NULL)
		{
			adjVex = p->adjVex;
			//未访问过的邻接顶点
			if (this->vexs_visited[adjVex] != 1)
			{
				//访问顶点,并标记访问、入队
				cout << this->vexs[adjVex].name << " ";
				this->vexs_visited[adjVex] = 1;
				vexQ->EnQueue(new int(adjVex));
			}
			p = p->next;
		}
	}

	//4.释放队列
	int * e;
	while (vexQ->GetHead() != NULL)
	{
		e = vexQ->DeQueue();
		delete e;
	}
	delete vexQ;
}
//文件名:"GraphAdjList_Test.cpp"
#include "stdafx.h"
#include <iostream>
#include "GraphAdjList.h"
#include "ObjArrayList.h"
using namespace std;

int main()
{
	//初始化顶点数据
	string * v1 = new string("v1");
	string * v2 = new string("v2");
	string * v3 = new string("v3");
	string * v4 = new string("v4");
	string * v5 = new string("v5");
	string * v6 = new string("v6");
	string * v7 = new string("v7");
	ObjArrayList<string> * vexs = new ObjArrayList<string>();
	vexs->Add(v1);
	vexs->Add(v2);
	vexs->Add(v3);
	vexs->Add(v4);
	vexs->Add(v5);
	vexs->Add(v6);
	vexs->Add(v7);

	//初始化边(弧)数据
	GraphAdjList::ArcData * arc1 = new GraphAdjList::ArcData{ "v1", "v2", 2 };
	GraphAdjList::ArcData * arc2 = new GraphAdjList::ArcData{ "v1", "v3", 3 };
	GraphAdjList::ArcData * arc3 = new GraphAdjList::ArcData{ "v1", "v4", 4 };
	GraphAdjList::ArcData * arc4 = new GraphAdjList::ArcData{ "v3", "v1", 5 };
	GraphAdjList::ArcData * arc5 = new GraphAdjList::ArcData{ "v3", "v2", 6 };
	GraphAdjList::ArcData * arc6 = new GraphAdjList::ArcData{ "v3", "v5", 7 };
	GraphAdjList::ArcData * arc7 = new GraphAdjList::ArcData{ "v2", "v5", 8 };
	GraphAdjList::ArcData * arc8 = new GraphAdjList::ArcData{ "v4", "v6", 9 };
	GraphAdjList::ArcData * arc9 = new GraphAdjList::ArcData{ "v4", "v7", 9 };
	GraphAdjList::ArcData * arc10 = new GraphAdjList::ArcData{ "v6", "v7", 9 };
	ObjArrayList<GraphAdjList::ArcData> * arcsList = new ObjArrayList<GraphAdjList::ArcData>();
	arcsList->Add(arc1);
	arcsList->Add(arc2);
	arcsList->Add(arc3);
	arcsList->Add(arc4);
	arcsList->Add(arc5);
	arcsList->Add(arc6);
	arcsList->Add(arc7);
	arcsList->Add(arc8);
	arcsList->Add(arc9);
	arcsList->Add(arc10);

	//测试1:无向图
	cout << endl << "无向图初始化:" << endl;
	GraphAdjList * udg = new GraphAdjList(GraphAdjList::UDG);
	udg->Init(vexs, arcsList);
	udg->Display();
	//1.1.深度优先遍历
	cout << endl << "无向图深度优先遍历序列:(递归)" << endl;
	udg->Display_DFS_R(v1);
	cout << endl << "无向图深度优先遍历序列:(非递归)" << endl;
	udg->Display_DFS(v1);
	//1.2.广度优先遍历
	cout << endl << "无向图广度优先遍历序列:" << endl;
	udg->Display_BFS(v2);
	//1.3.插入新边
	cout << endl << "无向图新边:" << endl;
	udg->InsertArc(new GraphAdjList::ArcData{ "v7", "v1", 8 });
	udg->Display();
	//1.4.删除边
	cout << endl << "无向图删除边arc9:" << endl;
	udg->DeleteArc(arc9);
	udg->Display();

	//测试2:有向图
	cout << endl << "有向图:" << endl;
	GraphAdjList * dg = new GraphAdjList(GraphAdjList::DG);
	dg->Init(vexs, arcsList);
	dg->Display();
	//2.1.深度优先遍历
	cout << endl << "有向图深度优先遍历序列:(递归)" << endl;
	dg->Display_DFS_R(v1);
	cout << endl << "有向图深度优先遍历序列:(非递归)" << endl;
	dg->Display_DFS(v1);
	//2.2.广度优先遍历
	cout << endl << "有向图广度优先遍历序列:" << endl;
	dg->Display_BFS(v2);

	//测试:无向网
	cout << endl << "无向网:" << endl;
	GraphAdjList * udn = new GraphAdjList(GraphAdjList::UDN);
	udn->Init(vexs, arcsList);
	udn->Display();

	//测试:有向网
	cout << endl << "有向网:" << endl;
	GraphAdjList * dn = new GraphAdjList(GraphAdjList::DN);
	dn->Init(vexs, arcsList);
	dn->Display();
	
	return 0;
}

 

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值