1.链表
链表不要求元素的存储位置连续,因此为了表示数据元素之间的关系,对于每个元素需要增加指针域来记录其直接后继和直接前驱的位置。
只记录后继或前驱位置的链表称为单向链表,即记录前驱又记录后继的链表称为双向链表。
链表中一般会有一个头指针(head)指向首元素或者尾指针(tail)指向尾元素。
2.链表的实现
实际使用中,一般通过结构体数组来模拟链表,指针域记录后继/前驱在数组中的下标。
链表的查找复杂度为O(N) ,插入和删除的时间复杂度为O(1) (只需要修改指针域3)
3.链表的作用
链表一般应用在只知道数据总量,不确定各部分数据量的题目中,用来节省存储空间
(1)示例:给定N( 2 ≤ N ≤ 10^5 个人,编号1…N,其中有M 1 ≤ M≤10^6 对人之间有亲戚关系,给定这M对人,输出每个人的所有亲戚。
输入: 4 5 1 2 1 3 2 3 4 1 2 4
输出:2 3 4 1 3 4 1 2 1 2
思路:为每个人分配1个独立的线性表,用来存储其所有亲戚,输出时遍历线性表。
顺序表实现:每个人至多可能有N-1个亲戚,每个数组的大小至少为N-1,共N(N-1)。
使用链表实现,由于只有M对人之间有亲戚关系,每对关系需要记录两次,最多2*M条记录
每个链表需要一个指向首条元素的head指针,N个链表可以定义数组存储
|-------------------------------------------------------------------------------------------------------------------------------|
关键来了!!!!!
当插入、删除、移动位置操作比较多时,使用数组必定会超时,此时就可以使用链表来进行时间优化。
使用双向链接记录每个数据的左指针、右指针以及数据值,其中指针指的是数组下标
(1)插入操作
0 a1 2 1 a2 3 2 a3 0
例如,在a1和a2之间插入a4,进行如下操作
① 将a4.left赋值为a2的left a4.left=a2.left
② 将a4的rigth赋值为2 a4.right=2;
③将a1的rigth赋值为4 a1.right=4;
④ 将a2的left赋值为4 a2.left=4 1 a4 2
0 a1 4 4 a2 3 2 a3 0 1 a4 2
(2)删除操作
0 a1 2 1 a2 3 2 a3 0
例如,将a2删除,进行如下操作:
① 将a2的right对应下标的值的left更改为a2的left a[a2.right].left=a2.left
② 将a1right赋值为a2的right a1.right=a2.right
0 a1 3 1 a3 0
(3)移动操作
0 a1 2 1 a2 3 2 a3 4 3 a4 0
例如,将a3移动到a2的左边,进行如下操作:
① 将a3的left赋值为a2的left a3.left=a2.left
② 将a2的right赋值为a3的right a2.right=a3.right
③ 将a3的right下标对应的元素的left赋值为2 a[a3.right].left=2
④ 将a3的right赋值为2 a3.right=2
⑤ 将a2left对应下标的元素的right赋值为3,a[a2.left].right=3;
⑥ 将a3left赋值为a2的left,a3.left=a2.left;
⑦ 将a2left赋值为3,a2.left=3;
0 a1 3 3 a2 4 1 a3 2 2 a4 0
2.例题精讲:《移动盒子》
题目描述
桌子上有n个盒子排成一条直线,从左到右编号是1到n,你的问题是模拟下面4中操作:
1 X Y:将编号为x的盒子移动到编号为Y的盒子的左边(如果X已经在Y的左边了,则忽略)
2 X Y: 将编号为X的盒子移动到编号为Y的盒子的右边(如果X已经在Y的又变了,则忽略)
3 X Y:将编号为X的盒子与编号为Y的盒子交换位置
4 : 将桌子上的盒子进行反转
给定的所有操作命令都是合法的,不会出现X等于Y的情况。
例如:n=6,一开始盒子是按照如下顺序摆放的:1 2 3 4 5 6,执行操作1 1 4后,顺序为2 3 1 4 5 6,再执行操作2 3 5,执行后顺序为2 1 4 5 3 6,再执行操作3 1 6,执行后顺序为2 6 4 5 3 1,然后再执行操作4,执行后顺序为1 3 5 4 6 2
输入格式
输入为多组数据,最多10组:
对于每组数据:
第一行,包含两个整数n和m,分别表示桌子上有n个盒子以及要执行的操作数量m,1<=n,m<=100000
接下来m行,每行描述一个操作命令,命令格式如题目描述
输出格式
对于每组数据,输出一行,一个整数,所有奇数位置上的盒子编号之和,位置从左到右编号是1到n。
输入输出样列
输入样例1:
6 4 1 1 4 2 3 5 3 1 6 4 6 3 1 1 4 2 3 5 3 1 6 100000 1 4
输出样例1:
Case 1: 12 Case 2: 9 Case 3: 2500050000
【耗时限制】1000ms 【内存限制】128MB
思路点拨:
如果用数组来存储盒子,那么对于移动操作,例如把盒子x移动到盒子y左边,那么需要把y及y后面的盒子全部都往后挪,挪动一次时间复杂度是O(n),共m次挪动,可能会超时!
可以利用双向链表的性质,用left[i]和right[i]分别存储编号为i的盒子左边和右边的盒子编号,当进行移动操作时,只需要更改相关联的盒子即可
例如,一开始从左到右盒子的编号一次是1,2,3,...,n,执行编号为9的盒子移动到编号为3的盒子左边
假设我们有如下函数可以让两个结点相互连接
void link(int L,int R)
{
Right[L]=R;
Left[R]=L;
}
对于操作1,假设把9移动到3的左边,X是9,Y是3,那么
Y=3,left[3]=2,right[3]=4 --> LY=2,RY=4;
X=9,left[9]=8,right[9]=10 --> LX=8,RX=10
那么依次执行以下操作即可完成移动
link(LX,RX); //实现效果:right[8]=10,left[10]=8
link(LY,X); //实现效果:right[2]=9,left[9]=2
link(X,Y); //实现效果:right[9]=3,left[3]=9
对于操作2,同理假设把3移动到9的右边,X是3,Y是9:
X=3,left[3]=2,right[3]=4 --> LX=2,RX=4; Y=9,left[9]=8,right[9]=10 --> LY=8,RY=10
那么依次执行以下操作即可完成移动:
link(LX,RX); //实现效果:right[2]=4,left[4]=2
link(X,RY); //实现效果:right[3]=10,left[10]=3
link(Y,X); //实现效果:right[9]=3,left[3]=9
对于操作3,假设把3和9进行交换,X是3,Y是9,那么
X=3,left[3]=2,right[3]=4 --> LX=2,RX=4; Y=9,left[9]=8,right[9]=10 --> LY=8,RY=10
那么依次执行以下操作即可完成移动:
link(LX,Y); //实现效果:right[2]=9,left[9]=2
link(Y,RX); //实现效果:right[9]=4,left[4]=9
link(LY,X); //实现效果:right[8]=3,left[3]=8
link(X,RY); //实现效果:right[3]=10,left[10]=3 K13718
对于操作3,如果交换的两个元素本身是相邻的,就会出现自己的左右指针指向自己的情况
假设把3和4进行交换,X是3,Y是4,那么
X=3,left[3]=2,right[3]=4 --> LX=2,RX=4;
那么依次执行以下操作即可完成移动:
link(LX,Y); //实现效果:right[2]=4,left[4]=2
link(Y,X); //实现效果:right[4]=3,left[3]=4
link(X,RY); //实现效果:right[3]=5,left[5]=3 Y=4,left[4]=3,right[4]=5 --> LY=3,RY=5
对于操作4,反转整条链,将last指针和头结点指针对调。但是这样还不够,2、3操作涉及把盒子移动到左边右边,如果反转了,左边右边关系就改变了,如果对每个结点对调左右指针会比较麻烦和费时。如果数据结构上的某一个操作很耗时,有时可以用加标记的方式处理,而不需要真的执行那个操作。
例如,现在有个链条是a->c->d->b
如果没有反转操作,执行一次 1 a b,就是把a放在b的左边,操作后的顺序是c->d->a->b
如果有反转操作,反转2次 第1次反转,
反转后:b->d->c->a
执行1 a b, 执行后:a->b->d->c;
第2次反转:反转后 c->d->b->a
存在上述反转情况下,与没有反转情况下执行了一次把a移动到b的右边的效果一样,即执行一次2ab。反之进行反转,执行2 a b,再反转,执行结果与执行一次1 a b效果一样。
因此可以得出一个结论,用inv标记反转次数,反转一次就取反,两次反转等于没有反转,并且反转操作只会影响操作1和操作2。只不过inv为1时,op==1时就相当于op==2,op==2时就相当于op==1。所以op=3-op。
利用inv这个变量也有利于最后的输出,最后输出发现inv是0的话,就是没逆转,那么直接把奇数位置的数加起来即可,反之,要用总和减去这个偶数序列(因为当n是偶数并且逆转的情况,sum其实是偶数位置,n是奇数时不影响累加和)
详解代码
#include<iostream>
#include<cstdio>
using namespace std;
int n,Left[100005],Right[100005];
void link(int L,int R)
{
Right[L]=R;
Left[R]=L;
}
int main()
{
int m,k=0;
while(cin>>n>>m)
{
for(int i=1;i<=n;i++)
{
Left[i]=i-1;
Right[i]=i+1;
}
Right[0]=1;
Left[0]=n;
int op,X,Y,f=0;
while(m--)
{
cin>>op;
if(op==4) f=!f;
else
{
cin>>X>>Y;
if(op==3&&Right[Y]==X) swap(X,Y);
if(op!=3&&f) op=3-op;
if(op==1&&X==Left[Y]) continue;
if(op==2&&X==Right[Y]) continue;
int LX=Left[X],RX=Right[X],LY=Left[Y],RY=Right[Y];
if(op==1)
{
link(LX,RX);
link(LY,X);
link(X,Y);
}
else if(op==2)
{
link(LX,RX);
link(Y,X);
link(X,RY);
}
else if(op==3)
{
if(Right[X]==Y)
{
link(LX,Y);
link(Y,X);
link(X,RY);
}
else
{
link(LX,Y);
link(Y,RX);
link(LY,X);
link(X,RY);
}
}
}
}
int b=0;
long long ans=0;
for(int i=1;i<=n;i++)
{
b=Right[b];
if(i%2==1) ans+=b;
}
if(f&&n%2==0) ans=(long long) n*(n+1)/2-ans;
printf("Case %d: %lld\n",++k,ans);
}
}
第一次做这么长的文章,望大家多多支持!!!