数据结构基础:P2-线性结构----编程作业03:Reversing Linked List

本系列文章为浙江大学陈越、何钦铭数据结构学习笔记,前面的系列文章链接如下
数据结构基础:P1-基本概念
数据结构基础:P2.1-线性结构—>线性表
数据结构基础:P2.2-线性结构—>堆栈
数据结构基础:P2.3-线性结构—>队列
数据结构基础:P2.4-应用实例—>多项式加法运算
数据结构基础:P2.5-应用实例—>多项式乘法与加法运算-C实现
数据结构基础:P3.1-树(一)—>树与树的表示
数据结构基础:P3.2-树(一)—>二叉树及存储结构
数据结构基础:P3.3-树(一)—>二叉树的遍历
数据结构基础:P3.4-树(一)—>小白专场:树的同构-C语言实现
数据结构基础:P4.1-树(二)—>二叉搜索树
数据结构基础:P4.2-树(二)—>二叉平衡树
数据结构基础:P4.3-树(二)—>小白专场:是否同一棵二叉搜索树-C实现


一、题目描述

题目:给定一个常数K和一个单链表L,把L上所有K个元素的链接都颠倒过来。例如,给定L为1→2→3→4→5→6,如果K=3,那么输出3→2→1→6→5→4;如果K=4,输出4→3→2→1→5→6。
输入格式: 每个输入文件包含一个测试用例。对于每种情况,第一行包含第一个结点的地址,结点的总数为 N N N( N ⩽ 1 0 5 N\leqslant10^{5} N105),要反转的子列表的长度为 K K K( K ⩽ N K\leqslant N KN)。结点的地址为5位非负整数,NULL用-1表示。接着是N行,每一行描述一个结点的格式为:Address Data Next,其中Address是结点的位置,Data是一个整数,Next是下一个节点的位置。
输出格式: 对于每种情况,输出得到的有序链表。每个结点占用一行,并用与输入相同的格式打印。
示例输入:
00100 6 4
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
示例输出:
00000 4 33218
33218 3 12309
12309 2 00100
00100 1 99999
99999 5 68237
68237 6 -1

1.1 什么是抽象的链表

比较有意思的一个问题是:有同学在论坛上面发言时候说,链表这个东西是不是只对C和C++语言的程序才有意义,我用的是JAVA, JAVA里头没有指针,没有指针哪来的链表呢?所以在这里我们很有必要重申一下,到底什么是链表:

我们说链表是一个抽象的数据结构,链表中间的结点需要两个域来存,一块是有地方来存它的基础数据,另外一个很重要的特征是需要有一块地方来存指针,这个指针指向下一个结点。C和C++只是提供了一种特殊的机制可以实现这个指针的功能,一个抽象的指针指的就是地址。任何一种形式去存了下一个结点的位置,这个东西就叫做指针。

我们来看一下我们这道题目的样例输入。第一行是给了这个单链表头结点的位置、链表结点数量、需要反转的结点数量。接着下面几行分别给出了每个结点的位置、在链表中的位次、每个结点下一个结点的位置。那我们在编程实现的时候到底怎么来存这样一个链表呢?

一个非常直截了当的方法就是整个模拟一个真实的链表在内存里面存在的状态。也就是说,我开一个充分大的结构数组来代表内存空间,这种情况下,什么是一个结点所处的位置?结点的位置就是它在这个数组里面的下标,而元素的下标是可以用一个整数来表示的。在这里我们说到一个指针的时候,实际上我们说的是一个整数,这个整数记录的是下一个结点在这个数组里面的下标而已。因此,链表对应的结构如下:
在这里插入图片描述


1.2 链表逆转算法

接下来我们来看怎么样实现单链表的逆转。在这个单链表的前面我个人习惯加一个头结点,虽然说在一定程度上是浪费了空间,但是如果这个头结点的空间对你不是那么紧要的话,那么加一个头结点会使后面的很多操作变得比较简单。这是我们原始的链表的形态。
在这里插入图片描述
我们希望完成了逆序以后,整个链表应该是长得这个样子的:前面四个结点的方向是从4指到3指到2指到1的,然后这个头结点要指在4这个位置,而1后面接的是剩余部分的这个链表。
在这里插入图片描述


那么怎么去实现它呢?

①我们需要两个指针,第一个指针我把它命名叫做new,指的是新的那个已经逆转好的链表它的头结点的位置。它的初始的位置显然可以指向第一个结点,我们认为第一个结点是已经逆转好了的。然后另外一个指针叫做old,它指向的是当前还没有完成逆转的那个老链表的头结点,我们让其初始位置指向new的后面结点。
②我们来看看反转链表的具体操作:
那我们要做第一件事情就是要把这个2的指针逆过来指向1,但是我在做这个操作之前我得先把3的这个位置给记住。所以我需要另外一个tmp指针去指向3,把这个位置记住。否则的话,我把2的这个指针一转向之后,后面这段链表就被丢掉了。
在这里插入图片描述
记住了3的位置之后我就可以把2的指针反向, 让它指向1
在这里插入图片描述
完成了这一步以后我们就可以把这三个指针向后位移。
在这里插入图片描述
然后把old的这个指针转过去指向这个新的头结点
在这里插入图片描述
然后继续向后移动,继续执行前面两步的操作。
在这里插入图片描述
这样一直重复,直到达到K个结点,我们这里K4
在这里插入图片描述
停下来以后我们发现:1next指针现在还指向2,这是不对的,1应该指向5。我怎么知道5是什么呢,5就是当前还没有逆转的这个老的链表的头结点。然后我这个空的头结点它应该指向当前这个已经逆转好的链表的头结点,也就是指向这个new所指的位置。
在这里插入图片描述

对应伪代码如下:

Ptr Reverse( Ptr head, int K )
{ 
	cnt = 1;
	new = head->next;
	old = new->next;
	while ( cnt < K ) {
		tmp = old->next;
		old->next = new;
		new = old; old = tmp;
		cnt++;
	}
	head->next->next = old;
	return new;
}

1.3 测试数据

我们在设计一道题目的测试数据的时候,至少有两个方面是一定要考虑到的。

最一般的情形
①也就是像我们的样例,给了你一个很平常的链表,然后有一部分是需要反转的,尾巴是不需要反转的。
边界测试
②在题目里面给定的是地址是一个5位的整数,那么我的地址就一定要取到上下界,就是5个0和5个9的情况。
③元素的个数正好是 k 的整数倍,所以你整个的链表每一段都正好需要全反转的。
④K 就等于 N,整个的大链表做一个完整的反转
⑤最小的 k 就是1,就意味着哪个结点都不需要反转。
⑥最大(最后剩K-1不反转)、最小N
⑦有多余结点。加了一些多余的结点让不合适的算法挂掉。

二、代码实现

整体代码如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>          
#include <stdlib.h>    
#define MAXSIZE 100001

//节点:包含数据、下一个节点位置
struct node {
	int data;
	int next;
};

//反转K个节点函数(我这里没有像陈越老师一样新建个空的头节点)
int Reverse(struct node *list, int head, int K)
{
	int cnt = 1;
	int new = head;
	int old = list[new].next;
	while (cnt < K) {
		int tmp = list[old].next;
		list[old].next = new;
		new = old; old = tmp;
		cnt++;
	}
	list[head].next = old;
	return new;
}

//打印链表
void PrintList(struct node* list, int head)
{
	while (list[head].next != -1) {
		printf("%05d %d %05d\n", head, list[head].data, list[head].next);
		head = list[head].next;
	}
	printf("%05d %d -1", head, list[head].data); //尾节点
}

int main()
{
	//存放各个节点的一个大数组
	struct node list[MAXSIZE];

	//第一行输入:链表的头节点位置、节点数量、反转节点数量
	int HeadPosition;
	int N;
	int K;
	scanf("%d %d %d", &HeadPosition, &N, &K);

	//数据读入:读取每个节点
	int address, data, next;
	for (int i = 0; i < N; i++) {
		scanf("%d %d %d", &address, &data, &next);
		list[address].data = data;
		list[address].next = next;
	}

	//进行反转操作
	int res = Reverse(list, HeadPosition, K);

	//打印反转后的链表
	PrintList(list, res);
	return 0;
}

运行,输入测试案例,结果正确。
在这里插入图片描述

题目描述 给定一个常数 $K$ 以及一个单链表 $L$,请编写程序将 $L$ 中每 $K$ 个结点反转。例如:给定 $L$ 为 1→2→3→4→5→6,$K$ 为 3,则输出应该为 3→2→1→6→5→4;如果 $K$ 为 4,则输出应该为 4→3→2→1→5→6,即最后不到 $K$ 个元素不反转。 输入格式 每个输入包含一个测试用例。每个测试用例第 1 行给出第 1 个结点的地址、结点总个数正整数 $N (\le 10^5)$、以及正整数 $K (\le N)$,即要求反转的子链结点的个数。结点的地址是 5 位非负整数,NULL 地址用 −1 表示。 接下来有 $N$ 行,每行格式为: Address Data Next 其中 Address 是结点地址;Data 是该结点保存的整数数据;Next 是下一结点的地址。题目保证给出的链表不为空。 输出格式 对每个测试用例,顺序输出反转后的链表,其上每个结点占一行,格式与输入相同。 输入样例 00100 6 4 00000 4 99999 00100 1 12309 68237 6 -1 33218 3 00000 99999 5 68237 23333 2 33218 输出样例 00000 4 33218 33218 3 12309 12309 1 99999 99999 5 68237 68237 6 23333 23333 2 -1 题目分析 本题需要将链表中每 $K$ 个结点反转,可以采用迭代的方法,每次找到 $K$ 个结点,将这 $K$ 个结点反转,然后将这 $K$ 个结点的前驱结点指向反转后的第一个结点,将反转后的 $K$ 个结点的最后一个结点指向下一个要反转的结点,然后继续进行下一轮反转。 需要注意的是,如果链表长度不足 $K$,则不进行反转。 代码实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知初与修一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值