题目
给出两条链表的首地址以及若干结点的地址、数据、下一个结点的地址,求两条链表的首个共用结点的地址。如果两条链表没有共用结点,则输出-1.
思路
(1)由于地址的范围很小,因此可以直接用静态链表,但是依照题目的要求,在结点的结构体中再定义一个int型变量flag,表示结点是否在第一条链表中出现,是则为1,不是为-1.
(2)由题目给出的第一条链表的首地址出发遍历第一条链表,将经过的所有结点的flag值赋为1.
接下来枚举第二条链表,当出现第一个flag值为1的结点,说明第一条链表中出现过的结果,即为两条链表的第一个共用结点。如果第二条链表枚举完仍然没有发现共用结点,则输出-1.
注意点
(1)使用%05d格式输出地址,可以使不足5位的整数的高位补0;
(2)使用map容易超时
(3)scanf使用%c格式时是可以读入空格的,因此在输入地址、数据、后继结点地址时。格式不能写成%d%c%d,必须在中间加空格。
#include<cstdio>
#include<cstring>
const int maxn=100010;
struct node{char data;int next;bool flag;
}node[maxn];
int main(){for(int i=0;i<maxn;i++){node[i].flag=false;}int s1,s2,n;scanf("%d%d%d",&s1,&s2,&n);int address,next;char data;for(int i=0;i<n;i++){scanf("%d %c %d",&address,&data,&next);node[address].data=data;node[address].next=next;}int p;for(p=s1;p!=-1;p=node[p].next){node[p].flag=true;}for(p=s2;p!=-1;p=node[p].next){if(node[p].flag==true){break;}}if(p!=-1){printf("%05d\n",p);}else{printf("-1\n");}return 0;
}
以上是静态链表所能解决的比较简单的题,而对一些稍微复杂的题,此处归纳出了一类问题的通用解题步骤。
(1)定义静态链表
struct node{int address;//结点地址typename data;int next;xxxx;//结点的某个性质,不同的题目会有不同的设置
}node[100010];
(2)在程序的开始,对静态链表进行初始化,一般来说,需要对定义中的XXXX进行初始化,将其定义为正常情况下达不到的数字(一般来说,需要小于所有能达到的数字)。例如对结点是否在链表上这个性质来说,我们可以初始化为0,表示结点不在链表上。
for(int i=0;i<maxn;i++){node[i].XXXX=0;
}
(3)题目一般都会给出一条链表的首结点的地址,那么我们就可以依据这个地址来遍历得到整条链表。需要注意的是,这一步同时也是我们对结点的性质XXXX进行标记、并且对有效结点的个数进行计数的时候,例如对结点是否在链表上这个性质来说,当我们遍历链表时,就可以把XXXX置为1.
int p=begin,count=;
while(p!=-1){XXXX=1;count++;p=node[p]->next;
}
(4)由于使用静态链表时,是直接采用地址映射(hash)的方式,这就会使得数组下标的不连续,而很多时候题目给出的结点并不都是有效结点(即可能存在不在链表上的结点)。为了能够可控地访问有效结点,一般都需要用对数组进行排序以把有效结点移到数组左端,这样就可以用步骤3得到的count来访问它们。
既然需要把有效结点移到前面,那么就可以用之前定义的XXXX来帮忙。在步骤2中,XXXX需要被初始化为比正常结点的XXXX取值更小的数值,这个做法就可以在这一步起到作用。
由于无效结点的XXXX在步骤3中不会被修改,因此一定比有效结点的XXXX小。于是在写sort的排序函数cmp时,就可以在cmp的两个参数结点中有无效结点时按XXXX从大到小排序。这样就可以把有效结点全部移到数组左端。
一般来说,题目一定会有额外的要求,因此cmp函数中一般都需要有第二级排序,不过这需要以不同的题目要求来确定。例如,如果题目的要求需要把链表的按结点顺序排序,就需要在cmp函数中建立第二级排序,即在cmp的两个参数结点中有无效结点时按XXXX从大到小排序,而当两个参数结点都是有效结点时按结点在链表中的位置从大到小排序(结点的顺序可以在第三步得到)
bool cmp(node a,node b){if(a.XXXX==-1||b.XXXX==-1){return a.XXXX>b.XXXX;}else{//第二级排序 }
}
(5)在经历了步骤4后,链表中的有效结点就都在数组左端了,且已经按结点的性质进行排序,接下来就要看题目在排序之后具体要求做什么了。
题目运用
给出N个结点的地址address,数据域data以及指针域next,然后给出链表的首地址,要求把在这个链表上的结点按data值从小到大输出。
思路
此题目可以直接套用上面的解题步骤
(1)定义静态链表,其中结点性质由bool型变量flag定义,表示为结点在链表中是否出现。flag为false表示无效结点(不在链表上的结点)
(2)初始化,令flag均为false,表示初始状态下所有结点都是无效结点。
(3)由题目给出的链表的首地址begin遍历整条链表,并标记有效结点的flag为true,同时计数有效结点的个数count。
(4)对结点及逆行排序,排序函数cmp的排序原则是:如果cmp的两个参数结点中有无效结点的话,则按flag从大到小排序,以把有效结点排在数组左端(因为有效结点的flag为1,大于无效结点的flag)。否则按数据域从小到大排序。
(5)由于有效结点已经按照数据域从小到大排序,因此按要求输出有效结点即可。
注意点
(1)可以直接使用%05d的输出格式,以在不足五位时在高位补0。但是要注意-1不能使用%05d输出,否则会输出-0001(而不是-1或者-00001),因此必须留意-1的输出。
(2)题目可能会有无效结点,即不在题目给出的首地址开始的链表上。
(3)数据里面还有均为无效的情况,这时就要根据有效结点的个数特判输出“0 -1”
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100005;
struct Node{int address,data,next;bool flag;
}node[maxn];
bool cmp(Node a,Node b){if(a.flag==false||b.flag==false){return a.flag>b.flag;}else{return a.data<b.data;}
}
int main(){for(int i=0;i<maxn;i++){node[i].flag=false;}int n,begin,address;scanf("%d%d",&n,&begin);for(int i=0;i<n;i++){scanf("%d",&address);scanf("%d%d",&node[address].data,&node[address].next);node[address].address=address;}int count=0,p=begin;while(p!=-1){node[p].flag=true;count++;p=node[p].next;}if(count==0){printf("0 -1");}else{sort(node,node+maxn,cmp);//防止-1被%05d化,提前判断 printf("%d %05d\n",count,node[0].address);for(int i=0;i<count;i++){if(i!=count-1){printf("%05d %d %05d\n",node[i].address,node[i].data,node[i+1].address);}else{printf("%05d %d -1\n",node[i].address,node[i].data);}}}return 0;
}