Luogu——P1160 队列安排 线性表——链表

本文通过解决Luogu数据结构题,讲解如何使用队列模拟来解决学校同学排队问题,涉及插入与删除操作,使用自定义链表结构实现。最终分享了完整代码和学习心得。

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、题目

题目描述

输入格式

输出格式

说明/提示

二、思路分析

1.“队列”emmm,要不试试队列模拟?

2.emmm插入/删除?这些个我链表兄弟熟啊!

①思路框架

②这里我用结构体模拟链表哦~

③add()函数封装

④remove()函数封装 

三、完整代码 

总结


前言

链表是运用非常广泛的数据结构,除了查询可能要花点功夫外,增删都以O(n)的复杂度完成,学会用链表,是基础。本篇将以Luogu题单中(【数据结构1-1】线性表 )的一题为切入,总结模拟链表的心得。

学习永不停止,本博客将随着对链表的学习深入,不断更新。


一、题目

题目描述

一个学校里老师要将班上 NN 个同学排成一列,同学被编号为 1\sim N1∼N,他采取如下的方法:

  1. 先将 11 号同学安排进队列,这时队列中只有他一个人;

  2. 2-N2−N 号同学依次入列,编号为 ii 的同学入列方式为:老师指定编号为 ii 的同学站在编号为 1\sim(i-1)1∼(i−1) 中某位同学(即之前已经入列的同学)的左边或右边;

  3. 从队列中去掉 M(M<N)M(M<N) 个同学,其他同学位置顺序不变。

在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。

输入格式

第 11 行为一个正整数 NN,表示了有 NN 个同学。

第 2\sim N2∼N行,第 ii 行包含两个整数 k,pk,p,其中 kk 为小于 ii 的正整数,pp 为 00 或者 11。若 pp 为00,则表示将 ii 号同学插入到 kk 号同学的左边,pp 为 11 则表示插入到右边。

第 N+1N+1 行为一个正整数 MM,表示去掉的同学数目。

接下来 MM 行,每行一个正整数 xx,表示将 xx 号同学从队列中移去,如果 xx 号同学已经不在队列中则忽略这一条指令。

输出格式

11 行,包含最多 NN 个空格隔开的正整数,表示了队列从左到右所有同学的编号,行末换行且无空格。

说明/提示

样例解释:

将同学 22 插入至同学 11 左边,此时队列为:

2 1

将同学 33 插入至同学 22 右边,此时队列为:

2 3 1

将同学 44 插入至同学 11 左边,此时队列为:

2 3 4 1

将同学 33 从队列中移出,此时队列为:

2 4 1

同学 33 已经不在队列中,忽略最后一条指令

最终队列:

2 4 1

数据范围

对于 20\%20% 的数据,有 1\leq N\leq 101≤N≤10;

对于 40\%40% 的数据,有 1\leq N\leq 10001≤N≤1000;

对于 100\%100% 的数据,有 1\leq N,M\leq1000001≤N,M≤100000。

//输入样例1
4
1 0
2 1
1 0
2
3
3
//输出样例1
2 4 1

二、思路分析

1.“队列”emmm,要不试试队列模拟?

用队列模拟队列,考虑起来还是挺容易的。为了完成队列出入过程中对数据的删改,于是声明两个队列,模拟过程就在“一个出一个进”的操作中完成。

#include<iostream>
#include<queue>

using namespace std;

int n;
int k,p;
queue<int> q1,q2;

int main()
{
	cin>>n;
	q1.push(1);
	for(int i=2;i<=n;++i)
	{
		cin>>k>>p;
		if(!q1.empty() && q2.empty())
		{
			if(p==0)
			{
				while(!q1.empty())
				{
					if(q1.front()==k)
						q2.push(i);
					q2.push(q1.front());
					q1.pop();
				}
			}
			else
			{
				while(!q1.empty())
				{
					if(q1.front()==k)
					{
						q2.push(q1.front());
						q2.push(i);
						
					}
					else
					{
						q2.push(q1.front());
					}
					q1.pop();
				}				
			}
		}
		else
		{
			if(p==0)
			{
				while(!q2.empty())
				{
					if(q2.front()==k)
						q1.push(i);
					q1.push(q2.front());
					q2.pop();
				}
			}
			else
			{
				while(!q2.empty())
				{
					if(q2.front()==k)
					{
						q1.push(q2.front());
						q1.push(i);
					}
					else
					{
						q1.push(q2.front());
					}
					q2.pop();
				}				
			}
		}
		
	}
	int m;
	cin>>m;
	for(int i=1;i<=m;++i)
	{
		cin>>k;
		if(!q1.empty() && q2.empty())
		{
			while(!q1.empty())
			{
				if(q1.front()!=k)
				{
					q2.push(q1.front());
				}
				q1.pop();
			}
		}
		if(!q2.empty() && q1.empty())
		{
			while(!q2.empty())
			{
				if(q2.front()!=k)
				{
					q1.push(q2.front());
				}
				q2.pop();
			}
		}	
	}
	while(!q1.empty())
	{
		cout<<q1.front()<<" ";
		q1.pop();
	}
	while(!q2.empty())
	{
		cout<<q2.front()<<" ";
		q2.pop();
	}
	return 0;
}

喜提40分 ~~~~~TLE~~~~~ 

2.emmm插入/删除?这些个我链表兄弟熟啊!

①思路框架

总体就俩:插入/删除,直接上自定义函数封装这两个过程

输出:只要维护了队列排头,那么吸溜~~一长串都能遍历

小细节:

不在队列里的,emmm前一个后一个就设为INF吧~~     stu[i].last=INF;   stu[i].next=INF;

在队列里,队头前一个还是队头:stu[i].last=i;

在队列里,队尾后一个还是队尾:stu[i].last=i;

②这里我用结构体模拟链表哦~

struct tp{
	int last,next;
};
tp stu[100005]; //记录第i个同学前、后是谁,否则为INF 

③add()函数封装

void add(int i,int k,bool p)
{
	if(p)//插到右边 
	{
		if(stu[k].next!=k)//如果非队尾
		{
			stu[i].next=stu[k].next;			
			stu[stu[k].next].last=i;
			stu[k].next=i;
			stu[i].last=k;
		}
		else
		{
			stu[k].next=i;
			stu[i].last=k;
			stu[i].next=i;
		}
	}
	else
	{
		if(stu[k].last!=k)如果非队头
		{
			stu[i].last=stu[k].last;
			stu[i].next=k;
			stu[stu[k].last].next=i;
			stu[k].last=i;
		}
		else
		{
			stu[k].last=i;
			stu[i].next=k;
			stu[i].last=i;
			head=i;
		}
	} 	 
}

④remove()函数封装 

void remove(int k)
{
	if(stu[k].last==INF && stu[k].next==INF)return;//不在队列里的话怎么能删除呢?
	
    if(stu[k].next==k)//队尾
	{
		stu[stu[k].last].next=stu[k].last;
		stu[k].last=INF;
		stu[k].next=INF;
	}
	else if(stu[k].last==k)//队头
	{
		stu[stu[k].next].last=stu[k].next;
		head=stu[k].last;  //别忘了维护队列第一个同学是谁哦~~
		stu[k].next=INF;
		stu[k].last=INF;
	}
	else//普通队列内
	{
		stu[stu[k].last].next=stu[k].next;
		stu[stu[k].next].last=stu[k].last;
		stu[k].next=INF;
		stu[k].last=INF;
	}
}

三、完整代码 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<stack>

#define INF 0x3f3f3f3f
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)

using namespace std;

struct tp{
	int last,next;
};
tp stu[100005]; //记录第i个同学前、后是谁,否则为INF 

void add(int,int,bool);
void remove(int);

int n,m;
int head;

int main()
{
	IOS;
	cin>>n;
	int k;
	bool p;
	for(int i=1;i<=n;++i)
	{
		stu[i].last=INF;
		stu[i].next=INF;
	}//一开始无人在队列中 
	/************下面是入列部分************/ 
	stu[1].last=1;
	stu[1].next=1;
	head=1;
	
	for(int i=2;i<=n;++i)
	{
		cin>>k>>p;
		add(i,k,p);//将i插入到k的p一侧 
	}
	/*************下面是删除部分***********/
	cin>>m;
	for(int i=1;i<=m;++i) 
	{
		cin>>k;
		remove(k);
	}
	
	/*************下面是输出部分***********/ 
	while(true)
	{
		cout<<head<<" ";
		if(stu[head].next==head)break;
		head=stu[head].next; 
	}
	return 0;
} 

void add(int i,int k,bool p)
{
	if(p)//插到右边 
	{
		if(stu[k].next!=k)
		{
			stu[i].next=stu[k].next;			
			stu[stu[k].next].last=i;
			stu[k].next=i;
			stu[i].last=k;
		}
		else
		{
			stu[k].next=i;
			stu[i].last=k;
			stu[i].next=i;
		}
	}
	else
	{
		if(stu[k].last!=k)
		{
			stu[i].last=stu[k].last;
			stu[i].next=k;
			stu[stu[k].last].next=i;
			stu[k].last=i;
		}
		else
		{
			stu[k].last=i;
			stu[i].next=k;
			stu[i].last=i;
			head=i;
		}
	} 	 
}

void remove(int k)
{
	if(stu[k].last==INF && stu[k].next==INF)return;
	if(stu[k].next==k)
	{
		stu[stu[k].last].next=stu[k].last;
		stu[k].last=INF;
		stu[k].next=INF;
	}
	else if(stu[k].last==k)
	{
		stu[stu[k].next].last=stu[k].next;
		head=stu[k].last;
		stu[k].next=INF;
		stu[k].last=INF;
	}
	else
	{
		stu[stu[k].last].next=stu[k].next;
		stu[stu[k].next].last=stu[k].last;
		stu[k].next=INF;
		stu[k].last=INF;
	}
}




总结

最近开始刷数据结构——线性表的部分,加入小队以来开始有计划地学习,路阻且常(长)。本篇也是借机会把最基础的链表模拟给拿下。之后学习各种进阶链表,也会一直更新。

与诸君共勉!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值