PTA 6-1 单链表逆转
1.题目
函数接口定义:
List Reverse( List L );
其中List
结构定义如下:
typedef struct Node *PtrToNode;
struct Node {
ElementType Data; /* 存储结点数据 */
PtrToNode Next; /* 指向下一个结点的指针 */
};
typedef PtrToNode List; /* 定义单链表类型 */
L
是给定单链表,函数Reverse
要返回被逆转后的链表。
裁判测试程序样例:
#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;
typedef struct Node *PtrToNode;
struct Node {
ElementType Data;
PtrToNode Next;
};
typedef PtrToNode List;
List Read(); /* 细节在此不表 */
void Print( List L ); /* 细节在此不表 */
List Reverse( List L );
int main()
{
List L1, L2;
L1 = Read();
L2 = Reverse(L1);
Print(L1);
Print(L2);
return 0;
}
/* 你的代码将被嵌在这里 */
输入样例:
5
1 3 4 5 2
输出样例:
1
2 5 4 3 1
2. 实现代码
1. 1.0版本
List Reverse(List L){
if(!L) return NULL;
if(!L->Next) return L;
List p=L;
List p0=NULL;
int count=0;
while(p->Next){
count++;
p0=p;
p=p->Next;
}
List Head=p;
p->Next=p0;
p0->Next=NULL;
for(int i=0;i<count-1;i++){
p=L;
p0=NULL;
while(p->Next){
p0=p;
p=p->Next;
}
p->Next=p0;
p0->Next=NULL;
}
return Head;
}
- 这是我最初的思路,就是先找到这个链表的尾结点,同时标记尾结点的前驱结点,将尾结点的指针指向前驱结点,然后前驱结点的指针赋值成NULL,成为新的尾结点。重复这个过程,就能将链表反转。虽然这个代码没有超出题目的时间限制,但是不难看出,它的时间复杂度已经到达了O(n^2)。这显然不是我们想要的结果。
2. 2.0版本
List Reverse(List L){
if(!L) return NULL;
if(!L->Next) return L;
List pointer[10000];
int i=0;
List p=L;
while(p){
pointer[i++]=p;
p=p->Next;
}
p=L;
for(int j=i-1;j>=1;j--){
pointer[j]->Next=pointer[j-1];
}
pointer[0]->Next=NULL;
return pointer[i-1];
}
- 为了降低时间复杂度,我用一个数组(有赌的成分,因为根本就不知道链表有多长)记录每个结点的指针,然后通过这个数组对链表进行了反转。这样虽然成功的将时间复杂度降到了O(n),但是却浪费了很多的空间。
3. 最终版本
List Reverse(List L){
List p=L;
List new;
L=NULL;
while(p){
new=p;
p=p->Next;
new->Next=L;
L=new;
}
return L;
}
- 后来经过思考,发现其实完全可以在遍历链表的同时,对已经遍历过的结点进行操作(这样不会影响后续的结点)。首先将p赋值成L,然后L赋值为NULL,作为新的链表的头结点。在遍历结点的过程中,L是当前新链表的头结点。每遍历一个结点,首先将p转交给new,然后p指向下一项(如果先对new进行操作,则会导致链表指针错乱),之后将new添加到新链表中,之后将L更新。时间复杂度O(n)。
3. 总结
- 初入数据结构,经验不足,最终答案好像是最简单最简洁最通俗易懂的,但是往往开始的思路却是那么复杂。不过每次改进算法,也是一种成长。没有人一入武林便是高手,没有人一入乱世便是英雄。成长总是渐进的,努力前行吧。