您好,欢迎来到五一七教育网。
搜索
您的当前位置:首页数组与广义表的算法的实验报告

数组与广义表的算法的实验报告

来源:五一七教育网


数组与广义表的算法

实验工具:visual C++

实验内容:1、三元组表示稀疏矩阵的转置算法(一般&快速)<1-7页> 2、 稀疏矩阵乘

法、加法的算法(一般&十字链表)<8-21页> 3、广义表的各种算法<22-28页>

体验:通过利用visual C++实验工具,实现数组与广义表各类算法的过程中,本人对数组与

广义表的知识有了更深的了解,而且认识到数组与广义表各类操作可由形式多样的算法结构实现。算法并非统一标准的,同样的结果可有多种算法得出,算法的编写鼓励创造性思维。

1、 三元组表示稀疏矩阵的转置算法(一般&快速)

代码:

#include #include #include #include #define OK 1 #define ERROR 0

#define OVERFLOW 0 #define MAXSIZE 100 #define MAXRC 100 typedef int ElemType; typedef struct { int i,j; ElemType e; }Triple;

typedef struct { Triple data[MAXSIZE+1]; //非零元三元组 int rpos[MAXRC+1]; //各行第一个非零元的位置表 int mu,nu,tu; //矩阵的行数、列数和非零元个数 }RLSMatrix;

CreateSMatrix(RLSMatrix &M) //创建稀疏矩阵M { int i,m,n; ElemType e; int k,j; printf(\"输入矩阵的行数、列数、非零元的个数:\"); scanf(\"%d%d%d\ M.data[0].i=0; for(i=1;i<=M.tu;i++)

{ j=0; do { j++; if(j>3) //控制跳出死循环 { printf(\"本次输入失败!\"); return ERROR; } printf(\"按行序输入第%d个非零元素所在的行(1~%d)列(1~%d)值:\ scanf(\"%d%d%d\ k=0; if(m<1||m>M.mu||n<1||n>M.nu) //行或列超出范围 k=1; if(mvoid DestroySMatrix(RLSMatrix &M) //销毁稀疏矩阵M { M.mu=0; M.nu=0; M.tu=0; }

void PrinRLSMatrix(RLSMatrix M) //遍历稀疏矩阵 M { int i; printf(\"稀疏矩阵对应的三元组表为:\\n\\n\"); printf(\"行 列 元素值、\\n\\n\"); for(i=1;i<=M.tu;i++) printf(\"%2d%4d%8d\\n\ printf(\"\\n\\n\"); }

void print(RLSMatrix A) //打印矩阵函数,以通常形式输出矩阵 { int k=1,a,b;

printf(\"稀疏矩阵的通常形式为:\\n\"); int M[MAXSIZE][MAXSIZE]; for(a=0;avoid showtip() //菜单 { printf(\" ********************请选择要执行的操作********************\\n\\n\"); printf(\" & 1 采用一般算法实现 &\\n\"); printf(\" & 2 采用快速转置的算法实现 &\\n\"); printf(\" & 3 同时采用两种算法,先显示一般算法,再显示快速算法 &\\n\"); printf(\"

**********************************************************\\n\\n\"); }

////头文件结束

TransposeSMatrix(RLSMatrix M,RLSMatrix &T) //求稀疏矩阵M的转置矩阵T(一般算法) { int p,q,col; T.mu=M.nu; T.nu=M.mu; T.tu=M.tu; if(T.tu) { q=1; for(col=1;col<=M.nu;++col) //按列序求转置

for(p=1;p<=M.tu;++p) if(M.data[p].j==col) { T.data[q].i=M.data[p].j; T.data[q].j=M.data[p].i; T.data[q].e=M.data[p].e; ++q; } } return OK; }

FastTransposeSMatrix(RLSMatrix M,RLSMatrix &T) { int p,q,t,col,*num,*cpot; num=(int*)malloc((M.nu+1)*sizeof(int)); cpot=(int*)malloc((M.nu+1)*sizeof(int)); T.mu=M.nu; T.nu=M.mu; T.tu=M.tu; if(T.tu) { for(col=1;col<=M.nu;++col) num[col]=0; for(t=1;t<=M.tu;++t) ++num[M.data[t].j]; cpot[1]=1; for(col=2;col<=M.nu;++col) cpot[col]=cpot[col-1]+num[col-1]; printf(\"\\n辅助数组的值为:\\n\"); printf(\"列号:\"); for(t=1;t<=M.nu;++t) printf(\"%4d\ printf(\"\\n\"); printf(\"num[]\"); for(t=1;t<=M.nu;++t) printf(\"%4d\ printf(\"\\n\"); printf(\"cpot[]\"); for(t=1;t<=M.nu;++t) printf(\"%4d\ printf(\"\\n\\n\"); for(p=1;p<=M.tu;++p) { col=M.data[p].j;

//快速转置算法 q=cpot[col]; T.data[q].i=M.data[p].j; T.data[q].j=M.data[p].i; T.data[q].e=M.data[p].e; ++cpot[col]; } } free(num); free(cpot); return OK; }

void main() { int result; int j; RLSMatrix A,B; //************************************************ COORD Co={0,0};DWORD Write; SetConsoleTitle(\"稀疏矩阵的转置\\n\"); HANDLE hOut=GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hOut,FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_INTENSITY); FillConsoleOutputAttribute(hOut,FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_INTENSITY,100000000,Co,&Write); ///windows的API函数,用来设置控制台标题 do { showtip(); //调用菜单函数 int i; scanf(\"%d\ switch(i) { case 1: printf(\"创建矩阵A:\"); if((result=CreateSMatrix(A))==0) exit(ERROR); PrinRLSMatrix(A); printf(\"求A的转置矩阵B(一般算法):\\n\"); TransposeSMatrix(A,B); PrinRLSMatrix(B); print(B); DestroySMatrix(B); printf(\"\\n\\n\");break;

case 2: printf(\"创建矩阵A:\"); if((result=CreateSMatrix(A))==0) exit(ERROR); PrinRLSMatrix(A); printf(\"求A的转置矩阵B(快速转置):\\n\"); FastTransposeSMatrix(A,B); PrinRLSMatrix(B); print(B); DestroySMatrix(A); DestroySMatrix(B); printf(\"\\n\\n\");break; case 3:

printf(\"创建矩阵A:\"); if((result=CreateSMatrix(A))==0) exit(ERROR); PrinRLSMatrix(A);

printf(\"求A的转置矩阵B------(一般算法):\\n\"); TransposeSMatrix(A,B); PrinRLSMatrix(B); print(B);

DestroySMatrix(B); printf(\"\\n\\n\");

printf(\"求A的转置矩阵B------(快速转置):\\n\"); FastTransposeSMatrix(A,B); PrinRLSMatrix(B); print(B); DestroySMatrix(A); DestroySMatrix(B); printf(\"\\n\\n\");break; } printf(\" **********请选择是否继续输入其他稀疏矩阵?**********\\n\"); printf(\" 1 是,输入其他矩阵\\n\"); printf(\" 0 否,不输入\\n\"); printf(\"

****************************************************\"); fflush(stdin);//清除输入缓存区 scanf(\"%d\ }while(j==1); }

运行结果: (1)创建矩阵

(2)一般转置 (3)快速转置

2、 稀疏矩阵乘法、加法的算法(一般&十字链表)

代码:

#include

#include #define Size 2501 # define Size1 51 typedef struct { int i; int j;

int e;//非零元的值 }triple; //定义三元组

typedef struct {

triple data[Size+1];//矩阵中的元素

int rops[Size1+1];// rops[i]为第i行元素中的首非零元在data[]中的序号 int mu;//行数 int nu;//列数 int tu;//非零元数 } juzhen;//定义矩阵

typedef struct node// 定义十字链表元素 {

int i,j,e;

struct node *right,*down;// 该非零元所在行表和列表的后继元素 }node,*link;

typedef struct // 定义十字链表对象结构体 {

link *rhead,*chead;//行和列的头指针

int m,n,t;// 系数矩阵的行数,列数,和非零元素个数 }crosslist;

void createcross(crosslist &M)//建立十字链表 {

int i,j,e,k; node *p,*q;

printf(\"输入行,列和非零元数,空格隔开:\\n\"); scanf(\"%d %d %d\

M.rhead=(link *)malloc((M.m+1)*sizeof(link));//给行和列的头指针分配内存 M.chead=(link *)malloc((M.n+1)*sizeof(link)); for(k=1;k<=M.m;k++)//初始化行,列的头指针 M.rhead[k]=NULL;

for(k=1;k<=M.n;k++) M.chead[k]=NULL;

printf(\"输入非零元的行,列和值,空格隔开:\\n\"); for(k=1;k<=M.t;k++)//输入非零元 { scanf(\"%d %d %d\ p=(node *)malloc(sizeof(node)); p->i=i; p->j=j; p->e=e; if(M.rhead[i]==NULL||M.rhead[i]->j>j)//插入元素所在行无非零元或首非零元的列标大于插入元素的列标

{ p->right=M.rhead[i]; M.rhead[i]=p; } else { for(q=M.rhead[i];(q->right)&&q->right->jright);//空循环找到第一个列标大于或等于插入元素列标的元素

p->right=q->right; q->right=p; } if(M.chead[j]==NULL||(M.chead[j]->i>i))//插入元素所在列无非零元或首非零元的行标大于插入元素的行标

{ p->down=M.chead[j]; M.chead[j]=p; } else { for(q=M.chead[j];(q->down)&&q->down->idown);//空循环找到第一个行标大于或等于插入元素行标的元素

p->down=q->down; q->down=p; } } }

void printcross(crosslist A)//输出十字链表 {

if(A.m==0) printf(\"十字链表为空!\\n\"); else

{ printf(\"十字链表为:\\n\"); int i,j; for(i=1;i<=A.m;i++) { link p=A.rhead[i]; for(j=1;j<=A.n;j++) { if((p)&&(j==p->j)) { printf(\"%5d\->e); p=p->right; } else printf(\"%5d\ } printf(\"\\n\"); } }

printf(\"\\n\"); }

crosslist addcross() {

printf(\"十字链表加法:\\n\");

crosslist a,b;// 创建两个十字链表对象,并初始化 createcross(a); createcross(b);

node *pre,*h[51],*pa,*pb,*q;//定义辅助指针,pa,pb分别为a,b当前比较的元素,pre为pa的前驱元素

int i,j,k=0,m,n; //h[j]指向j列的当前插入位置

if(a.m!=b.m||a.n!=b.n) printf(\"格式不对,不能相加!\\n\"); else { for(i=1;i<=a.m;i++) { pa=a.rhead[i]; pb=b.rhead[i]; pre=NULL; for(j=1;j<=a.n;j++) h[j]=NULL;

while(pb) { link p; p=(node *)malloc(sizeof(node)); // 开辟新节点,存储b中取出的元素 p->i=pb->i; p->j=pb->j; p->e=pb->e; if(pa==NULL||pa->j>pb->j)//当a此行已经检查完或者pb因该放在pa前面

{ if (pre==NULL) a. rhead[p->i]=p; else pre->right=p; p->right=pa; pre=p; if (h[p->j]==NULL)//当前插入位置下面无非零元 //因为是逐行处理,so,h[p->j],依次下移 //因此每次都是指向插入的位置 { a. chead [p->j]= p; p->down = NULL; } else { p->down = h[p->j]->down; h[p->j]->down = p; } h[p->j]=p;//*******h[p->j]下移指向下次插入的位置 pb=pb->right;//pb指向该行下个元素 } else if((pa&&pa->jj))//只要移动pa的指针****先不加||(pb==NULL&&pa)

{ pre = pa; h[pa->j]=pa;//移动h[],使其指向下次插入的位置 pa = pa->right; } else if(pa->j==pb->j) { pa->e+=pb->e; if(pa->e)//不为零 {

pre=pa; h[pa->j]=pa; pb=pb->right;//加 } else//pa->e为零,删除节点 { if (pre ==NULL) a.rhead [pa->i]=pa->right; else { pre->right=pa->right; } p=pa;//p指向pa,用来在下面修改列指针 pa=pa->right; if (h [p->j]==NULL) a.chead [p->j]=p->down; else { h[p->j]->down=p->down; } free(p); pb=pb->right; } } } } }

return a; }

void multycross(crosslist &c)//十字链表乘法 {

node *p,*q,*u,*v,*p1,*p2; crosslist a,b; link *r; int i,j,k,e;

printf(\"十字链表乘法:\\n\"); createcross(a); createcross(b); if(a.n!=b.m) printf(\"格式错误,不能相乘!\\n\"); else

{

c.m=a.m; c.n=b.n; c.t=0;

c.rhead=(link *)malloc((a.m+1)*sizeof(link));//给行和列的头指针分配内存 c.chead=(link *)malloc((b.n+1)*sizeof(link)); for(k=1;k<=a.m;k++)//初始化行,列的头指针 c.rhead[k]=NULL; for(k=1;k<=b.n;k++) c.chead[k]=NULL;

r=(link *)malloc((b.n+1)*sizeof(link)); for(i=1;i<=a.m;i++) { u=(node *)malloc(sizeof(node)); u->e=0; u->i=0; u->j=0; for(k=1;k<=b.n;k++)//初始化r[] r[k]=u; p1=p=a.rhead[i]; for(j=1;j<=b.n;j++) { p=p1; q=b.chead[j]; v=(node *)malloc(sizeof(node));//初始化v,v为将插入c矩阵的元素 v->e=0; v->i=i; v->j=j; while(p&&q) { if(p->j>q->i) q=q->down; else if(p->ji) p=p->right; else { v->e+=p->e*q->e; p=p->right; q=q->down; } } if(v->e)//如果不为零,则插入c矩阵中

{ //同建立十字链表 if(c.rhead[i]==NULL||c.rhead[i]->j>j)//插入元素所在行无非零元或首非零元的列标大于插入元素的列标

{ v->right=c.rhead[i]; c.rhead[i]=v; } else {

for(p2=c.rhead[i];(p2->right)&&(p2->right->jright);//空循环找到第一个列标大于或等于插入元素列标的元素

v->right=p2->right; p2->right=v; } if(c.chead[j]==NULL||c.chead[j]->i>i)//插入元素所在列无非零元或首非零元的行标大于插入元素的行标

{ v->down=c.chead[j]; c.chead[j]=v; } else {

for(p2=c.chead[j];(p2->down)&&(p2->down->idown);//空循环找到第一个行标大于或等于插入元素行标的元素

v->down=p2->down; p2->down=v; } } } } } }

void create(juzhen & M) //创建稀疏矩阵 {

int i,t=0;

printf(\"输入矩阵行数和列数and非零元的个数,以空格隔开:\\n\"); scanf(\"%d %d %d\

printf(\"输入矩阵非零元的行,列,和数值(中间空格隔开):\\n\"); for(i=1;i<=M.tu;i++) scanf(\"%d %d %d\输入三元组的元素 for(i=1;i<=Size1;i++)//初始化rops【】 M.rops[i]=0;

for(i=1,t=1;i<=M.mu;i++)//得到各行第一个元素的序号 { M.rops[i]=t; while(M.data[t].i<=i&&t<=M.tu)//遇到i行非零元,则t累加 t++; } }

void add(juzhen A,juzhen B,juzhen & C)//稀疏矩阵加法 {

int k=1,temp=0,k1=1, k2=1;//k1,k2,k分别控制A,B,C中非零元的序号变化 printf(\"稀疏矩阵加法:\\n\"); create(A); create(B);

if(A.mu!=B.mu||A.nu!=B.nu) printf(\"格式不对,不能相加!\\n\"); else { while(k1<=A.tu&&k2<=B.tu)//当A,B中的非零元都没用完 { if(A.data[k1].iC.data[k++]=A.data[k1++]; else if(A.data[k1].i>B.data[k2].i)//同上 C.data[k++]=B.data[k2++]; else//data[k1],data[k2]行标相同 { if(A.data[k1].j>B.data[k2].j)//data[k1]列标大于data[k2]列标,则把data[k2]的值赋给data[k]

C.data[k++]=B.data[k2++]; else if(A.data[k1].jC.data[k].e=temp; k++; } k1++; k2++; } } } while(k1<=A.tu)//B中非零元已用完,A中还有非零元 C.data[k++]=A.data[k1++]; while(k2<=B.tu)//A中非零元已用完,B中还有非零元 C.data[k++]=B.data[k2++]; C.mu=A.mu;//确定C的行列数和非零元个数 C.nu=A.nu; C.tu=k-1; } }

void print(juzhen A)//输出稀疏矩阵 {

printf(\"\\n矩阵为:\\n\"); int i,j,k=1; if(A.mu==0) printf(\"矩阵为空!\\n\"); else if(A.tu==0)//矩阵元素为空 printf(\"零矩阵!\\n\"); else for(i=1;i<=A.mu;i++)//逐行输出 { for(j=1;j<=A.nu;j++) {

if(A.data[k].i==i&&A.data[k].j==j)//行和列分别对应相等则输出相应非零元,否则输出零

printf(\"%5d\ else printf(\"%5d\ } printf(\"\\n\");//该行输出结束,空行输出下一行 } printf(\"\\n\"); }

void multy(juzhen A,juzhen B,juzhen &C)//稀疏矩阵乘法 {

int arow,brow,ccol,temp[51],p,q,t,tp,i;//各变量代表含义见下面 printf(\"稀疏矩阵乘法:\\n\"); create(A); create(B);

if(A.nu!=B.mu) printf(\"格式错误,不能相乘!\\n\"); else { C.mu=A.mu;//初始化c的行列及非零元个数 C.nu=B.nu; C.tu=0; if(A.nu!=B.mu) printf(\"A,B格式不对不能相乘!\\n\"); else // { for(arow=1;arow<=A.mu;arow++)//arow为当前A矩阵的行标 { for(i=0;i<51;i++)//初始化temp temp[i]=0; if(arowelse //arow为最后一行 tp=A.tu+1; for(p=A.rops[arow];pif(browelse //brow大小等于B.mu t=B.tu+1; for(q=B.rops[brow];q{ ccol=B.data[q].j;//ccol:data[p]*data[q]所得结果所在的列 temp[ccol]+=A.data[p].e*B.data[q].e;//temp【ccol】:相乘所得的C矩阵中第arow行cool列元素的值

}

} for(ccol=1;ccol<=B.nu;ccol++)// if(temp[ccol])//temp【ccol】不为零,则把值赋到c中,c.tu加1。 { C.data[++C.tu].e=temp[ccol]; C.data[C.tu].i=arow; C.data[C.tu].j=ccol; } } } } }

void clear(juzhen & A)//清空稀疏矩阵 { int i;

A.mu=0; A.nu=0; A.tu=0;

for(i=0;ivoid main() {

juzhen A,B,C,D; crosslist a,b,c,d; lable:

printf(\"******************************************************************\\n\");

printf(\"请选择:1、稀疏矩阵的加法,并输出结果,2、稀疏矩阵乘法,并输出结果\\n\");

printf(\"\\n3、十字链表加法,并输出,4、十字链表乘法并输出,5、结束:\\n\");

printf(\"******************************************************************\\n\");

int x;

scanf(\"%d\ switch(x) {

case 1: add(A,B,C); print(C); printf(\"\\n\"); goto lable; case 2: multy(A,B,C); print(C); printf(\"\\n\"); goto lable; case 3: a=addcross(); printcross(a); printf(\"\\n\"); goto lable; case 4: multycross(b); printcross(b); printf(\"\\n\"); goto lable; case 5: break; }

printf(\"\\n\"); }

运行结果:

(1) 稀疏矩阵加法

(2)稀疏矩阵乘法

(3)十字链表加法

(4)十字链表乘法

3、 广义表的各种算法

代码:

#include #include #include #define maxlen 100 typedef char ElemType;

typedef struct GLode//广义表结构体的定义 { int tag; union { ElemType atom; struct GLode *hp; } val; struct GLode *tp; } GList;

typedef struct //栈结构的定义 { ElemType data[maxlen] ; int top; }SeqStack;

//生成广义表

GList *CreateGL(char *&s) { GList *h; char ch; ch=*s; s++; if (ch!='\\0') { h=(GList *)malloc(sizeof(GList)); if (ch=='(') { h->tag=1; h->val.hp=CreateGL(s); } else if (ch==')') h=NULL; else { h->tag=0;

h->val.atom=ch; } } else h=NULL; ch=*s; s++; if (h!=NULL) if (ch==',') h->tp=CreateGL(s); else h->tp=NULL; return h; }

//遍历广义表

void DispGL(GList *g) { if (g!=NULL) { if (g->tag==1) { printf(\"(\"); if (g->val.hp==NULL) printf(\"\"); else DispGL(g->val.hp); } else printf(\"%c\->val.atom); if (g->tag==1) printf(\")\"); if (g->tp!=NULL) { printf(\ DispGL(g->tp); } } }

//求广义表的深度

int GLDepth(GList *g) {

int max=0,dep;

if (g->tag==0) return 0; g=g->val.hp; if (g==NULL) return 1; while (g!=NULL) { if (g->tag==1) { dep=GLDepth(g); if (dep>max) max=dep; }

g=g->tp; }

return(max+1); }

//求广义表的表尾 GList *tail(GList *g) { GList *p=g->val.hp; GList *t; if (g==NULL) { printf(\"空表不能求表尾\\n\"); return NULL; } else if (g->tag==0) { printf(\"原子不能求表尾\\n\"); return NULL; } p=p->tp; t=(GList *)malloc(sizeof(GList)); t->tag=1;t->tp=NULL; t->val.hp=p; return t; }

//查找函数

void FindGListX(GList *g,char x,int &mark) { if(g!=NULL){

if (g->tag == 0 && g->val.atom ==x) { mark = 1; } else if(g->tag == 1) FindGListX(g->val.hp,x,mark); FindGListX(g->tp,x,mark); } }

//求广义表的逆表

void NIGList(GList *g,SeqStack *s) { if(g!=NULL) { if (g->tag==1) { s->top++; s->data[s->top]=')'; if (g->val.hp==NULL) printf(\"\"); else NIGList(g->val.hp,s); } else { s->top++; s->data[s->top]=g->val.atom; } if (g->tag==1) { s->top++; s->data[s->top]='('; } if (g->tp!=NULL) { s->top++; s->data[s->top]=','; NIGList(g->tp,s); } } }

//广义表的输出

void Pop(SeqStack *s) { while(s->top>=0) { printf(\"%c\->data[s->top]); s->top--; } }

//以下主函数用于调试 void main() { GList *g,*gt; printf(\"请输入一个广义表:如((a,b),c)\\n\"); char str[30]; char x; int y=0,mark,xz=1; system(\"color 0c\"); //调用系统命令,改变运行时的字体颜色 SeqStack *k; k=(SeqStack *)malloc(sizeof(SeqStack)); k->top=-1; char *s=gets(str); g=CreateGL(s); printf(\"你输入的广义表为:\\n\"); while(xz) { DispGL(g); printf(\"\\n\"); printf(\"****广义表的运算****\\n\"); printf(\"====================\\n\"); printf(\"** 1.广义表的查找 **\\n\"); printf(\"** 2.求广义表表尾 **\\n\"); printf(\"** 3.求广义表深度 **\\n\"); printf(\"** 4.求广义表逆表 **\\n\"); printf(\"** 0.退出表的运算 **\\n\"); printf(\"====================\\n\"); printf(\"请 选 择:(0~5)\\n\"); scanf(\"%d\ switch (y) { case 1: printf(\"请输入要查找的元素:\");

mark=0; getchar(); scanf(\"%c\ FindGListX(g,x,mark); if (mark) printf(\"^_^该元素存在于您输入的表中!\\n\"); else printf(\"T_T对不起,没有找到该元素!\\n\"); break; case 2: gt=tail(g); printf(\"表尾:\");DispGL(gt);printf(\"\\n\"); break; case 3: printf(\"广义表的深度:%d\\n\ break; case 4: printf(\"所求广义表的逆表为:\\n\"); NIGList(g,k); Pop(k); printf(\"\\n\"); break; default : system(\"cls\"); /*调用系统命令CLS,实现清屏*/ printf(\"再见,欢迎再次使用!\\n\"); return ; } printf(\"是否继续:1.继续;0.停止\\n\"); printf(\"请选择:\"); scanf(\"%d\ if(xz==1) system(\"cls\"); else { system(\"cls\"); printf(\"再见,欢迎再次使用!\\n\"); } } }

运行结果:

(1) 创建广义表 (2)广义表的查找

(3)广义表表尾

(5)广义表的逆表

(4)广义表的深度

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 517ttc.cn 版权所有 赣ICP备2024042791号-8

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务