军训日记——初识双向链表

题目描述

背景:

        小军的军训进行到了一半了,今天军训教官搞了一波突然袭击,进行查寝。

        提前了解到查寝消息的小军准备进行一波整理归纳,来使自己的寝室变得更加整洁。具体来说,小军有n件物品,放在n个盒子里,第i个盒子有物品i,小军会进行m次整理,第i次整理,小军会依次在第x个盒子顶拿走物品放入第y个盒子内,直至第x个盒子完全搬空。比如第1个盒子自顶向下有物品1、2,第2个盒子有物品3,将盒子1内的物品搬入盒子2内后结果是: 第1个盒子没有物品,第2个盒子自顶向下是2、1、3

        现在,小军告诉你n还有m次操作具体是什么,你能告诉他最后每个盒子内有几个物品,他们具体是什么么?

输入:

        一个正整数n代表盒子和物品数,一个正整数m代表整理归纳的次数

        接下来m行输入,一行两个正整数x y,代表用上述的方法将盒子x的物品搬到盒子y里

        1n≤10^5, 1≤m≤10^6, 1≤x,y≤n

        题目保证x != y

输出:

        有n行输出

        第i行,先输出一个正整数k,表示第i个盒子内的物品数,接下来输出n个数,表示第i个盒子自顶向下的物品标号

注意:

行末无空格,文末有回车。

 第一印象

        我第一眼看到题的时候,觉得这题很简单,只要开一个栈的数组,用栈去模拟即可,满心欢喜提交上去,结果有错误提示:bad_alloc,查了一下,是因为内存不够,开不出来有1e5个stack的数组。不过貌似即使能开出来,用栈去模拟,一个一个地pop和push还是会超时(毕竟m最大有1e6)。

再思考

        后来我想到,移动盒子的物品时,并不需要一个一个地移动,只需要将盒x中物品倒置在y盒中,直接接上就行了。像这样只在一组数据的两头进行连接操作,很明显是链表的的特征。同时考虑到链表的两头可能都需要连接物品,所以要采用双向链表去模拟每一个盒子中存放物品的情况。

        于是我先构造了一种结构类型node来实现双向链表,node里面有指向上一个和下个节点的指针pre和next,以及代表物品序号的整型数int。

所遇问题

  1. 在连接和输出时,如何找到一个盒子的链表的最上端node和最下端node?对于此,我开了两个指针数组high和low,high[i]和low[i]分别指向第i个盒子最上端node和最下端node。
  2. 连接操作如何实现?简单来说,就是把第x个盒子的最上端node和第y个盒子的最上端node连接起来,然后更新high[y], low[y], high[x], low[x]。对于x空或y空的情况还要进行特判,避免在使用指针的过程中造成无效内存引用(RE)。实际操作的过程还是很复杂的,需要考虑到各个方面,并注意用if else判断时代码的逻辑和我们所想的是否一致。
  3. 连接时需要考虑接的方向吗(用next还是pre指针去指向要接的节点)?这个我思考了很久,结果是:实际操作过程会很复杂,根本无法保证一个链表的方向。所以我们在连接时,只要接上了就行,不用考虑是用x盒最上端node的pre指针还是next指针去指向y盒的最上端node。
  4. 那么,输出一个盒子中的物品时该如何遍历链表呢?对于一个非空的盒子,我们将p指针初始化为high[i],显然我们不能再让p一直访问p的next结点或pre结点,那么,我们该如何指定p所访问的方向呢?显然每一个物品序号只会被输出一次,因此对于每一个node,我又在其中添加了一个bool变量isvisit,来标志该结点是否被访问过。通过此,我们就可以知道哪个节点被访问过,让p向未访问的方向移动即可。
  5. 此外,我又开了一个num数组,num[i]表示第i个盒子中物品数,在实行操作时不断更新就ok了。 

时间复杂度

        这样做的话,我们可以将时间复杂度降至O(max(m,n))级别,对于n<=1e5和m<=1e6,显然可行。

代码实现

        很多功能我都放在函数里

#include <iostream>
#define maxn 100010
struct node{
	node *pre;
	int id;
	node *next;
	bool isvisit;	
};
node *high[maxn]={NULL},*low[maxn]={NULL};
int num[maxn];
void list_cat(int x,int y);
void node_create(int i);
void box_visit(int i);

int main() 
{
	int m,n,x,y;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		node_create(i);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		list_cat(x,y);
	}
	for(int i=1;i<=n;i++)
	{
		box_visit(i);
	}
	return 0;
}

void list_cat(int x,int y)
{
	if(high[x]==NULL) ;
	else 
	{
		if(high[x]->pre==NULL)
		{
			high[x]->pre=high[y]; 	
		}
		else
		{
			high[x]->next=high[y];
		}
		if(high[y]!=NULL)
		{
			if(high[y]->pre==NULL) high[y]->pre=high[x];
			else high[y]->next=high[x];
		}
		else
		{
			low[y]=high[x];
		}
		high[y]=low[x];
		high[x]=NULL;
		low[x]=NULL; 
		num[y]+=num[x];
		num[x]=0;
	}
}
void node_create(int i)
{
	node *em=new node;//创建链表节点
	em->id=i;
	em->next=NULL;
	em->pre=NULL;
	high[i]=em;
	low[i]=em;
	num[i]=1;
}
void box_visit(int i)
{
	printf(num[i]==0?"%d\n":"%d ",num[i]);
	node *p=high[i];
	for(int j=1;j<=num[i];j++)
	{
		printf(j==num[i]?"%d\n":"%d ",p->id);
		p->isvisit=true;
		bool flag=false;
		if(p->next!=NULL && !(p->next)->isvisit) 
		{
			p=p->next;
			flag=true;//flag用于记录p在这里有没有向next访问
		}
		if(!flag && p->pre!=NULL && !(p->pre)->isvisit)
		{
			p=p->pre;
		}
	}	
}

其他方法

        这题我是手写的链表,主要原因是对list容器不熟TAT,不知道连接list时该如何操作,以及该如何存储每个盒子中的链表。在讨论区还看到大佬们说可以采用图来解决,我是小白,对图所知甚少,欢迎大佬们赐教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值