链表反转:有两种方式 - 普通、递归
2013-04-18 13:10
281 查看
2013-4-11,周四,搜狗笔试题:单链表反转。这个题是经常出现的一个笔试和面试题。
题目:输入一个链表的头结点,反转该链表,并返回反转后链表的头结点
分析:这是一道广为流传的微软面试题。由于这道题能够很好的反应出程序员思维是否严密,在微软之后已经有很多公司在面试时采用了这道题。
为了正确地反转一个链表,需要调整指针的指向。与指针操作相关代码总是容易出错的,因此最好在动手写程序之前作全面的分析。在面试的时候不急于动手而是一开始做仔细的分析和设计,将会给面试官留下很好的印象,因为在实际的软件开发中,设计的时间总是比写代码的时间长。与其很快地写出一段漏洞百出的代码,远不如用较多的时间写出一段健壮的代码。
为了将调整指针这个复杂的过程分析清楚,我们可以借助图形来直观地分析。假设l、m和n是三个相邻的结点:
a b …l m n …
假设经过若干操作,我们已经把结点l之前的指针调整完毕,这些结点的 pNext 指针都指向前面一个结点。现在我们遍历到结点m。当然,我们需要把调整结点的 pNext 指针让它指向结点l。但注意一旦调整了指针的指向,链表就断开了,如下所示:
a b…l m n…
因为已经没有指针指向结点n,我们没有办法再遍历到结点n了。因此为了避免链表断开,我们需要在调整m的 pNext 之前要把n保存下来。
接下来我们试着找到反转后链表的头结点。不难分析出反转后链表的头结点是原始链表的尾位结点。什么结点是尾结点?就是 pNext 为空指针的结点。
示例代码:
延伸阅读:
class Node
{
Object data;
Node next; //指向下一个结点
}
将数据域定义成Object类是因为Object类是广义超类,任何类对象都可以给其赋值,增加了代码的通用性。为了使链表可以被访问还需要定义一个表头,表头必须包含指向第一个结点的指针和指向当前结点的指针。为了便于在链表尾部增加结点,还可以增加一指向链表尾部的指针,另外还可以用一个域来表示链表的大小,当调用者想得到链表的大小时,不必遍历整个链表。
需要反复思考,总结,消化为自己的知识。
参考链接:
http://blog.163.com/sparkle_tiangz/blog/static/11759020320102125278750/
题目:输入一个链表的头结点,反转该链表,并返回反转后链表的头结点
分析:这是一道广为流传的微软面试题。由于这道题能够很好的反应出程序员思维是否严密,在微软之后已经有很多公司在面试时采用了这道题。
为了正确地反转一个链表,需要调整指针的指向。与指针操作相关代码总是容易出错的,因此最好在动手写程序之前作全面的分析。在面试的时候不急于动手而是一开始做仔细的分析和设计,将会给面试官留下很好的印象,因为在实际的软件开发中,设计的时间总是比写代码的时间长。与其很快地写出一段漏洞百出的代码,远不如用较多的时间写出一段健壮的代码。
为了将调整指针这个复杂的过程分析清楚,我们可以借助图形来直观地分析。假设l、m和n是三个相邻的结点:
a b …l m n …
假设经过若干操作,我们已经把结点l之前的指针调整完毕,这些结点的 pNext 指针都指向前面一个结点。现在我们遍历到结点m。当然,我们需要把调整结点的 pNext 指针让它指向结点l。但注意一旦调整了指针的指向,链表就断开了,如下所示:
a b…l m n…
因为已经没有指针指向结点n,我们没有办法再遍历到结点n了。因此为了避免链表断开,我们需要在调整m的 pNext 之前要把n保存下来。
接下来我们试着找到反转后链表的头结点。不难分析出反转后链表的头结点是原始链表的尾位结点。什么结点是尾结点?就是 pNext 为空指针的结点。
示例代码:
package com.suanfa.lianbiao; class Node<E> { // 数据 public E data; // 指向下一个节点 public Node<E> next; public Node(E data) { this.data = data; } // public Node<E> getNext() { // return next; // } // // public void setNext(Node<E> next) { // this.next = next; // } } class LinkList<E> { public Node<E> first; // 链表中数据项的个数 public int size; public LinkList() { first = null; size = 0; } // 在表头插入新的数据 public void insertFirst(E value) { Node<E> link = new Node<E>(value); link.next = first; first = link; size++; } // 判断链表是否为空 public boolean isEmpty() { return size == 0; } // 删除表头 public Node<E> deleteFirst() { Node<E> temp = first; first = first.next; size--; return temp; } // 输出链表中的所有数据 public void display() { Node<E> curr = first; while (curr != null) { System.out.print(curr.data + " "); curr = curr.next; } System.out.println(); } // 返回链表中数据项的个数 public int size() { return size; } // 获取从头至尾的第i个数据项 public Node<E> get(int i) { if (i > size() - 1 || i < 0) { try { throw new IndexOutOfBoundsException(); } catch (Exception e) { e.printStackTrace(); } } Node<E> curr = first; for (int n = 0; n < size(); n++) { if (n == i) { return curr; } else { curr = curr.next; } } return null; } // 输出从头至尾的第i个数据项 public void remove(int i) { if (i == 0) { deleteFirst(); } else if (i == size() - 1) { get(i - 1).next = null; } else { get(i - 1).next = get(i + 1); } size--; } // 其实下面这2个反转的方法原理都是一样的,就是有的把第一个节点当做cur,有的把第二个节点当做cur // 两种方式实现单链表的反转(递归、普通) public Node<E> reverse_1(Node<E> head) { if (head == null) { return null; } // 链表反转后的新头结点 Node<E> reverseHead = null; Node<E> cur = head; Node<E> pre = null; while (cur != null) { Node<E> next = cur.next; if (next == null) { reverseHead = cur; } cur.next = pre; pre = cur; cur = next; } return reverseHead; } /** * 遍历,将当前节点的下一个节点缓存后更改当前节点指针 * */ public Node<E> reverse_2(Node<E> head) { if (null == head) { return head; } Node<E> pre = head; Node<E> cur = head.next; Node<E> next; while (null != cur) { next = cur.next; cur.next = pre; pre = cur; cur = next; } // 将原链表的头节点的下一个节点置为null,再将反转后的头节点赋给head head.next = null; head = pre; return head; } /** * 递归,在反转当前节点之前先反转后续节点 */ public Node<E> reverse_3(Node<E> head) { if (null == head || null == head.next) { return head; } Node<E> reversedHead = reverse_3(head.next); head.next.next = head; head.next = null; return reversedHead; } } public class Link_list { public static void main(String[] args) { LinkList<Long> ll = new LinkList<Long>(); // 构造链表 for (int i = 0; i < 10; i++) { Long value = (long) (Math.random() * 100); ll.insertFirst(value); } ll.display(); while (!ll.isEmpty()) { ll.deleteFirst(); ll.display(); } System.out.println("Ok"); } }
延伸阅读:
class Node
{
Object data;
Node next; //指向下一个结点
}
将数据域定义成Object类是因为Object类是广义超类,任何类对象都可以给其赋值,增加了代码的通用性。为了使链表可以被访问还需要定义一个表头,表头必须包含指向第一个结点的指针和指向当前结点的指针。为了便于在链表尾部增加结点,还可以增加一指向链表尾部的指针,另外还可以用一个域来表示链表的大小,当调用者想得到链表的大小时,不必遍历整个链表。
需要反复思考,总结,消化为自己的知识。
参考链接:
http://blog.163.com/sparkle_tiangz/blog/static/11759020320102125278750/
相关文章推荐
- 【剑指offer】递归循环两种方式反转链表
- 链表反转(使用递归和非递归两种方式)
- 【剑指offer】递归循环两种方式反转链表
- 【剑指offer】递归循环两种方式反转链表
- 使用递归和非递归方式反转单向链表
- 链表反转的递归和非递归实现方式
- 使用递归和非递归方式反转单向链表
- 递归的方式反转链表
- 两有序链表合并为一个--递归与非递归两种方式
- 链表反转的递归和非递归实现方式
- 递归建立普通二叉树两种方式遍历
- 全面分析再动手的习惯:链表的反转问题(递归和非递归方式)
- 使用递归和非递归方式反转单向链表
- 递归,迭代,堆栈三种方式实现单链表反转(C++)
- 使用单链表反转的递归和非递归实现方式
- 反转链表的循环方式和递归方式
- java实现单链表反转(递归方式)
- 两有序链表合并为一个--递归与非递归两种方式
- 如何使用递归和非递归方式反转单向链表
- 反转一个链表的两种方法:递归和循环