一、题目
二、分析思路
2.1 审题和理解
我们把题目看了一遍之后,先总结出以下重要的几点:
-
初始有n个孤立的数字,分别是 1 ∼ n 1 \sim n 1∼n,两个孤立的数字发生碰撞会产生一个环,比如 9 → 7 9 \rightarrow 7 9→7会产生上面示例图中最后的那个环
-
把环写出来需要把环里面最小的数字放在前面,比如只能写成【7-9】不能写成【9-7】,还有示例里的输出也是这样的
-
环和环之间也可能发生碰撞, x → y x \rightarrow y x→y表示两个环上发生碰撞融合的点,比如有环 R 1 R_1 R1=【1-2】和 R 2 R_2 R2=环【3-4-5】,发生碰撞 1 → 4 1 \rightarrow 4 1→4,碰撞融合的过程相当于,把 R 1 R_1 R1从数字1处断开, R 2 R_2 R2把环解开捋直成链,把4接在1后面,最后一整条链再圈成环
2.2 思路
C++双向链表
经过上一步的分析以及题目的暗示,我们很容易可以想到要用双向链表这个数据结构来表示环。碰撞就是两个双向链表的相连
struct point {
struct point* pre;//指向上一个数字
int value;
bool flag;//用于输出
struct point* next;//指向下一个数字
};
一开始所有的泡泡环都只有一个数字,我们先来初始化一下,创建一个指针数组a,数组里每一个元素都是一个指向结构体point的指针,即a[i]指向初始的带数字 i 的泡泡环
struct point* a[10000001]; //指针数组,数组的元素是指向每个数字的指针
for (int i = 1; i <= n; i++)
{
a[i] = (struct point*)malloc(sizeof(struct point));//分配内存空间
a[i]->value = i;
a[i]->flag = 0 //0表示还未输出过,1表示已经打印过了
a[i]->next = a[i]->pre = a[i];//重点语句
}
接着我们来看具体如何实现泡泡环的碰撞
到这里其实就基本完成了,只剩下输出打印所有泡泡环,题目要求从泡泡环里数字最小的地方开始输出,这个简单我们只需要for(int i=1;i<=n;i++)遍历a[i],a[i]指向的数字是从小到大,本身保证了题目要求,每一个泡泡环的第一个数字一定是当前最小的。比如1->2->3->4这个泡泡环,肯定是第一次遍历 i=1,从a[1]开始,根据next一直输出,直到再次回到a[1]。由于2 3 4已经在第一个泡泡环里了,后面遍历到a[2],a[3],a[4]直接跳过就行,这个时候就需要用到上面设置的flag了。
for(int i = 1; i <= n; i++)
if(a[i]->flag == 0)//0表示还未输出
{
p1 = a[i];
do{
cout << p1->value << " ";//初始化时a[i]->value = i
a[p1->value]->flag = 1;//1表示该value已经被输出过,后面遍历到的时候跳过
p1 = p1->next;
}while(p1 != a[i]);
cout << endl;S
}
python列表
这道题也能用python列表实现,建立一个二维列表b,b里有n+1个元素,代表初始时的n个孤立泡泡环,泡泡环用列表来表示,方向是从左到右的方向
b = [[0],[1],[2],[3],[4],[5],...,[n-1],[n]]
b[i]表示数字 i 的泡泡环,b[0]闲置不用
碰撞 x → y x \rightarrow y x→y为了方便看出哪个在前/左,哪个在后/右,写成 l → r l \rightarrow r l→r
直接用题目给的例子来理解
b = [[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]]
先来看一开始最简单的碰撞,比如 1 → 2 1 \rightarrow 2 1→2,会得到[1 , 2],相当于在b[1]中1的位置后面插入2,即b[1]=[1,2],那么此时b[2]就不再是孤立的泡泡环了,数字2已经在b[1]这个环里了。为了表示数字2在b[1]这个泡泡环里面,我们令b[2] = 1 本来b[2]=2表示 2 位于b[2],是一个孤立泡泡环,而现在 2 经过碰撞位于b[1]了。
b = [[0],[1,2],1,[3],[4],[5],[6],[7],[8],[9]]
接着假设发生碰撞 6 → 4 6 \rightarrow 4 6→4,会得到[6 , 4],相当于在b[6]中6的位置后面插入4,得到b[6] = [6 , 4],和上面一样同理。但是有一个不同的是,我们不打算就让b[6] = [6 , 4],而b[4]=6。而是改成b[4]=[6 , 4],b[6]=4。因为后面输出的时候,我们更加希望如果b[i]是列表,那么b[i]代表的泡泡环中,i 就是环中最小的数字。这样输出的时候只需要找到数字 i 的索引,从此处开始输出列表
# 如果b[i]是列表,那么b[i]代表的泡泡环中,i就是环中最小的数字
for i in range(1, n+1):
if isinstance(b[i], list):
idx = b[i].index(i)
print(*(b[i][idx:] + b[i][:idx]), sep=' ')
那么b就变成下面这样子,b[6]=4,表示指向b[4]那个泡泡环
b = [[0],[1,2],1,[3],[6,4],[5],4,[7],[8],[9]]
如果是两个泡泡环的碰撞,道理和上面一样,只是改成在列表中插入列表。可以利用切片巧妙地实现。
另外注意如果b[i]不是列表而是数字,如果b[2]=1和b[6]=4,发生碰撞时就需要检查b[i]是不是列表,如果检查发现b[i]的数据类型是int,那就需要根据这个int数找到它所在的泡泡环
l, r = map(int, input().split(' '))
id_l = l # 用来表示数字l所在的泡泡环下标,一开始数字i就在孤立泡泡环b[i]里
id_r = r # 用来表示数字r所在的泡泡环下标
while isinstance(b[id_l], int): # 找到数字l所在的环b[id_l]
id_l = b[id_l]
while isinstance(b[id_r], int): # 找到数字r所在的环b[id_r]
id_r = b[id_r]
三、代码实现
3.1、C++实现
#include<bits/stdc++.h>
#include<iostream>
#include<vector>
using namespace std;
struct point {
struct point* pre;
int value;
bool flag;
struct point* next;
};
int main()
{
int n, m, x, y;
cin >> n >> m;
struct point* a[10000001]; //指针数组,数组的元素是指向每个数字的指针
struct point* p1=NULL, * p2=NULL;
// 初始化
for (int i = 1; i <= n; i++) {
a[i] = (struct point*)malloc(sizeof(struct point));//分配内存空间
a[i]->value = i;
a[i]->next = a[i]->pre = a[i];//重点语句
a[i]->flag = 0;//0表示还没打印,1表示已打印(暂时不用关注这句话)
}
// 碰撞
for (int i = 0; i < m; i++) {
cin >> x >> y;
p1 = a[x]->next; p2 = a[y]->pre;//记录断点,以便后面重新接上
a[x]->next = a[y];
a[y]->pre = a[x];
p2->next = p1; p1->pre = p2;
}
// 输出每一个泡泡
for(int i = 1; i <= n; i++)
{
if(a[i]->flag == 0)//0表示还未输出
{
p1 = a[i];
do{
cout << p1->value << " ";//初始化时a[i]->value = i
a[p1->value]->flag = 1;//1表示该value已经被输出过,后面遍历到的时候跳过
p1 = p1->next;
}while(p1 != a[i]);
cout << endl;
}
}
for (int i = 0; i < n; i++)free(a[i]);//释放内存空间
return 0;
}
3.2、python代码实现
n, m = map(int,input().split(' '))
b = [[i]for i in range(n+1)]
# print(b)
for _ in range(m):
l, r = map(int, input().split(' '))
id_l = l # 碰撞方
id_r = r # 被碰撞方
while isinstance(b[id_l], int): # 找到数字l所在的环b[id_l]
id_l = b[id_l]
while isinstance(b[id_r], int): # 找到数字r所在的环b[id_r]
id_r = b[id_r]
cut_l = b[id_l].index(l) + 1 # 碰撞位置
cut_r = b[id_r].index(r) # 碰撞位置
b[id_l][cut_l:cut_l] = (b[id_r][cut_r:] + b[id_r][:cut_r])
maxv = max(l, r)
if id_l < id_r:
b[id_r] = id_l
b[maxv] = id_l
else:# id_r < id_l
b[id_r] = b[id_l]
b[id_l] = id_r
b[maxv] = id_r
# print(b)
for i in range(1, n+1):
if isinstance(b[i], list):
idx = b[i].index(i)
print(*(b[i][idx:] + b[i][:idx]), sep=' ') # 巧妙用法
题目示例的运行过程:
四、总结
-
这道题比较容易想,可以很明显地看出需要使用双向链表来模拟泡泡环,最主要的是模拟碰撞,双向链表注意插入的细节问题,指针next和pre域的改变顺序不能乱
-
突发奇想又尝试了用python实现,不得不说,想的时候容易,实现起来还是很费时间,而且很明显逻辑和思路没有C++双向链表那么清晰易懂。所以写成文字说明还是有难度的,希望大家看得懂,不过这种实现思路还是很有趣的
-
最终尝试了把两种代码放到平台上跑,最终发现python的运行时间明显比C++长,仔细看了一眼代码也发现C++版本很典型的空间换时间,而python则相反,用时间换空间,而码题集上给的内存空间是很充足的,相比之下时间的限制比较大,所以也是为后面写题的时候提供了点思路:如果后面运行超时,可以尝试牺牲空间换时间
⭐如有不足之处,请多指教!⭐