-
题目来源:
中国大学MOOC-陈越、何钦铭-数据结构 -
题目详情:
译文:
给定一个常数 K K K和一个单链表 L L L,你应反转 L L L上每个 K K K元素的链接。例如,假设L为 1 → 2 → 3 → 4 → 5 → 6 1→2→3→4→5→6 1→2→3→4→5→6,如果 K = 3 K = 3 K=3,则须输出 3 → 2 → 1 → 6 → 5 → 4 3→2→1→6→5→4 3→2→1→6→5→4; 如果 K = 4 K = 4 K=4,则须输出 4 → 3 → 2 → 1 → 5 → 6 4→3→2→1→5→6 4→3→2→1→5→6。
输入格式:
每个输入文件包含一个测试用例。 对于每种情况,第一行都包含第一节点的地址,正数
N
(
≤
1
0
5
)
N(≤10^5)
N(≤105)和正数
K
(
≤
N
)
K(≤N)
K(≤N),正数
N
(
≤
1
0
5
)
N(≤10^5)
N(≤105)是节点总数,正数
K
(
≤
N
)
K(≤N)
K(≤N)是要反转的子列表的长度。 节点的地址是
5
5
5位非负整数,
−
1
-1
−1表示
N
U
L
L
NULL
NULL。
然后有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
解题思路一:链表
本着尽量不使用库函数(使用库函数在刷题比赛的时候确实很使用,但实际上可读性较差,如果实际上一个程序需要多人维护,但是别人没用过你用的库函数的话就不方便维护,还得去学你用的库函数),基于C的去解决问题的原则来审题。
题目说的给出的是链表,因此最开始很自然的考虑使用链表解决问题。
流程大致如下:
- 捕获数据
- 根据地址构建有序链表
- 按照要求反转链表
- 输出反转的链表
思路应不难,但是如果实现这几个看似简单的步骤需要注意的地方很多,比如因为数据最大长度为 1 0 5 10^5 105,需要在构建有序链表的时候注意,否则会通不过最大数据的边界测试(别问我怎么知道的,差点因此放弃这个做法);再比如不是所有数据都是有用数据,会有一些数据不属于链表,因此在构建链表的时候也需要注意;再比如如何反转链表等等。
这里只介绍如何反转链表,其他的注意点程序中都有注释,直接看程序即可。
假设现有链表
1
→
2
→
3
1→2→3
1→2→3,头节点head
指向1所在节点,将节点1
指向节点2
的箭头反转为指向节点1
前一个节点(此处为空)的步骤为:
- 创建一个
节点cur
=head
,一个空节点pre
和一个空节点temp
- temp = cur->next
- cur->next = pre
- pre = cur
- cur = temp
按照这个几个步骤循环遍历即可将需要的箭头反转。
思路介绍到这,接下来是代码时间。
完整程序如下:
//做法一,链表实现
#include <stdio.h>
#include <stdlib.h>
#define MAX_ADD 100000 //5位地址的上界
typedef struct LNode{
int add; //节点地址
int val; //指数值
int next_add; //下一个节点地址
struct LNode* next; //下一个节点指针
}Node;
typedef Node *List;
void printList(List L);
List creatList(int TargetAdd, int N, int K);
List reverseList(List input, int length, int K);
void reverseKSubList(List head, int K);
int length = 0; //链表的有效长度
int main(){
int InputAdd, N, K;
List input = NULL, output = NULL;
scanf("%d %d %d", &InputAdd, &N, &K);
input = creatList(InputAdd, N, K);
output = reverseList(input, length, K);
printList(output);
return 0;
}
//创建有序链表
List creatList(int TargetAdd, int N, int K){
List res = (List)malloc(sizeof(Node));
res->next_add = TargetAdd;
res->next = NULL;
List res_temp = res;
//用于存储地址对应的数据值和下一节点的地址
int temp_add, data[MAX_ADD], next_add[MAX_ADD];
for(int i=0; i<N; i++){
scanf("%d ", &temp_add);
scanf("%d %d", &data[temp_add], &next_add[temp_add]);
}
//创建有序链表
while(1){
List temp = (List)malloc(sizeof(List));
if(res_temp->next_add == -1) //当下一个节点地址为-1时构建结束,其他为垃圾数据
break;
temp->add = res_temp->next_add;
temp->val = data[temp->add];
temp->next_add = next_add[temp->add];
res_temp->next = temp;
res_temp = res_temp->next;
length++;
}
res_temp->next = NULL;
return res;
}
//反转链表
List reverseList(List input, int length, int K){
if(K == 1)
return input;
int times = length / K; //需要反转的次数
List res = input;
for(int i=0; i<times; i++){
reverseKSubList(input, K); //反转以input为头节点的K个节点
for(int j=0; j<K; j++)
input = input->next; //将头节点后移K位
}
return res;
}
void reverseKSubList(List head, int K){
List cur = head->next;
List pre = NULL;
for(int i=0; i<K; i++){
List temp = cur->next;
cur->next = pre;
if(!pre)
cur->next_add = -1;
else
cur->next_add = pre->add;
pre = cur;
cur = temp;
}
head->next->next = cur;
if(cur) //判断是否有后续节点
head->next->next_add = cur->add;
else
head->next->next_add = -1;
head->next = pre; //将反转后的链表与剩余的链表连接起来
head->next_add = pre->add;
}
//打印链表
void printList(List L)
{
List p=L->next;
if(p)
{
List r;
r = L;
while(r->next)
{
r = r->next;
if(r->next_add == -1)
printf("%05d %d %d\n",r->add, r->val, r->next_add);
else
printf("%05d %d %05d\n",r->add, r->val, r->next_add);
}
}
else
{
printf("NULL");
}
}
结果如下:
可以发现因为该做法创建了两个很大的数组,因此当做最大N的边界测试时暂用的内存不是很客观,因此考虑其他做法。
解题思路二:数组
虽然题目给出的要求是反转链表,但链表说白了就是线性表,对于PTA这种黑盒测试,只要程序输出结果对了即可,因此被输出的对象也可以不是链表,因此考虑用其他的线性结构的数组来实现。具体做法如下:
- 捕获数据存储到数组中
- 根据地址序将数组中的数据交换位置排好序
- 根据要求反转数组
- 输出数据
这种思路对我而言确实不是那么容易想到,我第一反应还是做法一中的创建有序链表,但是一旦想到之后实现难度还是较做法一简单很多的,我觉得直接上程序就OK了,关键的部分程序中都有注释。
完整程序如下:
//做法二,数组
#include <stdlib.h>
#include <stdio.h>
typedef struct Lnode {
int add; //节点地址
int val; //节点值
int next_add; //下一个节点的地址
}List;
int arrangeList(List data[], int N, int head_add);
void exchange(List data[], int i, int j);
void reverseList(List data[], int eff, int K, int last);
void printList(List data[], int last);
int main(){
int K, N, head_add, last;
scanf("%d %d %d", &head_add, &N, &K); //捕获输入的K,N,入口地址
List data[N];
for (int i = 0; i<N; i++) { //捕获输入的列表数据
scanf("%d %d %d", &data[i].add, &data[i].val, &data[i].next_add);
}
last = arrangeList(data, N, head_add); //将无序数据排序,返回末尾节点的下标
reverseList(data, last, K, last); //反转列表
printList(data, last); //输出列表
return 0;
}
int arrangeList(List data[], int N, int head_add) {
int last;
for (int i = 0; i<N; i++) { //遍历数据,找到头节点并置入列表首位
if (data[i].add == head_add) {
exchange(data, i, 0);
break;
}
}
for (int i = 0; i<N; i++) { //遍历数据,按地址顺排序
for (int j = i + 1; j<N; j++) {
if (data[i].next_add == data[j].add) {
exchange(data, i+1, j);
break;
}
}
}
//遍历数据,因为测试集中存在无用数据,因此需找到尾节点下标
for (int i = 0; i<N; i++) {
if (data[i].next_add == -1) {
last = i + 1;
}
}
return last;
}
void exchange(List data[], int i, int j) {
List temp;
temp.add = data[j].add;
temp.val = data[j].val;
temp.next_add = data[j].next_add;
data[j].add = data[i].add;
data[j].val = data[i].val;
data[j].next_add = data[i].next_add;
data[i].add = temp.add;
data[i].val = temp.val;
data[i].next_add = temp.next_add;
}
void reverseList(List data[], int N, int K, int last) {
int times = N / K; //反转次数
if (times) {
//数据首尾两两交换
for (int i = 1; i <= times; i++) {
for (int j = 1; j <= K; j++) {
if ((i - 1)*K + j - 1 <= i * K - j - 1) {
exchange(data, (i - 1) * K + j - 1, i * K - j);
}
}
}
}
//更新逆转完的列表各个节点的下一节点地址
for (int i = 0; i<last; i++){
data[i].next_add = data[i+1].add;
if (i == last - 1) {
data[i].next_add = -1;
}
}
}
void printList(List data[], int last){
//因为测试集存在无用数据,因此尾节点下标之后的数据不需要输出
for (int i = 0; i<last - 1; i++) {
printf("%05d %d %05d\n", data[i].add, data[i].val, data[i].next_add);
}
printf("%05d %d %d\n", data[last - 1].add, data[last - 1].val, -1);
}
如果有哪里理解错的地方欢迎大家留言交流,如需转载请标明出处。
手工码字码图码代码,如果有帮助到你的话留个赞吧,谢谢。
以上。