专题 数据结构——链表

文章目录算法简介1.什么是链表?2.链表结构的优点3链表结构的缺点单向链表双向链表链表的启发式合并参考文献算法简介1.什么是链表?链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。...
摘要由CSDN通过智能技术生成

算法简介

1.什么是链表?

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。链表有很多种不同的类型:单向链表双向链表以及循环链表

2.链表结构的优点

相比较普通的线性结构,链表结构的优点非常突出:

  1. 单个结点创建插入)非常方便,普通的线性内存通常在创建的时候就需要设定数据的大小,而链表可以在 O ( 1 ) O(1) O(1)的时间复杂度里实现;
  2. 结点的删除*非常方便,不需要像线性结构那样移动剩下的数据,同样可以在 O ( 1 ) O(1) O(1)的时间复杂度里实现;
  3. 结点的访问方便,可以通过循环或者递归的方法访问到任意数据;

3.链表结构的缺点

.相比较普通的线性结构,链表结构的缺点也很明显:

  1. 链表平均的访问效率较低。
  2. 链表结构的调试相对复杂。

单向链表

例题 1 UVA11988 破损的键盘 Broken Keyboard (a.k.a. Beiju Text)

思路
本题采用单向链表解决。每输入一个字符就把它存起来,设输入字符串是 s [ 1 ~ n ] s[1~n] s[1n],则可以用 n e x t [ i ] next[i] next[i]表示在当前显示屏中 s [ i ] s[i] s[i]右边的字符编号(即在 s s s中的下标)。

为了方便起见,假设字符串s的最前面还有一个虚拟的 s [ 0 ] s[0] s[0],则 n e x t [ 0 ] next[0] next[0]就可以表示显示屏中最左边的字符。再用一个变量 c u r cur cur表示光标位置:即当前光标位于 s [ c u r ] s[cur] s[cur]的右边。 c u r = 0 cur=0 cur=0说明光标位于“虚拟字符” s [ 0 ] s[0] s[0]的右边,即显示屏的最左边。

为了移动光标,还需要用一个变量 l a s t last last表示显示屏的最后一个字符是 s [ l a s t ] s[last] s[last]

代码如下

#include <bits/stdc++.h>
#define next pre
using namespace std;

const int MAXN=100005;
int last,cur,next[MAXN];
char str[MAXN];

int main()
{
	while(scanf("%s",str+1)!=EOF)
	{
		int n=strlen(str+1);
		cur=last=0;
		next[0]=0;
		for(int i=1;i<=n;++i)
		{
			if(str[i]=='[') cur=0;
			else if(str[i]==']') cur=last;
			else
			{
				next[i]=next[cur];
				next[cur]=i;
				if(cur==last) last=i;//更新“最后一个字符”的编号 
				cur=i;//移动光标 
			}
		}
		for(int i=next[0];i;i=next[i])
			printf("%c",str[i]);
		printf("\n");
	}
	return 0;
}

双向链表

例题 2 UVA12657 移动盒子 Boxes in a Line

思路
本题采用双向链表来解决:用 l e f t [ i ] left[i] left[i] r i g h t [ i ] right[i] right[i]分别表示编号为i的盒子左边和右边的盒子编号(如果是 0 0 0,表示不存在),则下面的过程可以让两个结点相互连接:

void link(int l,int r)
{
	right[l]=r; left[r]=l;
}

有了这个代码,可以先记录好操作之前 x x x y y y两边的结点,然后用link函数按照某种顺序把它们连起来。操作 4 4 4比较特殊,为了避免一次修改所有元素的指针,此处增加一个标记 i n v inv inv,表示有没有执行过操作 4 4 4(如果 i n v = 1 inv=1 inv=1时再执行一次操作 4 4 4,则 i n v inv inv变为 0 0 0)。这样,当 o p op op 1 1 1 2 2 2 i n v = 1 inv=1 inv=1时,只需把 o p op op变成 3 − o p 3-op 3op(显然操作 3 3 3不受 i n v inv inv影响)即可。最终输出时要根据 i n v inv inv的值进行不同处理。

代码如下

#include <bits/stdc++.h>
#define left Left
#define right Right
using namespace std;

const int MAXN=100005;
int n,m,kase=0,inv;
int left[MAXN],right[MAXN];

inline int read()
{
    int X=0; bool flag=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
    while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
    if(flag) return X;
    return ~(X-1);
}

void link(int l,int r)
{
	right[l]=r; left[r]=l;
}

void init()
{
	for(int i=1;i<=n;++i)
	    left[i]=i-1,right[i]=(i+1)%(n+1);
	right[0]=1; left[0]=n;
	inv=0;	
}

void work()
{
	for(int i=1;i<=m;++i)
	{
		int op=read();
		if(op==4) inv=!inv;
		else
		{
			int x=read(),y=read();
			if(op==3&&right[y]==x) swap(x,y);
			if(op!=3&&inv) 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);
			}
		}
	}	
}

void printans()
{
	int b=0;
	long long ans=0;
	for(int i=1;i<=n;++i)
	{
		b=right[b];
		if(i%2==1) ans+=b;
	}
	if(inv&&n%2==0) ans=(long long)n*(n+1)/2-ans;
	printf("Case %d: %lld\n",++kase,ans);
}

int main()
{
//	freopen("input.txt","r",stdin);
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		init();
		work();
		printans();
	}
	return 0;
}

链表的启发式合并

例题 3 洛谷 P3201 [HNOI2009]梦幻布丁

思路
一种颜色的布丁变成另一种颜色就相当于合并两种颜色的布丁,并且两种布丁合并了之后显然不可能分开,所以考虑启发式合并

我们先求出原序列的答案,对于每一种颜色都用链表结构存储起来,并记录下尾节点。每次修改,都根据启发式合并的方法来暴力合并,然后处理一下此次合并对答案的影响。

但是如果我们把 1 1 1 染成 2 2 2并且 ∣ S 1 ∣ &gt; ∣ S 2 ∣ |S_1|&gt;|S_2| S1>S2,那么我们应该把 2 2 2 接到 1 1 1 的后面。这样会有一个问题:本次修改后这个链的颜色是 1 1 1(颜色为 2 2 2 的链被删除了),如果接下来修改颜色 2 2 2(显然这是合法的),会使得找不到颜色 2 2 2 而只能找到颜色 1 1 1 了。所以我们需要使用一个 f f f 数组,表示当我们要寻找颜色 x x x 时,实际上需要寻找颜色为 f [ x ] f[x] f[x] 的链。如果 f [ x ] f[x] f[x]表示的链的大小比 f [ y ] f[y] f[y]大就要交换 f [ x ] f[x] f[x] f [ y ] f[y] f[y]

代码如下

#include <bits/stdc++.h>
using namespace std;

const int MAXN=100005;
const int MAXM=1000005;
int n,m,ans=0;
int c[MAXN],f[MAXM],size[MAXM];
int start[MAXM],next[MAXM],head[MAXM];

inline int read()
{
    int X=0; bool flag=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
    while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
    if(flag) return X;
    return ~(X-1);
}

void merge(int x,int y)
{
	for(int i=head[x]; i;i=next[i]) ans-=(c[i-1]==y)+(c[i+1]==y);
	for(int i=head[x]; i;i=next[i]) c[i]=y;
	next[start[x]]=head[y]; size[y]+=size[x]; head[y]=head[x];
	start[x]=head[x]=size[x]=0;
}

void readdata()
{
	n=read(); m=read();
	for(int i=1;i<=n;++i) 
	{
		c[i]=read(); f[c[i]]=c[i];
		ans+=(c[i]!=c[i-1]);
		if(!head[c[i]]) start[c[i]]=i;
		++size[c[i]]; next[i]=head[c[i]]; head[c[i]]=i;
	}	
	//size指的是c[i]所属的链的大小(start,next,head与之相似);
	//本应用f[c[i]],但此时c[i]=f[c[i]],故直接用的c[i] 
}

void work()
{
	for(int i=1;i<=m;++i)
	{
		int op=read();
		if(op==2) printf("%d\n",ans);
		else
		{
			int x=read(),y=read();
			if(x==y) continue;
			if(size[f[x]]>size[f[y]]) swap(f[x],f[y]);
			if(size[f[x]]==0) continue;
			merge(f[x],f[y]);			
		}
	}
}

int main()
{
//	freopen("input.txt","r",stdin);
	readdata();
	work();
	return 0;
}  

参考文献

刘汝佳.《算法竞赛入门经典(第2版)》 清华大学出版社

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值