图的各类算法实现

图的各类算法实现

实验内容:
设图结点的元素类型为char,建立一个不少于8个顶点的带权无向图G,实现以下图的各种基本操作的程序:
① 用邻接矩阵作为储结构存储图G并输出该邻接矩阵;
② 按DFS算法输出图G中顶点的遍历序列;
③ 按BFS算法输出图G中顶点的遍历序列;
④ 按Prime算法从某个指定的顶点出发输出图G的最小生成树

输入样例:
a b c d e f g h i j k l //顶点数据
0 1 10 //边的数据 (v1 v2 权值)
0 2 4
0 3 30
1 4 11
1 5 20
2 5 5
2 6 60
3 7 55
4 8 8
4 9 100
6 9 34
6 11 19
8 9 9
9 10 8

测试数据

input1.txt

a b c d e f g h i j k l
0  1   10
0  2   4
0  3   30
1  4   11
1  5   20
2  5   5
2  6   60
3  7   55
4  8   8
4  9   100
6  9   34
6  11  19
8  9   9
9  10  8

output1.txt

a b c d e f g h i j k l
0    10   4    30   1000 1000 1000 1000 1000 1000 1000 1000
10   0    1000 1000 11   20   1000 1000 1000 1000 1000 1000
4    1000 0    1000 1000 5    60   1000 1000 1000 1000 1000
30   1000 1000 0    1000 1000 1000 55   1000 1000 1000 1000
1000 11   1000 1000 0    1000 1000 1000 8    100  1000 1000
1000 20   5    1000 1000 0    1000 1000 1000 1000 1000 1000
1000 1000 60   1000 1000 1000 0    1000 1000 34   1000 19
1000 1000 1000 55   1000 1000 1000 0    1000 1000 1000 1000
1000 1000 1000 1000 8    1000 1000 1000 0    9    1000 1000
1000 1000 1000 1000 100  1000 34   1000 9    0    8    1000
1000 1000 1000 1000 1000 1000 1000 1000 1000 8    0    1000
1000 1000 1000 1000 1000 1000 19   1000 1000 1000 1000 0
a b e i j g c f l k d h
a b c d e f g h i j l k
a c 4
c f 5
a b 10
b e 11
e i 8
i j 9
j k 8
a d 30
g j 34
g l 19
d h 55

代码体
AdjMGraph.h

/*
设图结点的元素类型为char,建立一个不少于8个顶点的带权无向图G,实现以下图的各种基本操作的程序:
① 用邻接矩阵作为储结构存储图G并输出该邻接矩阵;
② 按DFS算法输出图G中顶点的遍历序列;
③ 按BFS算法输出图G中顶点的遍历序列;
④ 按Prime算法从某个指定的顶点出发输出图G的最小生成树;
*/
#ifndef ADJMGRAPH_H
#define ADJMGRAPH_H


#include "SeqList.h"//顺序表头文件
#include "SeqCQueue.h"//顺序循环队列
#include "SeqList.h"  //顺序堆栈


#define MaxVertices 50
#define MaxWeight 1000

typedef char VDataType;

typedef struct{
	SeqList Vertices;//存放顶点的顺序表
	int edge[MaxVertices][MaxVertices];//存放边的邻接矩阵
	int numOfEdges;//边的个数
}AdjMGraph;//图的结构体定义


//最小生成树的存储结构体
typedef struct{
	int v1;  
	int v2;
	int weight;
}MinSpanTreeEage;

void AdjInitiate(AdjMGraph*G,int n);

int  InsertVertex(AdjMGraph*G,VDataType vertex);

int  DeleteEdge(AdjMGraph*G,int v1,int v2);

int  InsertEdge(AdjMGraph*G,int v1,int v2,int weight);

int  GetFirstVex(AdjMGraph G,int c);

int  GetNextVex(AdjMGraph G,int v1,int v2);

void CreateGraph(AdjMGraph* G, FILE* file );

void PrintGraph(AdjMGraph G );

void DepthFSearch(AdjMGraph G,int v ,int visited[],VDataType* result,int* count );

void BroadFSearch(AdjMGraph G,int v,int visited[],VDataType* result,int* count );

void Prime(AdjMGraph G,MinSpanTreeEage closeVertex[]);

#endif

SeqCQueue.h

#ifndef SEQCQUEUE_H
#define SEQCQUEUE_H
#include <stdio.h>
#include <stdlib.h>


#define MaxQueueSize 100
typedef int QDataType;
//顺序循环队列的结构体
typedef struct Queue{ 

	QDataType queue[MaxQueueSize];
	int rear;  //队尾指针
	int front;  //队头指针
	int count;  //计数器
	
} SeqCQueue; 

void QueueInitiate(SeqCQueue *Q);

int  QueueNotEmpty(SeqCQueue Q);

int  QueueAppend(SeqCQueue *Q, QDataType x);

int  QueueDelete(SeqCQueue *Q, QDataType *d);

int  QueueGet(SeqCQueue Q, QDataType *d);
 


#endif

SeqList.h

#ifndef SEQLIST_H
#define SEQLIST_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define MaxSize 100
typedef char ItemType;

typedef struct List{

    ItemType list[MaxSize];	
	int length;
} SeqList;

void ListInitiate(SeqList *L);
int ListInsert(SeqList *L, int i, ItemType x);
int ListDelete(SeqList *L, int i, ItemType *x);
int ListGet(SeqList L, int i, ItemType *x);
#endif

SeqStack.h

#ifndef SEQSTACK_H
#define SEQSTACK_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define MaxStackSize 100

typedef int DataType;

typedef struct Stack
{	
	DataType stack[MaxStackSize];			
	int top;
} SeqStack;


void StackInitiate(SeqStack *S);	

int StackNotEmpty(SeqStack S);

int StackPush(SeqStack *S, DataType x);


int StackPop(SeqStack *S, DataType *d);

int StackTop(SeqStack S, DataType *d);

#endif

SeqCQueue.c

#include "SeqCQueue.h"

//初始化QueueInitiate(Q)
void QueueInitiate(SeqCQueue *Q)
{
	Q->rear = 0;		
	Q->front = 0;
	Q->count = 0;
}


//判断循环队列Q非空否,非空则返回1,否则返回0
int QueueNotEmpty(SeqCQueue Q)
{
	if(Q.count != 0)	return 1;
	else return 0;
}


//把数据元素值x插入顺序循环队列Q的队尾,成功返回0,失败返回-1
int QueueAppend(SeqCQueue *Q, QDataType x)
{
	if(Q->count > 0 && Q->rear == Q->front)
	{	
		printf("The Queue is full! \n");
		return -1;
	}
	else
	{	
		Q->queue[Q->rear] = x;
		Q->rear = (Q->rear + 1) % MaxQueueSize;
		Q->count++;
		return 0;
	}
}


//删除顺序循环队列Q的队头元素并赋值给d,成功则返回0,失败返回-1
int QueueDelete(SeqCQueue *Q, QDataType *d)
{
	if(Q->count == 0){
		printf("The Queue is empty! \n");
		return -1;
	}
	else
	{	*d = Q->queue[Q->front];
		Q->front = (Q->front + 1) % MaxQueueSize;
		Q->count--;
		return 0;
	}
}

//取队头数据元素 QueueGet(Q, d)
int QueueGet(SeqCQueue Q, QDataType *d)
{
	if(Q.count == 0)
	{
		printf("The Queue is empty!\n");
		return -1;
	}
	else
	{
		*d = Q.queue[Q.front];
		return 0;
	}
}

SeqList.c

#include "SeqList.h"

//初始化   
void ListInitiate(SeqList *L)	 
{	
	L->length= 0;  /*定义初始数据元素个数*/                  
}

//求当前数据元素个数  
int ListLength(SeqList L)		 
{	
	return L.length;
} 

//在顺序表L的第i(0≤i ≤ length-1)个位置插入数据元素x
//插入成功返回0,失败返回-1
int ListInsert(SeqList *L, int i, ItemType x)
{	int j;
	if(L->length >MaxSize)
	{
		printf("The list is full!");
		return -1;
	}
	else if(i<0||i>L->length)
	{
		printf("The parameter i is illegal!\n");
		return -1;
	}  else 
	     {
		for(j = L->length; j > i; j--)
    	    		L->list[j] = L->list[j-1];             	 /*依次后移*/
    	L->list[i] = x;
		L->length ++;
		return 0;
	   }
}

//删除顺序表L中位置i(0≤i ≤ length-1)上的数据元素并保存到x中
//删除成功返回0,删除失败返回-1   
int ListDelete(SeqList *L, int i, ItemType *x)	

{	int j;
	if(L->length <=0)
	{
		printf("The list is empty!");
		return -1;
	}
	else if(i<0||i>L->length-1)
	{
		printf("The parameter i is illegal!");
		return -1;
	}  else 
		{
			*x = L->list[i];		/*保存删除的元素到x中*/
			for(j=i+1; j<=L->length-1; j++)
       			L->list[j-1] = L->list[j];      	/*依次前移*/
			L->length--;			/*数据元素个数减1*/
			return 0;
		}
}

//取数据元素,成功返回0,失败返回-1     
int ListGet(SeqList L, int i, ItemType *x)
{	if(i < 0 || i > L.length-1)
	{
	    printf("The parameter i is illegal!");
	    return -1;
	}
	else
	{
	    *x = L.list[i];
	    return 0;
	}
}

SeqStack.c

#include "SeqStack.h"

//初始化   
void StackInitiate(SeqStack *S)	
{
	S->top = -1;		
}

//非空否   
int StackNotEmpty(SeqStack S)
{
	if(S.top < 0)	return 0;
	else return 1;
}


//把数据元素值x存入顺序堆栈S中,入栈成功则返回0,失败返回-1
int StackPush(SeqStack *S, DataType x)
{
	if(S->top > MaxStackSize-1)
	{	
		printf("Stack is full!");	
		return -1;
	}
	else
	{	
	    S->top ++;
		S->stack[S->top] = x;
		
		return 0;
	}
}

//出栈 ,成功则返回0,失败返回-1  
int StackPop(SeqStack *S, DataType *d)
{
	if(S->top < 0)
	{	
		printf("The stack is empty!");
		return -1;
	}
	else
	{	

		*d = S->stack[S->top];
		S->top --;
		return 0;
	}
}
//取栈顶数据元素 
int StackTop(SeqStack S, DataType *d)
{
	if(S.top <0)
	{	
		printf("The stack is empty!");
		return -1;
	}
	else
	{	*d = S.stack[S.top];
		return 0;
	}
}

AdjMGraph.c

#include "AdjMGraph.h"
//初始化最多可以存放n个顶点的顶点顺序表和邻接矩阵
void AdjInitiate(AdjMGraph*G,int n){
	int i,j;
	for(i=0;i<n;i++){
		for(j=0;j<n;j++){
			if(i==j)G->edge[i][j]=0;
			else G->edge[i][j]=MaxWeight;
		}
	}
	G->numOfEdges=0;
	ListInitiate(&G->Vertices);
}

//在图中插入顶点vertex,顶点序号自动分配,成功返回0,失败返回-1
int InsertVertex(AdjMGraph*G, VDataType vertex){

	return ListInsert(&(G->Vertices),G->Vertices.length,vertex);
	
}

//在图中插入边<v1,v2>,v1,v2为顶点序号,权值为weight,成功返回0, 失败返回-1
int InsertEdge(AdjMGraph*G,int v1,int v2,int weight){

	if(v1<0||v1>=G->Vertices.length||v2<0||v2>=G->Vertices.length){
		printf("parameter v1 or parameter v2 is illegal!",v1,v2);
		return -1;
	}
	G->edge[v1][v2]=weight;//无向边;邻接矩阵为对称矩阵
	G->edge[v2][v1]=weight;
	G->numOfEdges++;
	return 0;
}

//删除边<v1,v2>,v1,v2为顶点序号,成功返回0, 失败返回-1
int DeleteEdge(AdjMGraph*G,int v1,int v2){

	if(v1<0||v1>=G->Vertices.length||v2<0||v2>=G->Vertices.length||v1==v2){
		printf("parameter v1 or parameter v2 is illegal!",v1,v2);
		return -1;
	}
	if(G->edge[v1][v2]==MaxWeight||v1==v2){
		printf("The edge is not exist!");
		return -1 ;
	}
	G->edge[v1][v2]=MaxWeight;
	G->edge[v2][v1]=MaxWeight;
	G->numOfEdges--;
	return 0;
}
//取第一个邻接顶点序号,没有则返回-1
int GetFirstVex(AdjMGraph G,int v){
	int col;
	if(v<0||v>=G.Vertices.length){
		printf("parameter v is illegal!");
			return -1;
	}
	for(col=0;col<G.Vertices.length;col++){
		if(G.edge[v][col]>0&&G.edge[v][col]<MaxWeight)
			return col;
	}
	return -1;
}

//取下一个邻接顶点序号,没有则返回-1
int GetNextVex(AdjMGraph G,int v1,int v2){
	int col;
	if(v1<0||v1>=G.Vertices.length||v2<0||v2>=G.Vertices.length){
		printf("parameter v1 or parameter v2 is illegal!");
			return -1;
	}
	for(col=v2+1;col<G.Vertices.length;col++){
		if(G.edge[v1][col]>0&&G.edge[v1][col]<MaxWeight)
			return col;
	}
	return -1;
}

//从文件中读取数据,构建图
void CreateGraph(AdjMGraph* G, FILE* file ){

	int i, k;
	int n=0;
	char c;
	int v1,v2,weight;
	char vdata[100];
	fscanf(file,"%c",&c);
	while(c!='\n'){
		if(c>='a'&&c<='z'){
			 vdata[n]=c;
			 n++;
		}
		fscanf(file,"%c",&c);
	}	
	AdjInitiate(G,n);//初始化顺序表和邻接矩阵
	for(i=0;i<n;i++)
		InsertVertex(G,vdata[i]);//插入顶点
		
	
	int numberOfEges=0;
	while(fscanf(file,"%d %d %d",&v1,&v2,&weight)!=EOF){
		InsertEdge(G,v1,v2,weight);//插入边
		numberOfEges++;
	}
		
	G->numOfEdges=numberOfEges;
}

//打印图的信息
void PrintGraph(AdjMGraph G ){
	int n= G.Vertices.length;
	int i,j;

	for( i=0; i<n;i++){
		for( j=0;j<n;j++)
		 printf("%-4d ",G.edge[i][j]);
		printf("\n");
	}

}
//深度优先遍历
//连通图G以v为初始顶点的深度优先遍历,遍历结果放入 result[],count为resualt数组中元素个数
//数组visitted标记相应顶点是否已被访问过
void DPS(AdjMGraph G,int visit[],int a,VDataType* result,int* count){
	int j;  
	result[(*count)]=G.Vertices.list[a];
	*count=(*count)+1;
    visit[a]=1;          
    for(j=0;j<G.Vertices.length;j++)           //依次搜索vi邻接点  
        if(G.edge[a][j]!=1000 && visit[j]==0&& a!=j)  
            DPS(G, visit,j,result,count);  
}


void DepthFSearch(AdjMGraph G,int v ,int visited[],VDataType* result,int* count )
{
	for (v = 0; v < G.Vertices.length; v++)
	{
		if (visited[v]==0){
			DPS(G, visited,v,result,count);    
		}	
	}
	
}


//广度优先遍历
//连通图G以v为初始顶点的广度优先遍历,遍历结果放入 result[],count为resualt数组中元素个数
//数组visited标记相应顶点是否已被访问过

void BFS(AdjMGraph G,int visit[],int k,VDataType* result,int* count)
{
	int i,j;  
    SeqCQueue Q;  
    QueueInitiate(&Q);  
    result[(*count)]=G.Vertices.list[k];
	*count=(*count)+1;
	
    visit[k]=1;  
    QueueAppend(&Q,k);  
    while (QueueNotEmpty(Q))  
    {  
        QueueDelete(&Q,&i);  
        for (j=0;j<G.Vertices.length;j++)  
            if(G.edge[i][j]!=1000 && visit[j]==0&& i!=j)  
            {  
                result[(*count)]=G.Vertices.list[j];
				*count=(*count)+1;
                visit[j]=1;  
                QueueAppend(&Q,j); 
            }  
    }  
}
void BroadFSearch(AdjMGraph G,int v,int visited[],VDataType result[],int* count ){
	for (v = 0; v < G.Vertices.length; v++)
	{
		 visited[v]=0;  
	}
	for (v = 0; v < G.Vertices.length; v++)
	{
		if (visited[v]==0){
			BFS(G, visited,v,result,count);    
		}	
	}
}

//使用普里姆算法,从序号为0的点开始求最小生成树
//求得的最小生成树存入  MinSpanTreeEage closeVertex[] 数组中,数组中每个元素表示最小生成树的一条边,MinSpanTreeEage 结构体定义如下
//这里的边是从下标为1开始存放,也就是说closeVertex[1]是第一条边,而closeVertex[0]没有任何作用,main函数打印所有边的时候从closeVertex[1]到closeVertex[n-1]
/*
typedef struct{
	int v1;    //最小生成树的边的起始点序号  (边中序号小的为起始点)
	int v2;    //最小生成树的边的终点序号 (边中序号大的为终点)
	int weight;  //边的权值
}MinSpanTreeEage;  //表示最小生成树的一条边
*/
void Prime(AdjMGraph G,MinSpanTreeEage closeVertex[])
{
	int t=0;
	int min, i, j ,k;
    int adjvex[MaxVertices]; //保存相关顶点下标
    int lowcost[MaxVertices]; //保存相关顶点间的权值
    lowcost[0]=0; //初始化第一个权值为0,cost为0,即下标为0的顶点,v_{0}加入生成树
    adjvex[0] = 0; //初始化第一个顶点 下标为0
    //将第一行的边,存储到lowcost
    for (i=1;i<G.Vertices.length;i++)  //循环除了下标为0外的全部顶点
    {
        lowcost[i] = G.edge[0][i]; //将v_{0}顶点与之有边的权值存入数组
        adjvex[i]=0;   //初始化都为v_{0}的下标
    }
    for (i=1;i<G.Vertices.length;i++)
    {
        min = 1000;
        j = 1; k = 0;
        while (j<G.Vertices.length)  //循环全部顶点
        {
            if(lowcost[j]!=0&&lowcost[j]<min)
            {
                min = lowcost[j];
                k=j;   //将当前最小值的下标存入k,k存储下一步的结点的标记
            }
            j++; //??为什么要j++
        } //得到min{{生成树的顶点的集合到其他各个顶点的最短距离}
        
        ++t;
        if(k<adjvex[k])
        {
       	 	closeVertex[t].v1=k;
	    	closeVertex[t].v2=adjvex[k];
	    	closeVertex[t].weight=G.edge[adjvex[k]][k];
	    }
	    else
	    {
	    	closeVertex[t].v1=adjvex[k];
	    	closeVertex[t].v2=k;
	    	closeVertex[t].weight=G.edge[adjvex[k]][k];
	    }
        
        lowcost[k] = 0; //将当前顶点的权值设置为0,表示该顶点已经完成任务
        for (j =1; j <G.Vertices.length; j++) {   //循环所有顶点
            if (lowcost[j]!=0&&G.edge[k][j]<lowcost[j])
            {
                //lowcost[j]!=0保证不会重复访问
                lowcost[j]=G.edge[k][j];
                adjvex[j] = k; //将下标为k的顶点存入adjvex,也就是存储下一步的最短边的起点
            }
        } //得到目前已经是生成树的顶点的集合到其他各个顶点的最短距离
    }
} 

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "AdjMGraph.h"
/*
功能:
	主函数
	
参数:
	argc - argv 数组的长度,大小至少为 1,argc - 1 为命令行参数的数量。
	argv - 字符串指针数组,数组长度为命令行参数个数 + 1。其中 argv[0] 固定指向当前
	       所执行的可执行文件的路径字符串,argv[1] 及其后面的指针指向各个命令行参数。
	       例如通过命令行输入 "C:\hello.exe -a -b" 后,main 函数的 argc 的值为 3,
	       argv[0] 指向字符串 "C:\hello.exe",argv[1] 指向字符串 "-a",argv[2] 指向字符串 "-b"。

返回值:
	成功返回 0, 失败返回 1
*/

int main(int argc, char* argv[])
{
	// 使用第一个参数输入待处理文件的名称,若没有输入此参数就报告错误
	if(argc < 2)
	{
		printf("Usage: app.exe filename\n");
		return 1;
	}
	
	// 打开待处理的文件。读取文件中的内容。
	FILE* file = fopen(argv[1], "rt");
	if(NULL == file)
	{
		printf("Can not open file \"%s\".\n", argv[1]);
		return 1;
	}
	AdjMGraph G;
	CreateGraph(&G, file );
	for(int i=0;i<G.Vertices.length;i++){
		char c;
		ListGet(G.Vertices, i, &c);
		if(i==G.Vertices.length-1)
		  printf("%c",c); //最后一个元素后面的空格不输出
		else
		 printf("%c ",c);
	}
	printf("\n");
	PrintGraph(G );
	int i=0;
	int* visited= (int*)malloc(sizeof(int)*G.Vertices.length);
	VDataType* output= (VDataType*)malloc(sizeof(VDataType)*G.Vertices.length);
	int count=0;
	for( i=0;i<G.Vertices.length;i++)
	visited[i]=0;
	DepthFSearch(G,0 ,visited,output,&count);
	for(int i=0;i<count;i++){
		if(i==count-1)
			printf("%c",output[i]); //最后一个元素后面的空格不输出
		else
			printf("%c ",output[i]);
	}
	printf("\n");
	for( i=0;i<G.Vertices.length;i++)
	visited[i]=0;
	count=0;
	BroadFSearch(G,0,visited,output,&count);
	for(int i=0;i<count;i++){
		if(i==count-1)
			printf("%c",output[i]); //最后一个元素后面的空格不输出
		else
			printf("%c ",output[i]);
	}
	printf("\n");
	MinSpanTreeEage* closeVertex = malloc(sizeof(MinSpanTreeEage)*(G.Vertices.length));
	Prime(G,closeVertex); 
	//printf("%c ",closeVertex[0].vertex);
	for( i=1;i<G.Vertices.length;i++)
	{
		char data1,data2;
		ListGet(G.Vertices,closeVertex[i].v1,&data1);
		ListGet(G.Vertices,closeVertex[i].v2,&data2);
		printf("%c %c %d\n",data1,data2,closeVertex[i].weight);
	}
	//
	free(visited);
	free(output);
	fclose(file);	
	
	return 0;
}

要注意在prime算法里面要比较好最小生成二叉树中两个结点的大小,再存入数组,不然会出现顺序出错的情况!!!

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Feistel算法是一种对称加密算法,它通过迭代轮次的方式,将明文分成两部分,然后进行一系列的轮函数运算和异或操作,最终得到加密后的密文。 具体实现Feistel算法的步骤如下: 1. 首先,需要确定算法的轮数n和密钥key。 2. 将明文分成两个部分L和R,每个部分的长度为明文长度的一半。 3. 迭代n轮,每轮的过程相同: a. 将R的内容保存在临时变量temp中。 b. 使用轮函数F对R和密钥key进行计算得到一个临时结果。 c. 将L与临时结果进行异或操作,并将结果存入R中。 d. 将temp的内容存入L中。 4. 在最后一轮迭代结束后,将L和R进行合并,得到加密后的密文。 Feistel算法的轮函数F通常使用的是可逆函数,如DES中的S盒置换等。该函数具有扩散性和混乱性,使得加密过程具有强大的安全性。同时,Feistel算法也具有解密的可逆性,只需要将密钥和轮函数逆向应用即可得到原始明文。 Feistel算法实现可以使用各种编程语言进行,例如Python、C++等。在编程实现过程中,需要注意将明文和密钥转换为二进制形式,然后按照上述步骤进行迭代运算,并得到加密后的二进制结果。最后再将二进制结果转换为十六进制或其他形式的字符串,作为最终的密文输出。 总之,Feistel算法通过迭代运算和异或操作,实现了对称加密的功能。它的设计思想简单、安全性较高,被广泛应用于各类加密算法中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值