(一)单链表的结点结构:
data域:存储数据元素信息的域称为数据域;
next域:存储直接后继位置的域称为指针域,它是存放结点的直接后继的地址(位置)的指针域(链域)。
data域+ next域:组成数据ai的存储映射,称为结点;
注意:①链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的。
②每个结点只有一个链域的链表称为单链表(Single Linked List)。
所谓的链表就好像火车车厢一样,从火车头开始,每一节车厢之后都连着后一节车厢。
要实现单链表存储,首先是创建一结点类,其Java代码如下:
package Single_Linked_List;
class Node {
private int Data;// 数据域
private Node Next;// 指针域
public Node(int Data) {
// super();
this.Data = Data;
}
public int getData() {
return Data;
}
public void setData(int Data) {
this.Data = Data;
}
public Node getNext() {
return Next;
}
public void setNext(Node Next) {
this.Next = Next;
}
}
(二)实现反转的方法:
(1)递归反转法:在反转当前节点之前先反转后续节点。这样从头结点开始,层层深入直到尾结点才开始反转指针域的指向。简单的说就是从尾结点开始,逆向反转各个结点的指针域指向,其过程图如下所示:
head:是前一结点的指针域(PS:前一结点的指针域指向当前结点)
head.getNext():是当前结点的指针域(PS:当前结点的指针域指向下一结点)
reHead:是反转后新链表的头结点(即原来单链表的尾结点)
Java代码实现:
递归实质上就是系统帮你压栈的过程,系统在压栈的时候会保留现场。
//递归反转单链表 特征:只要我有一个指针始终指向头结点的地址,它就相当于一个集合的功能了
package Single_Linked_List;
public class Di_Gui{
public static void main(String[] args) {
Node head = new Node(0);
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
head.setNext(node1);
node1.setNext(node2);
node2.setNext(node3);
// 打印反转前的链表
Node h = head;
while (null != h) {
System.out.print(h.getData() + " ");
h = h.getNext();
}
// 调用反转方法
head = Reverse1(head);
System.out.println("\n**************************");
// 打印反转后的结果
while (null != head) {
System.out.print(head.getData() + " ");
head = head.getNext();
}
}
/**
* 递归,在反转当前节点之前先反转后续节点
*/
public static Node Reverse1(Node head) {
// head看作是前一结点,head.getNext()是当前结点,reHead是反转后新链表的头结点
if (head == null || head.getNext() == null) {
return head;// 若为空链或者当前结点在尾结点,则直接还回
}
先反转后续节点head.getNext(),reHead永远指向的是Node3节点
Node reHead = Reverse1(head.getNext());
// 将当前结点的指针域指向前一结点,下面 是弹栈的过程
head.getNext().setNext(head);
// 前一结点的指针域令为null;
head.setNext(null);
// 反转后新链表的头结点
return reHead;
}
}
我们来看是怎样的一个递归过程:1->2->3->4
程序到达Node newHead = reverse(head.next);时进入递归
我们假设此时递归到了3结点,此时head=3结点,temp=3结点.next(实际上是4结点)
执行Node newHead = reverse(head.next);传入的head.next是4结点,返回的newHead是4结点。
接下来就是弹栈过程了
程序继续执行 temp.next = head就相当于4->3
head.next = null 即把3结点指向4结点的指针断掉。
返回新链表的头结点newHead
注意:当retuen后,系统会恢复2结点压栈时的现场,此时的head=2结点;temp=2结点.next(3结点),再进行上述的操作。最后完成整个链表的翻转。
(2)遍历反转法:递归反转法是从后往前逆序反转指针域的指向,而遍历反转法是从前往后反转各个结点的指针域的指向。
基本思路是:将当前节点cur的下一个节点 cur.getNext()缓存到temp后,然后更改当前节点指针指向上一结点pre。也就是说在反转当前结点指针指向前,先把当前结点的指针域用tmp临时保存,以便下一次使用,其过程可表示如下:
pre:上一结点
cur: 当前结点
tmp: 临时结点,用于保存当前结点的指针域(即下一结点)
Java代码实现:
package Single_Linked_List;
public class Di_Gui{
public static void main(String[] args) {
Node head = new Node(0);
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
head.setNext(node1);
node1.setNext(node2);
node2.setNext(node3);
// 打印反转前的链表
Node h = head;
while (null != h) {
System.out.print(h.getData() + " ");
h = h.getNext();
}
// 调用反转方法
head = reverse2(head);
System.out.println("\n**************************");
// 打印反转后的结果
while (null != head) {
System.out.print(head.getData() + " ");
head = head.getNext();
}
}
/**
* 遍历,将当前节点的下一个节点缓存后更改当前节点指针
*/
public static Node reverse2(Node head) {
if (head == null) return head;
// 上一结点
Node pre = head;
// 当前结点
Node cur = head.getNext();
// 临时结点,用于保存当前结点的指针域(即下一结点)
Node tmp;
// 当前结点为null,说明位于尾结点
while (cur != null) {
tmp = cur.getNext();
// 反转指针域的指向
cur.setNext(pre);
// 指针往下移动
pre = cur;
cur = tmp;
}
// 最后将原链表的头节点的指针域置为null,还回新链表的头结点,即原链表的尾结点
head.setNext(null);
return pre;
}
//循环实现链表反转--效率最佳
public static ListNode ReverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode pre = null;//存放当前节点前一个元素
ListNode post = null;//当前节点的下一个节点
while (head != null) {
post = head.next;
head.next = pre;
pre = head;
head = post;
}
return pre;
}
}
面试题二、返回一个链表的倒数第K个元素
//查找倒数第k个节点
public static ListNode FindKthToTail(ListNode head, int k) {
ListNode p, q;
p = head;
q = head;
int i = 0;
//两个指针p、q,查询倒数第k个元素,正序先让k先走K-1步,
//然后两个指针同时走,当p走到链表的尾部时,q刚好指的是倒数第K个
for (; p != null; i++) {
if (i >= k) {
q = q.next;
}
p = p.next;
}
return i < k ? null : q;
}
个人建议:
递归方法实现链表反转虽然比较优雅,但是对于不了解递归的同学来说还是有理解难度的。请读者好好自习琢磨一下,递归的妙法所在,当时自己理解了半天,搞错了。递归最后一边时,把head理解成了Node3节点,实际是Node2节点。如有相同的理解错误的读者,请仔细思考一番。
2、 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;RandomListNode(int label) {
this.label = label;
}
}
public
class
Solution {
public
RandomListNode Clone(RandomListNode pHead) {
if
(pHead ==
null
) {
return
null
;
}
RandomListNode currentNode = pHead;
//1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
while
(currentNode !=
null
){
RandomListNode cloneNode =
new
RandomListNode(currentNode.label);
RandomListNode nextNode = currentNode.next;
currentNode.next = cloneNode;
cloneNode.next = nextNode;
currentNode = nextNode;
}
currentNode = pHead;
//2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
while
(currentNode !=
null
) {
currentNode.next.random = currentNode.random==
null
?
null
:currentNode.random.next;
currentNode = currentNode.next.next;
}
//3、拆分链表,将链表拆分为原链表和复制后的链表
currentNode = pHead;
RandomListNode pCloneHead = pHead.next;
while
(currentNode !=
null
) {
RandomListNode cloneNode = currentNode.next;
currentNode.next = cloneNode.next;
cloneNode.next = cloneNode.next==
null
?
null
:cloneNode.next.next;
currentNode = currentNode.next;
}
return
pCloneHead;
}
}