数据结构代码总结(C语言实现)
目录
- 如何应对数据结构的代码题?
- 采取的学习流程
- ①首先对C语言的语法的熟悉
- ②学习掌握基本代码的写法,做到熟练
- 2.1插入排序
- 2.2快速排序
- 2.3二分查找
- 2.4树的遍历
- ③跟着网上视频开始熟悉对一些问题的解答
- ④结合真题的代码,寻找其中的结题规律
如何应对数据结构的代码题?
一开始我对代码题还是无从下手的,通过这些流程,对408真题上的代码逐渐理解并能自己写出个大概。希望我的复习流程可以大家提供一定的参考意义。
采取的学习流程
首先要明白几点,真题的代码是在一些基础代码的基础上进行增加参数,或者增加步骤,进而得到的
①首先对C语言的语法的熟悉
包括对结构体的定义,指针、数组、重命名,以及一些打印输出的句子。
学习的资料网址:菜鸟教程
基础的一些语法格式
定义结构体、以及重命名
typedef struct node{int data;struct node next;
}Lnode ,*List;
//这里利用typdef对结构体数据类型进行重命名,方便后面定义变量
//结构体变量struct node,命名为了Lnode,结构体变量struct node的指针命名为了,List
那么你可以用分别用:Lnode代替struct node ;List 代替struct node* 进行变量的定义数组
int nums[5]={1,2,3,4,5};
nums=(int *)malloc(sizeof(int)*(n+1));打印
char *a="123asd"
printf("(");
printf("%c",s);
②学习掌握基本代码的写法,做到熟练
基础代码
2.1插入排序
链表实现
注意插入排序链表实现的特点
维护的几个变量
LastSorted:已经排好序的最后一个结点
curr:需要进行插入排序的当前结点
Pre:寻找第一个大于当前结点之前的结点
//将结点指针变量的声明符号重新定义为 list 方便后续定义结点指针变量
typedef struct ListNode* list;
struct ListNode *insertionSortList(struct ListNode *head) {if (head == NULL) {return head;}list dummyHead = malloc(sizeof(struct ListNode));//虚拟头结点为了更好的对第一个元素之前进行插入dummyHead->val = 0;dummyHead->next = head;list lastSorted = head;//已排序元素的最后一个元素list curr = head->next;//当前需要进行判断的元素while (curr != NULL) {//当,当前元素大于等于已排好的最后元素,直接改最后元素if(lastSorted->val<=curr->val){lastSorted=lastSorted->next;}//当,当前元素小于已经排好元素时,需要从头开始找到第一个大于当前元素的前一个结点else{list pre=dummyHead;while(pre->next&&pre->next->val<=curr->val)//当下一个结点大于时,或者当前结点为最后一个结点时,跳出循环{pre=pre->next;}lastSorted->next=curr->next;//最后结点不变,最后结点的下一个结点变为原节点的下一节点curr->next=pre->next;//当前结点的下一节点先修改pre->next=curr;//然后修改pre结点的下一节点为当前结点}curr=lastSorted->next;//将已排序的后一个元素进行排序}return dummyHead->next;//最后返回head结点
}
数组实现
int insersorted(int a[],int n)
{for(int i=2;i<n;i++)//从第二个元素开始{if(a[i]<a[i-1])//小于往前插入{a[0]=a[i];for(int j=i-1;a[j]>a[0];j--){a[j+1]=a[j];}a[j+1]=a[0]}
}
2.2快速排序
//设计两个函数
//第一个函数实现每趟的交换排序
int partition(int *nums,int low,int high)
{ int tem=nums[low];while(low<high){while(low<high&&nums[high]>=tem)high--;nums[low]=num[high];while(low<high&&nums[low]<=tem)low++;nums[high]=nums[low];}nums[low]=tem;return low;//最后返回元素被最终放置的位置,然后以此为基点,对左右两边的数进行划分
}
//第二个函数实现递归的调用
int * quicksort(int * nums,int low,int high)
{if(low<high){int piv=partition(nums,low,high);quicksort(nums,piv+1,high);quicksort(nums,low,piv-1);}
}
2.3二分查找
维护的变量
low:最左边的位置
high:最右边的位置
middle:中间的位置
每次根据中间位置值与目标变量的大小关系调整low和high的值
int searchInsert(int* nums, int numsSize, int target){int low=0,high=numsSize-1,middle;while(low<=high){middle=(low+high)/2;if(nums[middle]==target)return middle;else if(nums[middle]<target){low=middle+1;}else{high=middle-1;}}if (nums[middle]>target)return middle;elsereturn middle+1;
}
2.4树的遍历
递归实现
//先序遍历
typedef struct BiNode
{struct BiNode* lchild,rchild;int data;
}BNode,*BiTree;
void preorder(BiTree T)
{
if(T!=NUll){visit(T);preorder(T->lchild);preorder(T->rchild);}
}
//中序遍历
void inorder(Bitree T)
{if(T!=NUll){inorder(T->lchild);visit(T);inorder(T->rchild);}
}
//后续遍历
void postorder(Bitree T)
{
if(T!=NUll){postorder(T->lchild);postorder(T->rchild);visit(T);}
}
非递归实现
//中序遍历
void inorder(BiTree T)
{InitStack(S);//用于存储有左子树的根节点BiTree p=T;//用于向下探索结点while(P||isempty(S))//当T不为空,同时栈内存在元素时,循环{//如果当前结点不为空,则把器左节点放入栈中if(p){push(S,p);p=p->lchild;}else{pop(S,p);visit(p);p=p->rchild;}}
}
③跟着网上视频开始熟悉对一些问题的解答
B站一位up主的的讲解视频:23考研数据结构编程代码题逐句精讲
看完视频并写完代码后,对于线性表的相关问题会有比较好的理解。
看视频时注重积累相关问题的结题方法,需要设置几个参数。
涉及的题目的结题代码
④结合真题的代码,寻找其中的结题规律
2009年
寻找倒数第k个结点,利用两个指针p,q, q先向后探测到第k-1个结点,如果此节点不是最后一个结点,那么p,q共同向后移动,知道q为最后一个结点,此时的p即为所求、
int find_k(Lnode *head, int k){Lnode *p=head,*q=head;int count=k-1;while(count<k-1&&q->next){q=q->next;}if(q->next==null) return 0;while(q->next){p=p->next;q=q->next;}printf("倒数第%d个位置上的值为%d",k,p->data);return 1;
}
2010年
将数组元素玄幻啊左移p位,那么可以采用对前p位反转,后n-p为反转,然后整体反转
void reverse(int *num,int low, int high)
{int mark=(low+high)/2,tem;for(int i=0;i<=mark;i++){tem=num[i];num[i]=num[low+high-i];num[low+high-i]=tem;}
}
void leftmove(int *num,int p,int n){reverse(num,0,p-1);reverse(num,p,n-1);reverse(num,0,n-1);
}
2011年
因为s1、s2等长为L,那么中位数为第L个元素,因为s1、s2均为升序排序,所以我可以用p、q表示s1、s2的下标,每次比较s1[p],s2[q]的大小,更小的元素指针后移,同时记录下次数更小的那个数,当后移了L次时,即找到了答案。
时间复杂度为O(n),空间复杂度为O(1)
int find_middle(int* s1,int* s2,int L){int count=0,p=0,q=0,pre_min;whiel(count<L){if(s1[p]<s2[q]){pre_min=s1[p];p++;}else{pre_min=s2[q];q++;}count++;return pre_min;}
2012年
思路:
后缀的起始位置的点的特点,结点的地址的指针相同。(注意不是结点的值相同,虽然这里结点的值页相同但是,只考虑地址相同会更方便)
我们可以从后往前看,只要我们同时从后面往前数第一个重合的元素开始比较,然后第一个相同的就是我们要找的。
所以,可以先分别算出str1、str2的长度
然后算出长度的差值:distance
用pq分别记录各链表的点的指针
对于长的链表,p先后移distance个结点,然后pq开始比较
Lnode* find(Lnode* str1,Lnode* str2)
{int m=0,n=0;Lnode* tem=str1->next;//先计算长度m、nwhile(tem){m++;tem=tem->next;}tem=str2->next;while(tem){n++;tem=tem->next;}Lnode *p=str1->next;Lnode *q=str2->next;//将链表队尾对齐while(m>n){p=p->next;m--;}while(n>m){q=q->next;n--;}int flag=1;//寻找第一个相同地址的结点,结束条件可能是找到最后没有找到那么就是nullwhile(flag&&p&&q){if(p=q)flag=0;else{p=p-next;q=q-next;}return p;
}
2014年
基础代码:先序遍历的递归实现
积累点
涉及对于需要考虑层数的,添加变量deep。
//在先序遍历的递归的基础上,加上参数deep(深度),然后在找到叶子节点时,计算权重:weight*deep,每次进行累加
typedef struct node{struct node * left,*right;int weight;
}*Btree;
static int count=0;//用来记录权重void func1(Btree root,int deep)
{if(root->left=NULL&&root->right=NULL){count=count+(root->weight)*deep;//找到叶子节点时,计算权重}if(root->left)func1(root->left,deep+1);//未找到叶子结点,继续往下找,深度+1if(root->right)func1(root->right,deep+1);
}func1(root,0)//一开始我根节点,属于第0层
return count;
2015
思路
首先,目标是删除绝对值相同的结点,然后这里只考虑时间复杂度尽可能高效,所以,我们用空间换时间,采取数组,存放对于以访问数的情况。
数组下标为数的绝对值,0表示未被访问到,1表示已存在。数组的长度为n+1
空间复杂度o(n):创建的数组
时间复杂度o(m):只要扫描一遍链表即可
typedef struct Lnode{int data;struct Lnode *link;
}List,LNode;
List funt(List L,int n)
{int * nums=(int*)malloc(sizeof(int)*(n+1));List tem,pre=L;int num;while(L->link){ num=L-link->data;if(nums[num]==0)num[num]=1;else{tem=pre->link;pre->link=pre->next->next;free(tem);}}free(nums);return L;
}
2016
思路:
首先满足n1-n2最小,s1-s2绝对值最大,那么我们需要找出最小n1个数,以及最大的n2个数,同时,n1=n/2(向下取整,n1)
采用快速排序,每次排序对元素进行划分,确定一个元素的最终位置,将元素分为左右两部分,左边的都是小于其的右边的都是大于其的。
所以我们需要在快速排序的基础上,尽快的排到第n/2这个位置。
int func(int *nums ,int n)
{int low=0,high=n-1;int pre_low=0,pre_high=n-1;//用来保存前一次的low,和high 的值,因为每次排序后,low最终等于highint mark=n/2,int flag=1;tem;//mark最为最终要排序的点的位置, flag 用来控制循环的进行while(flag){ tem=nums[low];while(low<high){while(nums[high]>=tem&&low<high)high--;nums[low]=nums[high];while(nums[low]<=tem&&low<high)low++;nums[high]=nums[low];}nums[low]=tem;//排序完一次,查看一下排好的元素的位置与最终位置的差距if(low=mark){flag=0;}else{if(low<mark){low=low+1;pre_low=low;high=pre_high;}else{high=low-1;pre_high=high;low=pre_low;} }}int s1=0,s2=0;for(int i=0;i<n;i++){if(i<=mark)s1=s1+nums[i];else s2=s2+nums[i];}return s2-s1;
}
2017
思路
首先二叉树的采用递归的中序遍历
首先,对于每个结点来说,要打印结点的字符串。
然后,对于根节点和叶子结点直接打印字符串,而对于非叶子结点,需要先打印"(“然后打印”)".
所以需要对结点进行区分,区分叶子结点,只需要判断结点的左右子树是否都为空,而判断根节点,非叶结点我们只能引入层数deep变量来加以区分
void func(BTree *root,int deep)
{if(root->left==NULL&&root->right==NULL)printf("%c",root->data);else{if(deep>1)printf("(");if(root->left!=NULL)func(root->left,deep+1);prinf("%c",root->data);if(root->right!=NULL)func(root->right,deep+1);if(deep>1)printf("(");}
}
void real(BTree * root)
{func(root,1);
}
2019
思路
首先找到链表的中点结点
然后对中点后的结点采用头插法进行逆序
然后对中点后的结点按指定位置插入中点前方的结点
维护的变量
p:中间结点
q:用来逆序,以及插入时作为标记后面结点的指针
void reorder(Lnode* head ){Lnode * p,q,r,s;p=head;q=head;while(q->next){p=p->next;q=q->next;if(q->next)q=q->next;//p每次移动一下,q每次移动两下。假设7个节点,当为奇数个结点时,p移动到了4位置,当为偶数个结点(6)时,p移动到了3}保持p结点不变,头插法逆转后续序列q=p->next;p->next=NULL;while(q){r=q->next//记录下一个要插入的结点q-next=p->next;p->next=q;q=r;}s=head->next;//第一个结点q=p->next;//中点的后一个结点,需要第一个插入到前面的结点p->next=NULL;//p最终变为为最后的结点,下一节点为空while(q){r=q->next;//记录下一个需要插入的结点q->next=p->next;//先记录p的下一个结点p->next=q;//p的下一个结点为qp=q->next;//下一个p为q的nextq=r;}
}
时间复杂度为O(n),空间复杂度为O(1)
这里需要多次对链表进行操作,需要对头插法实现序列的转置比较熟悉
2020
首先看清题目要求,只是输出最小距离,不需要输出相应的三元组。
这类问题先进行问题的简化,搞清楚我们要求什么。
要使距离D最小,怎么找。
假设a《b《c
那么,D= b-a + c-b + c-a= 2(c-a)也就最大值-最小值的两倍。
那么,要使D最小,我们就要不断的让最小值a向最大值c接近。
假设用a,b,c分别表示3个数组中遍历的数,那么我们每次将其中最小的数的下标后移1,知道某个数组的下标超出数组的长度时停止。
为什么的数不需要计算了?
首先,当一个列表的元素到了末尾,,说明上一次结尾找出的最小值为这个元素的最后一个元素,然后下标进行加1,但此时数组里已经没有元素了。如果,你取其他的数组里的元素,由于数组是从小到大排序的,那么其他数组的后一个元素必然导致与最小值的距离最变大,既c-a变大,相当于a以及不变了,你去移动其他元素。
所以此时得到的就是最小距离了。
#define MAX 9999999;
//计算绝对函数
int abs_(int a,int b){if(a>b) return a-b;else return b-a;}
int calculate(int* s1,int n1,int*s2,int n2,int* s3,int n3){int i=0,j=0,k=0;int pre_min=MAX;int dis=0;while(i<n1&&j<n2&&k<n3){dis=abs(s1[i],s2[j])+abs_(s2[j],s3[k])+abs_(s3[j],s1[k]);if(dis<pre_min){pre_min=dis;}if(s1[i]<s2[j]&&s1[i]<s3[k]){i++;}else if(s2[j]<s3[k]&&s2[j]<s1[i]){j++;}else{ k++;}}return pre_min;
}
相关文章:

数据结构代码总结(C语言实现)
目录如何应对数据结构的代码题?采取的学习流程①首先对C语言的语法的熟悉②学习掌握基本代码的写法,做到熟练2.1插入排序2.2快速排序2.3二分查找2.4树的遍历③跟着网上视频开始熟悉对一些问题的解答④结合真题的代码,寻找其中的结题规律如何应…...
zookeeper 复习 ---- chapter04
zookeeper 复习 ---- chapter04zookeeper 的精髓是什么? 1:它有四个节点类型 持久无序的节点 临时无序的节点 持久有序的节点 临时有序的节点 临时的节点的特征:当客户端和服务器端断开连接,当前客户端创建的节点被服务器端自动删…...
thinkphp6.0连接MYSQL
目录8.连接多个数据库7.多级控制器不存在6.分页5.非法请求4.关于路由**3.初体验页面****2.加入fileheader添加注释****1.配置mysql0. 官方开发手册一些网址 http://127.0.0.1:8000/index 原桌面 http://127.0.0.1:8000/hello/fsh hello,fsh(index中hello方法&#x…...

商家必读!超店有数分享,tiktok达人营销变现如何更快一步?
近几年来,“粉丝经济”发展越来越迅猛,“网红带货”已经成为了一种营销的方式。这种方式让商家能基于达人的影响下迅速抢占自己的私域流量池。消费者会基于对达人的信任,购买达人推荐的产品。达人效应可以助力品牌走出营销困境。如果商家想要…...

操作系统(day11)--快表,两级页表
具有快表的地址变换机构 时间局限性:会有大量连续的指令需要访问同一个内存块的数据的情况(程序中的循环) 空间局限性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。…...

预告| 亮点抢先看!第四届OpenI/O启智开发者大会主论坛24日启幕!
2023年2月24日至25日,第四届OpenI/O启智开发者大会将在深圳隆重举行。“算网筑基、开源启智、AI赋能”作为今年大会的主题,吸引了全球业界关注的目光。大会集结中国算力网资源基座、开源社区治理及AI开源生态建设、国家级开放创新应用平台、NLP大模型等前…...
猪齿鱼(Choerodon UI )的通用提交的封装 —— 两种方案,A.使用dataSet的自身的submit,B.使用axios.post来提交
submit组件(otherSubmit/axiosSubmit) 一、背景与简介 1、首先我们申请表提交,分为【保存】提交与【其他】提交; 1.1【保存】提交,要求表单必须要有变更,DataToJSON默认为dirty(只转换变更的…...

CISCN(Web Ezpentest)GC、序列化、case when
目录 REGEXP的一个点(正则) like(默认不区分大小写) 当禁用了空格 regexp,like的区分大小写的使用方法 [CISCN 2022 初赛]ezpentest 卡点 2022 HFCTF babysql 最近又学到了一道新知识,case when的错…...
OSG三维渲染引擎编程学习之五十七:“第六章:OSG场景工作机制” 之 “6.1 OSG访问器”
目录 第六章 OSG场景工作机制 6.1 OSG访问器 6.1.1 访问器模式 6.1.2 osg::NodeVisitor 6.1.3 访问器示例...

Python3 输入和输出实例及演示
在前面几个章节中,我们其实已经接触了 Python 的输入输出的功能。本章节我们将具体介绍 Python 的输入输出。 输出格式美化 Python两种输出值的方式: 表达式语句和 print() 函数。 第3种方式是使用文件对象的 write() 方法,标准输出文件可以用 sys.std…...
召回-回忆录(持续更新)
0.召回方法 词召回 swing、itemCF 缺点: 有冷启动问题不是全局召回,冷门活动难以得到召回结果容易召回过多的头部热门活动 向量召回 参考文献: 经典推荐算法学习(七)| Graph Embedding技术学习 | 从DeepWalk到No…...
1243. 糖果/状态压缩dp【AcWing】
1243. 糖果 糖果店的老板一共有 M种口味的糖果出售。 为了方便描述,我们将 M种口味编号 1∼M。 小明希望能品尝到所有口味的糖果。 遗憾的是老板并不单独出售糖果,而是 K颗一包整包出售。 幸好糖果包装上注明了其中 K颗糖果的口味,所以小…...

【Spring Cloud Alibaba】001-单体架构与微服务架构
【Spring Cloud Alibaba】001-单体架构与微服务 文章目录【Spring Cloud Alibaba】001-单体架构与微服务一、单体架构1、单体应用与单体架构2、单体应用架构图3、单体架构优缺点优点缺点二、微服务1、微服务的“定义”2、微服务的特性3、微服务架构图4、微服务的优缺点优点缺点…...
Renderer 使用材质分析:materials、sharedMaterials 及 MaterialPropertyBlock
一、materials 与 sharedMaterials 1.1 使用上的区别与差异 Unity 开发时,在 C# 中通过 Renderer 取材质操作是非常常见的操作,Renderer 有两种常规获取材质的方式: sharedMaterials:可以理解这个就是原始材质,所有使…...

java学习----网络编程
网络编程入门 网络编程概述 计算机网络 计算机网络是指地理位置不同的具有独立功能的计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理协调下,实现资源共享和信息传递的计算机系统…...
这些「误区」99%的研发都踩过
意识不到误区的存在最为离谱; 01生活中,职场上,游戏里,都少不了正面对喷过:意识太差; 在个人的认知中意识即思维,意识太差即思维中存在的误区比较多; 每个人或多或少都存在思维上的…...

Bi系统跟数据中台的区别是什么?
随着数据时代的发展,BI分析是当今数据时代必不可少的能力之一。BI系统通过系统化产品化的方法,能够大幅降低数据的获取成本、提升数据使用效率。同时借助可视化、交互式的操作,可以高效支持业务的分析及发展。 BI如此火热,随之而…...

微信小程序反编译方法分享
文章目录一、前言二、准备工作(一)安装Nodejs(二)解密和逆向工具三、小程序缓存文件解密(一)定位小程序缓存路径(二)源码解密(三)源码反编译四、小结一、前言…...

有了这些接口测试用例+工具,测试效率想不提升都难
写在前面:在日常开发过程中,有人做前端开发,有人负责后端开发。接口的主要作用就是连接前后台。但是,由于前端和后端开发的速度可能不一样,尤其是后端开发好了,但前端还未开发。这种时候我们需要做接口测试…...

麒麟 arm架构安装nginx
目录 1、下载nginx安装包并解压 在线安装: 离线安装: 上传nginx安装包(下载地址:https://nginx.org/download/nginx-1.20.2.tar.gz)到指定目录 2、安装系统相关依赖软件、组件包 1、上传或者下载对应的组件包 2、安…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...

逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...