数据结构:链表

231 阅读3分钟

单向链表:

是由一个个节点组成的,每个节点是一种信息集合,包含元素本身以及下一个节点的地址。

链表可以有头结点也可以没有,区别在于链表有头结点虽浪费空间,但易理解,边界好处理,不易出错

image.png

定义单链表及其操作

public class SingleList {
    //定义一个节点内部类
    class Node{
        int data;
        Node next;
        public Node(int data){
            this.data = data;
        }
    }
    //头节点(头节点我们保持不动)
    public Node head;
    //当前节点,方便添加和遍历操作(通过当前节点遍历链表)
    public Node current;
    public void add(int data){
        
        //链表为空的情况
        if(head == null){
            head = new Node(data);
            current = head;
        }else{
            //将新节点放在当前节点的后面
            current.next = new Node(data);
            //当前节点后移
            current = current.next;
        }

    }
    //可以从node开始遍历,不需要从head开始遍历
    public void print(Node node){
        if(node == null){
            return;
        }
        current = node;
        while(current != null){
            System.out.println(current.data);
            current=current.next;
        }
    }
    public static void main(String[] args) {
        SingleList list = new SingleList();
        list.add(1);
        list.add(2);
        list.add(3);
        list.print(list.head);
    }
}

如何将数组转换为链表:

将数组的第一个元素作为链表的头节点,current开始等于头结点,进入循环,将数组中的值赋值给节点,当前节点指向新节点,之后将新节点当作当前节点

public Node arrayToList(int[] arr){
        if(arr.length==0){
            return null;
        }
        Node head = new Node(arr[0]);
        Node current=head;
        for(int i=1;i<arr.length;i++){
            Node node =new Node(arr[i]);
            current.next=node;
            current=node;
        }
        return head;
    }

下面来看一些算法题:

例题1:查找单链表中的倒数第k个节点

思路1:先遍历出链表的长度n,倒数第k个节点在n-k+1位置

public Node FindKthToTail (Node head, int k) {
        int n=0;
        current = head;
        //第一次遍历得到长度n
        while(current.next!=null){
            n++;
            current=current.next;
        }
        //第二次遍历,输出倒数第k个节点
        current=head;
        for(int i=0;i<n-k+1;i++){
            current=current.next;
        }
        return current;
    }

思路2:定义两个指针,慢指针和快指针初始化都为head,将快指针移动到第K个节点时,快慢指针同时后移,当快指针到达尾节点时,慢指针此时指向倒数第K个节点

    public Node FindKthToTail (Node head, int k) {
       Node slow=head;
       Node fast=head;
       for(int i=1;i<k;i++){
           fast=fast.next;
       }
       while(fast.next!=null){
            fast=fast.next;
            slow=slow.next;
       }
       return slow;
    }

例题2:链表反转

思路1:使用递归

image.png

    public Node ReverseList(Node head) {
        if(head==null || head.next==null){
            return head;
        }
        Node nextNode = head.next;
        Node newHead = ReverseList(nextNode);
        nextNode.next=head;
        head.next=null;
        return newHead;
    }

思路2:定义前后指针,将前后指针的指向反转

image.png

public Node ReverseList(Node head) {
        if(head==null || head.next==null){
            return head;
        }
        Node pre=head;
        Node current=head.next;
        Node temp;
        while(current!=null){
            temp=current.next;
            current.next=pre;
            pre=current;
            current=temp;
        }
        head.next=null;
        return pre;
    }

例题3:环形链表

判断链表是否有环:设置两个快慢指针,慢指针以速度1移动,快指针以速度2移动。快慢指针如果相遇,说明链表有环

public boolean hasCycle(Node head){
        if(head==null || head.next==null){
            return false;
        }
        Node slow=head;
        Node fast=head;
        while(true){
            if(fast==null || fast.next==null){
                return false;
            }
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast){
                return true;
            }
        }
    }

找到环形链表的入口节点:

第一次相遇后,让fast重新指向头节点,然后fast和slow以相同速度移动,第二次相遇时,此节点即为入口节点

这个题目有点绕,题解就不写了

    public Node EntryNodeOfLoop(Node head){
        if(head==null || head.next==null){
            return null;
        }
        Node slow=head;
        Node fast=head;
        while(true){
            if(fast==null || fast.next==null){
                return null;
            }
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast){
                break;
            }
        }
        fast=head;
        while(true){
            slow=slow.next;
            fast=fast.next;
            if(slow==fast) {
                return slow;
            }
        }
    }

例题4:合并两个排序的链表

递归:先找到两个链表更小的那个头节点,这个头结点指向后面更小的数

public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null){
            return list2;
        }
        if(list2==null){
            return list1;
        }
        if(list1.val<list2.val){
            list1.next=Merge(list1.next,list2);
            return list1;
        }else{
            list2.next=Merge(list1,list2.next);
            return list2;   
        }
    }

用一个新链表来保存排序后的链表

image.png

public ListNode Merge(ListNode list1, ListNode list2) {
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }
        ListNode head = new ListNode(-1);
        ListNode list3 = head;
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                list3.next = list1;
                list1 = list1.next;
            } else {
                list3.next = list2;
                list2 = list2.next;
            }
            list3 = list3.next;
        }
   
        while (list1 != null) {
            list3.next = list1;
            list1 = list1.next;
            list3 = list3.next;
        }
   
        while (list2 != null) {
            list3.next = list2;
            list2 = list2.next;
            list3 = list3.next;
        }
        return head.next;
    }