单向链表的反转
对单向链表的反转是非常经典的算法题,链表不同于数组,节点的遍历需要每个节点逐个访问下去。理解反转的过程能对线性表的链式存储结构有个充分的认识。为了方便理解(也为了防止自己日后忘了)所以尽可能仔细的记录其过程。
建表
首先定义链表结构:
1 | typedef struct Node |
创建一个链表
1 | //n 描述了数据数组长度 |
一定要注意自己的建表方法,因为尾插法插入是按照数据顺序建表的,而头插法则是逆序。如果你没注意到而用头插法顺序插入数据然后反转当然又变成了顺序了。为了演示这边使用的是尾插法。同时补上头插法的代码,因为头插法的思想在这里非常重要
我们以一个长度为5数组来演示:
1 | a[5] = {1,2,3,4,5}; |
我们得到了一个以L为头指针,包含了五个节点的单向链表
反转
迭代法
把这条链表想象成一个字符串,如果让你逆序一个字符串”Hello World”,你会怎么做?除了对半交换外,还有个方法是创建一个新的空间然后逆序存放原字符串的每个字符。
我们也是这样,首先创建一个新的头节点 ,就叫它 NewL。让他的后继节点为NULL,是不是有点像头插法建表步骤, 同时新建一个p指针指向L表的第一个节点,也就是p = L -> next
。
现在我们可以让 p -> next指向 NewL?
指完就爆炸,不同于头插法那时我们一直在生成新的p节点,现在如果直接让 p 的下一节点指向新的表尾,那我们就丢失了L表中 p -> next,也就是p后继节点的位置,我们就没法遍历接下来的L表了,所以我们需要创建一个temp 来暂时存放p的后继节点,等到 p 也就是当前节点添加到新表完成后再让p回到temp继续。
- 我们先创建一个节点指针temp,让temp=p->next存储p的后继节点信息
- 接着我们就可以放心地让p指向新表的表尾(表尾是因为此时NewL还只是NULL)
1 | temp = p -> next; //让temp存放p后驱节点 |
我们完成了一个节点的插入到新表,接下来我们要准备后续节点。首先我们让 NewL -> next = p 从而使接下来的节点始终插入在新表头指针后面,如果你了解头插法你就会发现这一过程极其相似,为了实现逆序我们就是以原来的L表用头插法来创建一个新表,头插法本身就是逆置建表,让p回到temp后进入下次循环,新节点会不断的插入在表头后,直到L表结束。
1 | //头插法部分: |
1 | //迭代法部分: |
- NewL 头指针后继指向p(新插入节点)
- p移动到temp位置回来原表,准备下次循环直到NULL
代码实现:
1 | LinkList reverse(LinkList L){ |
显示链表,注意这里链表都是有头节点
1 | void display(LinkList L){ |
基本上就是迭代法实现单链表反转的过程。
后面会再讲讲还有一种方法递归法。
Asahi
2019.10.16