PAT 乙级 1075 链表元素分类
1. 题目简述及在线测试位置
1.1 给定一个链表 和 正数k,例如:3 2 -1 -2 0 1 和 k=1 。按如下规则对链表进行调整:规则1,负数需要排到最前面;规则2,大于k的数字需要排在[0,k] 数字的后面 ;规则3:每一类数字(指负数、大于k的数) 的原有顺序不改变,例如 之前是 -1 -2,调整后还是 -1 -2
1.2 在线测试位置: 1075 链表元素分类
2. 基本思路
2.0 本文采用了链表思路,实现起来真是一言难尽:具体实现不好想、 实现难度高、代码容易出错,debug了很久。最终在自己的努力下AC了,还是为自己点个赞。后面抽时间借鉴下别人的实现方式
2.1 数据结构采用结构体数组:数组下标对应链表结点的地址、数组元素包括:当前结点值、下一个结点的地址
struct LinkTable
{
int Data;
int Next;
}a[MAX];
2.2 首先按规则 负数需要排到最前面+负数的原有顺序不变 进行链表相关结点的调整。链表表头可能是负数,也可能非负数,因此我们需要先找到第一个非负数、即插入点,记录插入点、插入点的前一个结点,为后面的结点调整做准备。举个栗子:链表 -2 1 -3 4 ,插入位置应该是 -2 和 1之间,1是插入点,-2是插入点的前一个结点
另外需要注意,插入位置可能是头结点,需要单独处理。举个栗子:链表 1 -2 3,插入位置就是 1 的前面,插入-2后,头结点发生变化、插入位置也更新为 -2 和 1之间
int InsertPreNode = -1, InsertNode = HeadNode; //InsertPreNode = -1 表示插入结点是表头
NowNode = HeadNode;
//根据第一规则,定位插入点(链表中的第一个正数或0) //可能都是负数 需要添加循环退出条件
while (a[NowNode].Data < 0 && NowNode != -1)
{
InsertPreNode = NowNode;
InsertNode = a[NowNode].Next;
PreNode = NowNode;
NowNode = a[NowNode].Next;
}
定位到插入结点后,按规则执行插入操作:表头(代码中是InsertPreNode=-1) 、非表头两种情况分开处理,将负数按原有顺序 都调整到链表的前面
//根据第一规则调整链表
//NowNode = HeadNode; //定位插入点操作后,不需要对NowNode重新赋值,因为插入点前的元素不需要执行插入操作;如果赋值,则是画蛇添足,链表头也会被弄丢
while (NowNode != -1)
{
if (a[NowNode].Data >= 0)//非负数不调整
{
PreNode = NowNode;
NowNode = a[NowNode].Next;
}
else //负数
{
a[PreNode].Next = a[NowNode].Next; //剥离当前结点
if (InsertPreNode != -1) //插入位置非头结点
{
a[InsertPreNode].Next = NowNode;
a[NowNode].Next = InsertNode;
InsertPreNode = NowNode;
}
else //插入位置在头结点
{
a[NowNode].Next = InsertNode;
HeadNode = NowNode;
InsertPreNode = NowNode;
}
NowNode = a[PreNode].Next;
}
}
2.3 根据次规则进行调整 大于k的元素排在[0,k]的后面 + 大于k的元素原有位置不变,这个规则的难点是找到插入点。我的思路是 根据已有链表构造逆链表,顺序遍历逆链表,找到的第一个属于 [0,k]的数就是插入点 (原有的单向链表无法确认这个点,所以无奈之下构造逆链表)
例如:k=2, 3 4 2 0 1 7 9 插入位置就是 1 7之间
//通过逆链表b 找到符合第二规则的插入点
NowNode = bHeadNode;
PreNode = -1;
while (NowNode != -1)
{
if (b[NowNode].Data <= Value) //第二规则的插入点
{
InsertPreNode = NowNode;
InsertNode = PreNode;
break;
}
else
{
PreNode = NowNode;
NowNode = b[NowNode].Next;
}
}
根据找到的插入点,执行插入操作即可。
注意,链表表尾插入 、非表尾插入分开处理。还需注意,如果退出第二规则插入点的查找循环时,若NowNode值为 -1,说明没有符合条件的插入点,也就是意味着不需要执行插入动作
if (NowNode != -1) //测试点:找到了符合第二规则的插入点
{
//按第二规则调整
int Mark = InsertPreNode;
NowNode = HeadNode; //原始链表的表头
PreNode = -1; //与NowNode对应,代表其前的结点
while (NowNode != Mark) //Mark及之后的结点不需要更改顺序
{
if (a[NowNode].Data <= Value)
{
PreNode = NowNode;
NowNode = a[NowNode].Next;
}
else //需要调整的点 大于Value
{
if (PreNode == -1) //头结点需要改变位置
{
HeadNode = a[NowNode].Next; //剥离,头结点重新赋值
a[InsertPreNode].Next = NowNode;
a[NowNode].Next = InsertNode;
InsertPreNode = a[InsertPreNode].Next; //遗漏了 导致bug2 节点丢失
NowNode = HeadNode;
}
else //非头结点需要改变位置
{
a[PreNode].Next = a[NowNode].Next; //剥离
a[InsertPreNode].Next = NowNode;
a[NowNode].Next = InsertNode;
//InsertNode = a[InsertPreNode].Next; //error
InsertPreNode = a[InsertPreNode].Next;
//6 11 插入到5 8中 5 6 8 ,再插入11是 5 6 11 8,改变的是InsertPreNode
NowNode = a[PreNode].Next;
}
}
}
}
//else if(NowNode == -1) //未找到符合第二规则的插入点 因此不动作
2.4 打完收工:思路可取,但是实现起来太容易出错、而且实现起来不容易,因此本解题方法不推荐 o(╯□╰)o
3. 完整AC代码
#include <iostream>
using namespace std;
#define MAX 100001
struct LinkTable
{
int Data;
int Next;
}a[MAX];
int main()
{
int HeadNode, NodeNumber, Value;
cin >> HeadNode >> NodeNumber >> Value;
int Address, Data, Next;
for (int i = 0; i < NodeNumber; i++)
{
cin >> Address >> Data >> Next;
a[Address].Data = Data;
a[Address].Next = Next;
}
//当前节点、当前节点的前一个节点
int NowNode, PreNode=-1;
//举栗子:在 4 6中插入5,6是InsertNode,4是InsertPreNode
int InsertPreNode = -1, InsertNode = HeadNode; //InsertPreNode = -1 表示插入结点是表头
NowNode = HeadNode;
//根据第一规则,定位插入点(链表中的第一个正数或0) //可能都是负数 需要添加循环退出条件
while (a[NowNode].Data < 0 && NowNode != -1)
{
InsertPreNode = NowNode;
InsertNode = a[NowNode].Next;
PreNode = NowNode;
NowNode = a[NowNode].Next;
}
//根据第一规则调整链表
//NowNode = HeadNode; //bug 这条语句造成此类链表死循环:-10 0 -18
while (NowNode != -1)
{
if (a[NowNode].Data >= 0)//非负数不调整
{
PreNode = NowNode;
NowNode = a[NowNode].Next;
}
else //负数
{
a[PreNode].Next = a[NowNode].Next; //剥离当前结点
if (InsertPreNode != -1) //插入位置非头结点
{
a[InsertPreNode].Next = NowNode;
a[NowNode].Next = InsertNode;
InsertPreNode = NowNode;
}
else //插入位置在头结点
{
a[NowNode].Next = InsertNode;
HeadNode = NowNode;
InsertPreNode = NowNode;
}
NowNode = a[PreNode].Next;
}
}
//通过构建逆链表b
struct LinkTable b[MAX];
b[HeadNode].Data = a[HeadNode].Data;
b[HeadNode].Next = -1;
NowNode = HeadNode;
PreNode = NowNode;
NowNode = a[NowNode].Next;
while (NowNode != -1)
{
b[NowNode].Data = a[NowNode].Data;
b[NowNode].Next = PreNode;
PreNode = NowNode;
NowNode = a[NowNode].Next;
}
int bHeadNode = PreNode;
//通过逆链表b 找到符合第二规则的插入点
NowNode = bHeadNode;
PreNode = -1;
while (NowNode != -1)
{
if (b[NowNode].Data <= Value) //第二规则的插入点
{
InsertPreNode = NowNode;
InsertNode = PreNode;
break;
}
else
{
PreNode = NowNode;
NowNode = b[NowNode].Next;
}
}
if (NowNode != -1) //测试点:找到了符合第二规则的插入点
{
//按第二规则调整
int Mark = InsertPreNode;
NowNode = HeadNode;
PreNode = -1;
while (NowNode != Mark)
{
if (a[NowNode].Data <= Value)
{
PreNode = NowNode;
NowNode = a[NowNode].Next;
}
else //需要调整的点 大于Value
{
if (PreNode == -1) //头结点需要按第二规则调整
{
HeadNode = a[NowNode].Next;
a[InsertPreNode].Next = NowNode;
a[NowNode].Next = InsertNode;
InsertPreNode = a[InsertPreNode].Next; //遗漏了 导致bug2 节点丢失
NowNode = HeadNode;
}
else //非头结点需要按第二规则调整
{
a[PreNode].Next = a[NowNode].Next;
a[InsertPreNode].Next = NowNode;
a[NowNode].Next = InsertNode;
//InsertNode = a[InsertPreNode].Next; //error
InsertPreNode = a[InsertPreNode].Next;
//6 插入到5 8中 5 6 8 ,再插入11是 5 6 11 8,改变的是InsertPreNode
NowNode = a[PreNode].Next;
}
}
}
}
//else if(NowNode == -1) //未找到符合第二规则的插入点 因此不动作
NowNode = HeadNode;
while (NowNode != -1)
{
printf("%05d %d ", NowNode,a[NowNode].Data);
if (a[NowNode].Next == -1)
printf("-1\n");
else
printf("%05d\n", a[NowNode].Next);
NowNode = a[NowNode].Next;
}
return 0;
}