🌞欢迎来到数据结构的世界
🌈博客主页:卿云阁💌欢迎关注🎉点赞👍收藏⭐️留言📝
🌟本文由卿云阁原创!
🌠本阶段属于锻体阶段,希望各位仙友顺利完成突破
📆首发时间:🌹2021年1月27日🌹
✉️希望可以和大家一起完成进阶之路!
🙏作者水平很有限,如果发现错误,请留言轰炸哦!万分感谢!
目录
0️⃣✨✨✨线性表的类型定义✨✨✨
线性表是由具有相同类型的有限多个数据元素组成的一个有序序列。 例如,26个英文字母组成的字母表(A,B,C,…, Z),数据元素类型是字符型。
1️⃣✨✨✨有关操作的补充✨✨✨
(1)元素类型说明
typedef char ElemType;
其中ElemType就相当与char。
多项式的表示
指数,系数
typedef struct{ float p; int e; }Polynomial; //定义顺序表类型 typedef struct{ Polynomial data[MAXSIZE]; int length; //当前长度 }SqList; //定义顺序表类型
(2)数组定义
数组静态分配
#include<stdio.h> #define MAXSIZE 10 //MAXSIZE是根据实际问题定义的足够大的整数常量 typedef int DataType; typedef struct{ DataType data[MAXSIZE]; int length; }SqList; //定义顺序表类型 int main() { SqList L; return 0; }
数组动态分配
#include<stdio.h> #define MAXSIZE 10 //MAXSIZE是根据实际问题定义的足够大的整数常量 typedef int DataType; typedef struct{ DataType *data;//定义了一个指针变量存放地址 int length; }SqList; //定义顺序表类型 int main() { SqList L; L.data=(DataType*)malloc(sizeof(DataType)*MAXSIZE);//在C++中也可以使用new,和delete free(L.data); //释放空间 L.data=NULL; return 0; }
(3)C++中的参数传递
传值调用
#include<iostream> using namespace std; void swap(int m,int n){ int temp; temp=m; m=n; n=temp; } int main() { int a=5,b=10; cout<<"a="<<a<<"b="<<b<<endl; swap(a,b); cout<<"a="<<a<<"b="<<b<<endl; return 0;}
传址调用--指针变量作为参数
#include<iostream> using namespace std; void swap(int* m,int* n){ int temp; temp=*m; *m=*n; *n=temp; } int main() { int a=5,b=10; cout<<"a="<<a<<"b="<<b<<endl; swap(&a,&b); cout<<"a="<<a<<"b="<<b<<endl; return 0;}
传址调用--指针变量作为参数
2️⃣✨✨✨线性表的顺序表示及实现✨✨✨
线性表采用顺序存储的方式存储就称之为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。如图2.1所示,假设顺序表的每个结点占用k个内存单元,用location (ai)表示顺序表中第i个元素的存储地址。
location (ai+1) = location (ai) +k
location (ai) = location(a1) + (i-1)*k
其中,
location(a1)是线性表的第一个元素a1的存储地址,也称线性表的起始位置或基地址。
顺序表类型定义与初始化
#include<stdio.h> #define MAXSIZE 10 //MAXSIZE是根据实际问题定义的足够大的整数常量 typedef int DataType; typedef struct{ DataType data[MAXSIZE]; int length; }SqList; //定义顺序表类型 void InitSqList(SqList *L) { L->length=0; } int main() { SqList L; InitSqList(&L); return 0; }
2. 插入运算
顺序表的插入运算是将一个值为x的结点插入到顺序表的第i个位置1≤i≤n+1,
即将x插入到ai-1和ai之间,
如果i=n+1,则表示插入到表的最后,一般地可表示为:
插入前:{a1,a2, …, ai-1,ai, ai+1, …, an}
插入后:{a1, a2, …, ai-1,x, ai, ai+1,…, an}
插入后表长加1,并且数据元素ai-1和ai之间的逻辑关系发生了改变,因此除了i=n+1,否则必须移动元素才能反映这个逻辑关系的变化,如图2.2所示。
#include<stdio.h> #define MAXSIZE 5 //MAXSIZE是根据实际问题定义的足够大的整数常量 typedef int DataType; typedef struct{ DataType data[MAXSIZE]; int length; }SqList; //定义顺序表类型 void InitSqList(SqList *L) { L->length=3; L->data[0]=0; L->data[1]=1; L->data[2]=3; } void InsertSqList(SqList *L,int i,DataType x) { int j; if(L->length==MAXSIZE) { printf("\n顺序表是满的,无法插入元素!"); exit(1); } if(i<1||i>L->length+1) { printf("\n指定的插入位置不存在!"); exit(1); } for(j=L->length-1;j>=i-1;j--) L->data[j+1]=L->data[j]; L->data[i-1]=x; L->length++; } int main() { SqList L; InitSqList(&L); int i=3; int x=2; InsertSqList(&L,i,x); return 0; }
3. 删除运算
顺序表的删除操作是指删除顺序表中的第i个结点,1≤i≤n,
一般地可表示为: 删除前:{a1, a2, …, ai-1, ai, ai+1,…, an}
删除后:{a1,a2, …,ai-1, ai+1, …, an}
删除后表长减1,并且数据元素ai-1、ai和ai+1之间的逻辑关系发生了变化,为了在存储结构上反映这个变化,同样需要移动元素,如图2.3所示。
#include<stdio.h> #define MAXSIZE 5 //MAXSIZE是根据实际问题定义的足够大的整数常量 typedef int DataType; typedef struct{ DataType data[MAXSIZE]; int length; }SqList; //定义顺序表类型 void InitSqList(SqList *L) { L->length=4; L->data[0]=0; L->data[1]=1; L->data[2]=4; L->data[3]=2; } void DeleteSqList(SqList *L,int i) { int j; if(L->length==0) { printf("\n顺序表是空的,无法删除元素!"); exit(1); } if(i<1||i>L->length) { printf("\n指定的删除位置不存在!"); exit(1); } for(j= i;j< L->length;j++) L->data[j-1]=L->data[j]; L->length--; } int main() { SqList L; InitSqList(&L); int x=3; DeleteSqList(&L,x); return 0; }
4. 按值查找
给定数据x,在顺序表L中查找第一个与它相等的数据元素。如果查找成功,则返回该元素在表中的位置;如果查找失败,则返回-1。
#include<stdio.h> #define MAXSIZE 5 //MAXSIZE是根据实际问题定义的足够大的整数常量 typedef int DataType; typedef struct{ DataType data[MAXSIZE]; int length; }SqList; //定义顺序表类型 void InitSqList(SqList *L) { L->length=4; L->data[0]=0; L->data[1]=1; L->data[2]=4; L->data[3]=2; } int LocationSqList(SqList *L, DataType x) { int i; for (i=0; i<L->length; i++) if (L->data[i] == x) //查找成功,返回元素位置i return i+1; if (i == L->length) //查找失败,返回-1 return -1; } int main() { SqList L; InitSqList(&L); LocationSqList(&L, 4); return 0; }
有顺序表A和B,其元素均按从小到大的升序排列,编写一个算法将它们合并成一个顺序表C,要求C的元素也是从小到大的升序排列。
#include<stdio.h> #define MAXSIZE 5 //MAXSIZE是根据实际问题定义的足够大的整数常量 typedef int DataType; typedef struct{ DataType data[MAXSIZE]; int length; }SqList; //定义顺序表类型 void InitSqList(SqList *L) { L->length=4; L->data[0]=0; L->data[1]=1; L->data[2]=2; L->data[3]=4; } void InitSqList1(SqList *L) { L->length=4; L->data[0]=0; L->data[1]=1; L->data[2]=3; L->data[3]=5; } void merge(SqList A, SqList B, SqList *C) { int i,j,k; i=0;j=0;k=0; while (i<=A.length-1 && j<=B.length-1) if (A.data[i]<B.data[j]) C->data[k++]=A.data[i++]; else C->data[k++]=B.data[j++]; while (i<=A.length-1) C->data[k++]=A.data[i++]; while (j<=B.length-1) C->data[k++]=B.data[j++]; C->length=k; } int main() { SqList L,L1,L2; InitSqList(&L); InitSqList1(&L1); merge(L,L1,&L2); return 0; }
有一线性表的顺序表示 (a1,a2,… ,an) ,设计一算法将该线性表逆置成逆线性表(an,an-1,… ,a1),要求用最少的辅助空间。
3️⃣✨✨✨线性表的链式表示及实现✨✨✨
线性表的链式存储结构是用一组任意的存储单元存储线性表的数据元素。 结点的结构如图所示,其中存放数据元素信息的称为数据域,存放其后继地址的称为指针域,n个元素的线性表通过每个结点的指针域链结成一个链表,又因为每个结点里只包含一个指向后继的指针,所以称其为单链表。
typedef struct Node { DataType data; struct Node *next; }LNode , *LinkList;
如要将指向线性表中的某一结点的指针变量p说明为“LNode* 类型”,可以定 义为: LNode *p; 也可以定义为: LinkList p; 而要完成申请一块LNode类型的存储单元的操作,则需执行如下语句:
p=(LinkList)malloc(sizeof(LNode));
“头指针”通常用L来标识,如单链表L=(k1,k2,k3,k4,k5),是指该链表的第一个结点的地址放在了指针变量L中
有时,单链表的第一个结点之前附设一个结点,我们称为头结点。头结点的数据域可以不存储任何信息,也可存储如线性表的长度等类的附加信息
4️⃣ ✨✨✨单链表的基本运算的实现✨✨✨
1. 建立单链表
(1)在链表的头部插入结点建立单链表
#define flag -1 //定义数据输入结束的标志数据 void CreateLinkList1(LinkList L) { LNode *s; //定义指向当前插入元素的指针 DataType x; scanf("%d",&x); //根据元素类型而定,假定元素类型为整型 while(x!=flag) { s= (LinkList)malloc(sizeof(LNode)); //为当前插入元素的指针分配地址空间 s->data=x; s->next=L->next; L->next=s; scanf("%d",&x); } }
如果调用函数是main函数,主函数对建立单链表的调用如下: void main() { LinkList L; L=(LinkList)malloc(sizeof(LNode)); //为头结点申请空间 L->next=NULL; //建立空链表 CreateLinkList1(L); }
(2)在链表的尾部插入结点建立单链表
#define flag -1 //定义数据输入结束的标志数据 void CreateLinkList2(LinkList L) { LNode *r, *s; //s为指向当前插入元素的指针,r为尾指针 DataType x; scanf("%d",&x); //根据元素类型而定,假定元素类型为整型 r=L; while(x!=flag) { s=(LinkList)malloc(sizeof(LNode));//为当前插入元素的指针分配地址空间 s->data=x; r->next=s; r=s; scanf("%d",&x); } r->next=NULL; }
2. 查找运算
1)按序号查找
给定序号i,查找出单链表中的第i个位置的结点指针。首先从链表的第一个元素开始,判断当前结点是否是第i个结点,若是,则返回该结点的指针,否则继续后一个,直到链表结束为止。没有第i个结点时返回空指针]
LNode *GetLinkList(LinkList L,int i) { LNode *p; int j; //j是计数器,用来判断当前的结点是否是第i个结点 p=L; j=0; while(p!=NULL&&j<i) { p=p->next; //当前结点p不是第i个且p非空,则p移向下一个结点 j++; } return p; }
2)按值查找
按值查找,也称定位。从链表的一个元素开始,判断当前结点值是否等于x,若是,则返回该结点指针,否则继续后一个,直到链表结束。找不到时返回空指针
LNode *LocationLinkList(LinkList L,DataType x) { LNode *p; p=L->next; while(p!=NULL&&p->data!=x) { p=p->next; } return p; }
3. 插入运算
1)插入结点
设p指向单链表中某元素a的结点,s指向待插入的值为x的新结点,将s插入到p结点的后面
s->next=p->next; p->next=s;
将一个值为x的结点插入到单链表的第i个位置,这就要求查找到第i-1个结点指针p,若p存在,在p后面执行插入新结点操作,否则结束,插入不成功
void InsertLinkList(LinkList L,int i,DataType x) { //在单链表L中第i个位置插入值为x的新结点 LNode *p, *s; p=GetLinkList(L, i-1); //寻找到链表的第i-1个位置结点 if(p==NULL) { printf("插入位置不合法!"); exit(1); } else { s=(LinkList)malloc(sizeof(LNode)); s->data=x; s->next=p->next; p->next=s; } }
4. 删除运算
1)删除结点
假设p指向单链表中某一结点,删除p结点的后继结点
删除语句:LNode *q=p->next; p->next=q->next; 或p->next=p->next->next; free(q);
删除单链表的第i个位置结点,这就要求查找到第i-1个结点指针p,若p存在,在p后面执行删除结点操作,否则结束,删除不成功
void DeleteLinkList(LinkList L, int i) { //删除单链表上的第i个结点 LNode *p,*q; p=GetLinkList(L,i-1); if(p==NULL) { printf("删除位置不合法!"); //第i个结点的前驱结点不存在,不能执行删除操作 exit(1); } else { if(p->next==NULL) { printf("删除位置不合法!");//第i个结点不存在,不能执行删除操作 exit(1); } else { q=p->next; p->next=p->next->next; free(q); } } }
5.求表长运算
int LengthLinkList(LinkList L) { int len; //len记录L的表长 LNode *p; p=L; len=0; while(p->next) { len++; p=p->next; } return len; }
已知单链表L,写一个算法将其倒置,如图所示,要求使用最少的存储空间。
算法思路:依次取原链表的每一个结点,总是将其作为新链表当前的第一个结点插入到新链表中,指针p用来指向当前结点,p为空时结束
void reverse(LinkList L) { LNode *p, *q; p=L->next; L->next=NULL; while(p) { q=p; p=p->next; q->next=L->next; L->next=q; } }
已知单链表L和表中某一结点指针p,写一算法求p的直接前驱。
LNode *PreLNode(LinkList L,LNode *p) { LNode *q; q=L; while(q->next!=p) { q=q->next; } return q; }
已知一个带表头结点的递增单链表。试编写一个算法,功能是从表中去除值大于min,且值小于max的数据元素
void DelList(LinkList L,int min, int max) { LNode *ptr,*qtr, *s; ptr=L->next; //ptr指向链表的起始结点 qtr=L; while ((ptr!=NULL) && (ptr->Data<= min)) //跳过所有值<=min的结点 { qtr=ptr; ptr=ptr->next; } while ((ptr!=NULL) && (ptr->Data<max)) //若结点值<max,则去除 { s=ptr; //存放结点值在min与max之间的临时指针 ptr=ptr->next; qtr->next=ptr; free(s); } }
将两个有序链表合并成一个有序链表。 假设头指针为La和Lb的单链表分别是线性表LA和LB的存储结构,现要归并La和Lb得到单链表Lc
LinkList MergeList(LinkList La, LinkList Lb) { LNode *p,*q,*Lc,*r; p=La->next; //p指向La当前要比较插入的结点 q=Lb->next; //q指向Lb当前要比较插入的结点 Lc=r=La; //用La的头结点作为Lc的头结点,r指向Lc的尾结点 while(p&&q) { if(p->data<=q->data) { r->next=p;r=r->next;p=p->next; } else{ r->next=q;r=r->next;q=q->next;} } if(p)r->next=p; //若La有剩余,则插入剩余段 else r->next=q; //若Lb有剩余,则插入剩余段 free(Lb); //释放Lb的头结点 return Lc; }
5️⃣ ✨✨✨循环单链表✨✨✨
差别仅在于算法中的循环条件p->next是否等于NULL变成是否等于头结点L。 有时需要对链表常做的操作是在表头、表尾进行,我们可以在循环单链表中标识尾指针而不设头指针,例如,将单链表R1和R2合并成一个表,其中R1和R2分别是这两个链表的尾指针,这时只要将一个表的表尾和另一个表的表头连接相接,仅需改变两个指针
LinkList p=R2->next; R2->next=R1->next; R1->next=p->next; free(p);
6️⃣ ✨✨✨双向链表✨✨✨
双向链表结点的定义如下: typedef struct DNode { DataType data; struct DNode *prior,*next; }DLNode,*DLinkList;
和单链表的循环表类似,双向链表也可以有循环表,如图2.17(b)所示,链表中存在两个环,图2.17(a)所示为只有一个头结点的空表。
在双向链表中,若p为指向表中某一结点的指针,则显然有: p->next->prior=p->prior->next=p;
1. 插入结点
① s->next=p->next; ② p->next->prior=s; ③ s->prior=p; ④ p->next=s;
2. 删除结点
DLinkList q=p->next; //用q指向被删除的结点 ① p->next=q->next; ② q->next->prior=p; free(q);
void InsertDLinkList(DLinkList L,int i,DataType x) {//在带头结点的双向循环链表第i(1≤i≤n+1)个位置插入元素x DLNode *p,*s; p=GetDLinkList(L,i-1); //寻找到链表的第i-1个元素结点 if(p==L) { printf("插入位置不合法!"); exit(1); } else { s=(DLinkList)malloc(sizeof(DLNode)); s->data=x; s->next=p->next; p->next->prior=s; s->prior=p; p->next=s; } }
void DeleteDLinkList(DLinkList L,int i) {//删除带头结点的双向循环链表第i(1≤i≤n)个元素 DLNode *p,*q; p=GetDLinkList(L,i-1); if(p==L) { printf("删除位置不合法!");//第i个元素结点的前驱结点不存在,不能执行删除操作 exit(1); } else { if(p->next==L) { printf("删除位置不合法!");//第i个元素结点不存在,不能执行删除操作 exit(1); } else { q=p->next; p->next=q->next; q->next->prior=p; free(q); } } }
7️⃣ ✨✨✨静态链表✨✨✨
#define MAXSIZE 100 //链表的最大长度,可以自行定义 typedef struct { DataType data; int cursor; }SLinkList[MAXSIZE]; SLinkList S;
假如有静态链表S中存储线性表(a, b, c, d, f, g, h, i),MAXSIZE=11,如图2.19(a)所示,要在第四个元素后插入元素e,方法是:先在当前表尾加入一个元素e,即 S[9].data = e; 然后修改第四个元素的游标域,将e插入到链表中,即 S[9].cursor = S[4].cursor; S[4].cursor = 9; 如图2.19(b)所示,接着,若要删除第7个元素h,则先顺着游标链通过计数找到第6个元素存储位置6,删除的具体做法是令 S[6].cursor = S[7].cursor
静态链表的基本运算实现与单链表类似,初始化静态链表的算法: void InitSLinkList(SLinkList S) //初始化一个空静态链表 { S[0].cursor=0; }
求表长算法: int LengthSLinkList(SlinkList S) { int len,c; //len存放S的表长,c记录当前数组分量的位置 len=0; c=0; while(S[c].cursor!=0) { c=S[c].cursor; len++; } return len; }
按序号查找算法: int GetSLinkList(SLinkList S,int i) { int c; int j; //j是计数器,用来判断当前的结点是否是第i个结点 c=S[0].cursor; j=0; while(c!=0&&j<i) { c=S[c].cursor; //当前结点p不是第i个且p非空,则p移向下一个结点 j++; } return c; }
插入运算: void InsertSLinkList(SLinkList S,int i,DataType x) //在静态链表S中第i个位置插入值为x的新结点 { int c,len; //c记录当前数组分量的位置,len记录S的表长 c=GetSLinkList(L, i-1); //寻找到链表的第i-1个位置结点 if(c==0) { printf("插入位置不合法!"); exit(1); } else { len=lengthSLinkList(S); S[++len].data=x; //元素x存放在表尾,表长len加1 S[len].cursor=S[c].cursor; S[c].cursor=len; } }
删除运算: void DeleteSLinkList(SLinkList S, int i) //删除静态链表上的第i个结点 { int c; c=GetSLinkList(S, i-1); if(c==0) { printf("删除位置不合法!"); //第i个结点的前驱结点不存在,不能执行删除操作 exit(1); } else { if(S[c].cursor==0) { printf("删除位置不合法!");//第i个结点不存在,不能执行删除操作 exit(1); } else { S[c].cursor= S[S[c].cursor].cursor;} } }
表长为n的静态链表创建过程可以执行n次插入运算,具体如算法2.25所示。 void CreateSLinkList(SLinkList S) { DataType x; InitSLinkList(S); for(int i=1;i<=n;i++) { scanf("%d",&x); //假设x为整型 InsertSLinkList(S,i,x); } }
8️⃣✨✨✨线性表的应用举例✨✨✨
🅰一元多项式的表示及相加
在数学上,一个一元多项式Pn(x)可按升幂形式写为:
它由n+1个系数唯一确定。因此,在计算机中,它可用一个线性表P来表示:
每一项的指数i隐含在其系数 的序号中。
假设Q是一元m次多项式,同样用一个线性表Q来表示:
不失一般性,可设n>m,则两个多项式相加的结果:
可以用线性表R来表示:
我们可以采用顺序存储结构来实现顺序表的方法,使得多项式的相加的算法定义十分简单,即p[0]存系数p0,p[1]存系数p1, …, p[n]存系数p n, 对应单元的内容相加即可。但是在通常的应用中,多项式的指数有时可能会很高并且变化很大。 例如:
若采用顺序存储,则需要2001个空间,而存储的有用数据只有三个,这无疑是一种浪费。 若只存储非零系数项, 则必须存储相应的指数信息才行。
假设一元多项式Pn(x)=p1xe1+p2xe2+…+pmxem,其中pi是指数为ei的项的系数(且0≤e1≤e2≤…≤em=n), 若只存非零系数,则多项式中每一项由两项构成(指数项和系数项),用线性表来表示, 即
线性表的元素类型定义如下: typedef struct { float coef; //系数 int expn; //指数 }DataType;
已知多项式 与多项式
多项式相加如算法: LinkList Add_L(LinkList P,LinkList Q) { LNode *p,*q; LNode *r,*s; //r指向和多项式链表的尾指针,s指向待释放结点 float sum; //sum记录相同指数结点的系数和 p=P->next; q=Q->next; r=P; //r初始指向P多项式链表,并始终指向和多项式尾结点 while(p&&q) { if((p->data).expn<(q->data).expn) {//执行第一种情况时,P链表结点插入到和多项式尾部,且r指针后移 r->next=p; r=r->next; p=p->next; } else if((p->data).expn>(q->data).expn) {//执行第二种情况时,Q链表结点插入到和多项式尾部,且r指针后移 r->next=q; r=r->next; q=q->next; } else { //执行第三种情况时,先判断系数和是否为零 sum=(p->data).coef+(q->data).coef; if(sum!=0) {//和不为零时,系数和重新赋值给p并插入和多项式尾部 //释放q指向结点,另将q后移 (p->data).coef=sum; r->next=p; r=r->next; p=p->next; s=q;q=q->next; free(s); } else { //和为零时,无须插入结点到和多项式尾部, //释放p、q所指结点,另将p、q后移 s=p; p=p->next; free(s); s=q; q=q->next; free(s); } } } if(p) //若链表P还有待处理的结点,链接P链表中剩下结点 r->next=p; else //否则,链接Q链表中剩下结点 r->next=q; free(Q); //释放Q的头结点 return P; //返回和多项式头链表 }
🅱 约瑟夫环问题
设有编号为1,2,…,n的n(n>0)个人围成一个圈,从某个人开始报数,报到m时停止报数,报m的人出圈,再从他的下一个人起重新报数,报到m时停止报数,报m的出圈,……,如此下去,直到所有人全部出圈为止。当任意给定n和m后,设计算法求n个人出圈的次序。 显然,循环单链表可以很好地描述这个问题。我们将编号为1,2,…,n的n(n>0)个人围成一个圈表示成一个不带头结点的循环单链表L,其中L指向第一个结点,每个编号对应一个结点。
假设从第k个人开始报数,即从第k个结点开始报数,报m的人出圈,也就是删除报m的结点,再从它的下一结点重新报数,报到m时删除该结点,如此下去,直到所有结点删除完为止。
创建n个编号结点的循环单链表如算法: void Create_L(LinkList &L,int n) { int i; LNode *s,*r; L=NULL; r=L; for(i=1;i<=n;i++) { s=(LinkList)malloc(sizeof(LNode)); s->data=i; if(L==NULL){L=s; r=s;} else{r->next=s;r=r->next;} } r->next=L; }
void Josephus(LinkList L,int k,int n,int m) { LNode *s; LNode *t; s=GetLinkList(L,k-1); printf("所有人出队序列如下:\n"); while (s->next!=s) { for (int i=1; i<m; i++) //先数m-1个数 { t=s; s=s->next; } //把数到m的人从链表中删除 t->next=s->next; printf("%d\t",s->data); //输出数到m的人的编号 free(s); s=t->next; } printf("%d\n",s->data); //输出最后一个人的编号 free(s); }
假设调用函数为main函数,且已知n=6,m=4,k=3,调用函数如下: void main() { LinkList L; Create_L(L,6); Josephus(L,3,6,4); } 最后出队序列编号为6, 4, 3, 5, 2, 1。