单链表反转以及相关面试题

(一)单链表的结点结构: 

    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;
    }
}
 
    

(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节点。如有相同的理解错误的读者,请仔细思考一番。

发布了23 篇原创文章 · 获赞 12 · 访问量 9559

猜你喜欢

转载自blog.csdn.net/geng2568/article/details/87924315
今日推荐