想看最后的正确代码请直接奔最后!
在浙大版的《数据结构》的数据结构实现基础部分的链表部分看到这样的一个题目。
自己想了一个,结果不行,就看了解释以及代码,嗯,没错,看了好久才明白。
这个题目据说是很多公司的面试题,大致比较流行的有两种方法,一种叫”迭代法“,也就是用循环吧,这个有两种的思路(暂时算两种,这两种方法有点殊途同归的感觉,后面会具体分析到);另外一种是递归,这种方法等我学习到递归再来做一遍~
先说一下浙大书上的解法,这个解法看了老半天了,其实只是因为自己对某个知识点的理解出了问题,那就先说一下这个知识点吧!
p->next 是个什么鬼??
现在啊,我就这么直白的想,比如以下结构体的定义:
struct _node{
int data;
struct _node* next;
} Node;
来一个结构体实体:
p = (Node*) malloc( sizeof(Node));
然后呢,p->next 就是:
p 这个指针指向的那个结点的指针域所指的地址!
p->next 是个指针。
p->next 是个指针。
p->next 是个指针。
之前啊,我就老把它想成是p所指向的下一个节点,真是无语了!!
哦,对了,还有一个知识点:
比如p , q 他俩是指针,然后去q = p说明什么?
说明的就是q 现在要指向 p 所指向的那个地址单元了!! 不是我之前所理解的,q 指向 p,然后p 指向某个单元,晕~~~
说了这么多,终于可以开始说浙大书上的那种解决办法了,关键思想就是一句话:循环的主体部分就是将 oldHead 的第一个元素插入到 newHead 的头上,同时更新 newHead 和 oldHead 的值。(其中oldHead是一个待逆转链表的头,newHead 是一个已经逆转好的链表的头,这么说的话 oldHead 的初值就是需要逆转的链表的头,newHead 的初值就为空了,因为一开始的时候有0个结点已经逆转好)
然后呢,比如说现在的结点顺序是这样子的:A->B->C->D. A就是 oldHead 的第一个元素,newHead现在是为空的,要把 A 插入到 newHead 的头上,就是oldHead->next = new_Head !!这里是比较关键的一点,一定要想通。
首先你一定会认同那个关键思想,但很有可能在第一个结点上犯糊涂。你很可能会说,第一个结点干嘛要反转呢,不是只需要把 A 后面所有除去 A 之后的结点反转,然后让 A 的 next 指向空不就OK了吗。
嗯,没错,可以这么想,这就是下一个方法里比较重要的一点需要注意的了。现在这个方法的核心思想,也就是”循环不变式“(循环不变式表示一种在循环过程进行时不变的性质,不依赖于前面所执行过程的重复次数的断言),对它的理解,A 结点也不能例外:反转,即插入到 newHead 的头上!
好了,回到这个问题。现在我们已经完全认同、完全明白了这个思想,当把 A 插入到 newHead 的头上之后,我们就需要更新 newHead 的值了,newHead 是始终指向已逆转结点的头节点的,而 oldHead 所指向的 A 就是之前的待转结点,现在 A 已经逆转,就顺理成章的成为了已逆转链表的头,那么就要用 newHead 指向它了:newHead = oldHead。
这里还需要注意一个问题,比如说,当你逆转了 A 之后,即 oldHead->next = newHead之后,我们要怎么去找到 B ,然后把它逆转呢?这里就需要引进一个指针 temp,temp 用来来帮我们记录待转结点的下一个结点的指针。
所以代码如下:
Node* reverse2(Node* head)
{
Node* oldHead, *newHead, *temp;
oldHead = head;
newHead = NULL; //不明白可以看上面的文字解释
while( oldHead ){
temp = oldHead->next;
oldHead->next = newHead; //逆转结点
newHead = oldHead; //更新newHead的值
oldHead = temp; //更新oldHead的值
}
head = newHead;
return newHead;
}
下面讲一下我在想不通浙大版的代码的时候在网上看到的一个很好懂的方法。
这个方法也是有三个指针,但是给这三个指针的”名头“没有那么好听。
这三个指针是这样定义的:p1 = head; p2 = p1->next; p3 = p2->next;
在写完上面对浙大版思路的理解之后,发现,p1, p2, p3 这三个指针都可以在上面的方法中找到对应:p3 就是上面的 temp,它的存在是为了找到待转结点的下一个结点;p2 就是 oldHead,指向待转结点(待转链表的头);p1自然就是已转链表的头了。
这时候恐怕就要问了,p1 = head; 不就说明了 p1 是第一个结点了吗,那么第一个结点就不用转了咯,只需要在除第一个结点之后的其它结点都逆转之后再把p1接上去就OK了?答对了! 这就是方法一所说的,这是这个方法的一个需要注意的地方了。
为什么需要注意呢?就是因为第一个结点不用逆转,所以我们一开始就要做p1->next = NULL; 否则,将会出现无限循环。
相信有了第一种方法,这种方法会比较好理解了,就直接贴出代码了:(首先来一个自己想错的)
Node* reverse55(Node *head)
{
Node *p1, *p2,*p3;
p1 = head;
p2 = p1->next;
p3 = p2->next;
p1->next = NULL;
while ( p2 ){
p2->next = p1;
p2 = p3;
p3 = p3->next;
p1 = p2;
}
return p1;
}
有木有人真的去测试这个代码,这个代码的错误原因是我在MOOC上看C的视频的时候才是到的,错误的原因就是一个空指针的 next 是会是程序崩掉的,当循环到最后一次,p3->next 就是错误的了。
正确的代码:
Node* reverse55(Node *head)
{
Node *p1, *p2,*p3;
p1 = head;
p2 = p1->next;
p3 = p2->next;
p1->next = NULL;
while ( p3 ){
p2->next = p1;
p2 = p3;
p3 = p3->next;
p1 = p2;
}
p2->next = p1;
head = p2;
return head;
}