PTA Reversing Linked List 思路分析及代码解析 v0.93

一、前导

1. 需要掌握的知识

链表

2. 题目信息

  1. 题目来源:PTA / 拼题A
  2. 题目地址: Reversing Linked List

二、解题思路分析

1. 题意理解

  1. 输入数据
00100 6 4	//链表头地址 节点总数 每次反转的结点数
00000 4 99999  //当前结点地址 结点值 下一个结点地址
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
  1. 输出数据:打印反转后的链表
00000 4 33218
33218 3 12309
12309 2 00100
00100 1 99999
99999 5 68237
68237 6 -1
  1. 题意
    (1)本题再次强调了,指针本质就是数据的存储地址
    (2)根据输入数据创建一个链表,然后按要求反转,最后打印新链表

2. 思路分析

2.0 本题的解题思路和陈越老师讲的一致,使用模拟链表的方式求解
2.1 根据题意,可以用数组下标 模拟 内存地址 ,由于要存储下一个结点的地址,因此需要使用结构体数组
2.2 反转函数是AC的关键,如果没想清楚,建议先画图模拟一下
2.3 注意:输入数据中可能存在无效结点,例如下面的 00000 3 99999
00100 3 2
00000 3 99999
00100 1 12309
12309 2 -1

三、具体实现

1. 弯路和bug

对于反转操作,建议动手在纸上画一画,画出来后还是比较清晰的

2. 代码框架

2.1 采用的数据结构

结构体数组

#define MAX 1000000
struct Node
{
	ElementType  Data;
	Address  Next;
}a[MAX];

2.2 程序主体框架

               程序伪码描述
int main()
{	
	1.根据录入数据创建一个链表
	2.执行反转
	3.打印输出
}

2.3 各分支函数

2.3.1 Reverse( ) 核心函数,整个流程用文字还是挺难描述的,还是上图啦(以6结点的链表,每次反转3个结点为例),大体原则就是:
(1)将结点分为Now,Next,Bak三类,Now表示已反转的结点,Next表示Now之后的结点,Bak表示Next之后的结点(用于保存位置,因为一旦反转,Next之后的结点就会失联)
(2)第一次反转比较特殊,需要记录链表的头地址
(3)如果想不清楚,一定要动手画图
在这里插入图片描述

typedef int Address;

Address Reverse(Address Position, int ReversedNumber)
{
	Address Now, Next, Bak,PtrHead; //Bak=Next's Next

	if (Flag) //Flag用于标记是否属于首次翻转,初始值为True
		Now = Position;
	else
	{
		Now = a[Position].Next;
		PtrHead = a[Position].Next;
	}	
	Next = a[Now].Next;
	Bak = a[Next].Next;

	while (--ReversedNumber) //执行翻转
	{
		a[Next].Next = Now;

		Now = Next;
		Next = Bak;
		Bak = a[Next].Next;
	}

	if (Flag) //首次翻转
	{
		a[Position].Next = Next;
		First = Now;
		Flag = false;

		return Position;
	}
	else  //非首次
	{
		a[PtrHead].Next = Next; //本段链表首地址元素改变指向  //链表绕来绕去好烦啊 
		a[Position].Next = Now;

		return PtrHead;
	}
}

3. 完整编码

大家如有疑问或建议,欢迎留言 😃

#include <iostream>
using namespace std;

#define MAX 1000000
#define END -1

typedef int ElementType;
typedef int Address;

struct Node
{
	ElementType  Data;
	Address  Next;
}a[MAX];

Address Reverse(Address Position, int ReversedNumber);
void Print(int n);

int First, Number, ReversedNumber;  //链表首地址、待输入的记录条数、翻转数
bool Flag = true; //记录是否首次翻转

int main()
{
	
	cin >> First >> Number >> ReversedNumber; 
	Address Current,Next; ElementType Data; 
	Address Position;  int Count=0;//Count 意味着 链表中的有效节点数 

	for (int i = 0; i < Number; i++) //存储输入数据
	{
		cin >> Current >> Data >> Next;
		a[Current].Data = Data;
		a[Current].Next = Next;
	}

	Position = First; 
	while (Position != END) //Number中可能存在无效结点,需要甄别出来,这是一个测试点
	{
		Count++;
		Position = a[Position].Next;
	}

	int Mark = Count/ReversedNumber; //Mark是翻转链表的次数
	
	Position = First;
	while (Mark--) //翻转
		Position = Reverse(Position, ReversedNumber);

	Position = First;
	while(Position !=END)//打印输出数据
	{
		Print(Position);
		cout << " " << a[Position].Data << " ";
		Print(a[Position].Next);
		cout<< endl;
		Position = a[Position].Next;
	}

	return 0;
}

Address Reverse(Address Position, int ReversedNumber)
{
	Address Now, Next, Bak,PtrHead; //Bak=Next's Next

	if (Flag)
		Now = Position;
	else
	{
		Now = a[Position].Next;
		PtrHead = a[Position].Next;
	}	
	Next = a[Now].Next;
	Bak = a[Next].Next;

	while (--ReversedNumber) //执行翻转
	{
		a[Next].Next = Now;

		Now = Next;
		Next = Bak;
		Bak = a[Next].Next;
	}

	if (Flag) //首次翻转
	{
		a[Position].Next = Next;
		First = Now;
		Flag = false;

		return Position;
	}
	else  //非首次
	{
		a[PtrHead].Next = Next; //本段链表首地址元素改变指向  //链表绕来绕去好烦啊 
		a[Position].Next = Now;

		return PtrHead;
	}
}

void Print(int n)
{
	int Scale = 10000;

	if (n > 1)
	{
		while (n < Scale)
		{
			cout << "0";
			Scale /= 10;
		}
		cout << n;
	}
	else if (n == 1)
		cout << "00001";
	else if (n == 0)
		cout << "00000";
	else if (n == END)
		cout << "-1";

	return;
}

210928 AC代码:如果使用链表的方法求解,需要通过画图理清楚翻转的步骤,首次翻转 和 非首次翻转代码存在差异(首次翻转需要记录头地址)
进步点:打印时使用了printf() 省时省力;进行了代码规划
待改进:耗费时间太长,大概用了90分钟;Reverse()函数代码准确性低、进行了较长时间的Debug;忘记计算有效结点

#include <iostream>
using namespace std;

#define MAX 100000

typedef int address;
struct Node
{
	int Value;
	address Next;
}a[MAX];
int Head, N, ReverseNum;
bool Flag = true;

address Reverse(int ReverseNum, int Start);

int main()
{
	cin >> Head >> N >> ReverseNum;

	address Addr,Next; int Value;
	for (int i = 0; i < N; i++)
	{
		cin >> Addr >> Value >> Next;
		a[Addr].Value = Value; a[Addr].Next = Next;
	}

	int Address = Head,ValidN=0 ;
	while (Address != -1)
	{
		ValidN++;
		Address = a[Address].Next;
	}

	int Many = ValidN / ReverseNum, Start = Head;
	while (Many--)
		Start = Reverse(ReverseNum, Start);

	Address = Head;
	while (a[Address].Next != -1)
	{
		printf("%05d %d %05d\n", Address, a[Address].Value, a[Address].Next);
		Address = a[Address].Next;
	}
	printf("%05d %d -1", Address, a[Address].Value);

	return 0;
}

address Reverse(int ReverseNum,int Start)
{
	int R = ReverseNum - 1; 
	int Now, Next, Bak;

	if (Flag)
		Now = Start;
	else
		Now = a[Start].Next;

	Next = a[Now].Next;
	Bak = a[Next].Next;

	while (R--)
	{
		a[Next].Next = Now;
		
		Now = Next;
		Next = Bak;
		Bak = a[Next].Next;
	}

	if (Flag)
	{	
		a[Start].Next = Next;
		Head = Now;
		Flag = false;
	}
	else
	{
		int tmp = a[Start].Next;
		a[tmp].Next = Next;
		a[Start].Next = Now;
		Start = tmp;
	}
	
	return Start;
}

四、参考资料

浙江大学 陈越、何钦铭老师主讲的数据结构

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值