链表

单向链表:

双向链表:

 

单向链表的一些理解:

通过定义一个节点类Node来表示链表结点,每个结点是这个类的一个对象

这个类包括一个Node类型的属性next用来保存下一个节点的地址,包括一个属性data用来保存这个节点的数据

如果这个链表是无环的,则最后一个节点的next为null

下面是单向链表的实现和一些操作:

public class MyLink {

    Node head;         // 设置一个头部节点
   Node tail;          // 设置一个尾部节点
    
    private static class Node{
        Node next;    // 创建一个Node类型的属性,用来保存后面的结点地址
        // int data;  // 这个表只能存储int型的数据类型
        Object data;  
        public Node(Object data){
            this.data = data;
        }
    }


    // 向链表尾部或者头部添加节点
    public void addNode(Object d){
        Node newNode = new Node(d);
     // 方法一:添加节点到尾部
        if(head==null){
            head = newNode;
            return;
        }
        Node tmp = head;
        while(tmp.next != null){ 
            tmp = tmp.next;
        }
        tmp.next = newNode;
        /* 方法二:添加节点到头部
     Node newNode = new Node(d);
        newNode.next = head;
        head = newNode;
        */
    }

    // 添加节点到指定位置
    public void addNodeByIndex(int index, Object data){
        if(index<0 || index>linkLength()+1)
            System.out.println(">>>超出添加范围,添加失败<<<");
        else {
            Node newNode = new Node(data);
            if (index == 1) {   // 添加到头部
                newNode.next = head;
                head = newNode;
            }
            Node tmp = head;
            int i = 1;
            while (true) {     // 添加到任意位置
                if (i == index-1) {
                    Node target = tmp.next;
                    tmp.next = newNode;
                    newNode.next = target;
                    break;
                }
                i++;
                tmp = tmp.next;
            }
        }
    }

    // 删除指定位置的节点
    public void deleteNode(int index){
        if(index<1 || index>linkLength()){
            System.out.println(">>>超出链表范围<<<");
        }
        else {
            if (index == 1) {
                head = head.next;  // 删除头结点
            }
            int i = 1;
            // 先循环到找到指定节点的前一节点;这种方法可以吗
            Node tmp = head;
            while (tmp.next != null) {  
                if (i == index - 1)
                    break;
                tmp = tmp.next;
                i++;
            }
            Node target = tmp.next;
            tmp.next = target.next;
//            方法二:这种挺好
//            int i = 1;
//            Node preNode = head;
//            Node curNode = preNode.next;
//            while (curNode != null) {
//                if (i == index-1) {
//                    preNode.next = curNode.next;
//                    return true;
//                }
//                preNode = curNode;
//                curNode = curNode.next;
//                i++;
//            }
        }

    }

    // 统计链表长度
    public int linkLength(){
        Node tmp = head;
        int len=0;
        // 这里不能用tmp.next!=null做条件,因为当第一个节点为null的时候会报错
        while(tmp!=null){
            tmp = tmp.next;
            len++;
        }
        return len;
    }

    // 打印链表数据
    public void printLinkData(){
        System.out.println("---------打印开始------------");
        for(Node x=head; x!=null; x=x.next){
            System.out.println(x.data);
        }
        System.out.println("---------打印结束------------");
        // 打印方法二
//        Node tmp = head;
//        while(tmp!=null){
//            System.out.println(tmp.data);
//            tmp = tmp.next;
//        }
    }

    // 获取指定位置的链表数据
    public Object getDataByindex(int index){
        if(index<1 || index>linkLength())
            return null;
        Node tmp = head;
        int i=0;
        while(tmp!=null){
            if(i==index-1){
                return tmp.data;
            }
            i++;
            tmp = tmp.next;
        }
        return null;
    }

    public static void main(String[] args){
//        Node first = new Node(1);
//        Node second = new Node(2);
//        Node third = new Node(3);
//        first.next = second;
//        second.next = third;
//        for(Node x=first; x!=null; x=x.next){
//            System.out.println(x.data);
//        }
        MyLink myLink = new MyLink();
        myLink.addNode(1);
        myLink.addNode('a');
        myLink.addNode("string");
        myLink.addNode(3.4);
        myLink.addNode(5);
        myLink.printLinkData();
        myLink.deleteNode(3);
        myLink.printLinkData();
        System.out.println("length: " + myLink.linkLength());
        System.out.println("getData: " + myLink.getDataByindex(4));
        myLink.addNodeByIndex(5, "three");
        myLink.printLinkData();
    }
}
View Code

 

链表反转:

递归比较难理解,参考:如何递归反转链表

public Node reverseLink(Node head){
    Node pCurr = head;
    Node pPrev = null;
    while(pCurr!=null){      // 用一个简单的例子来理解过程  first second third
        Node pNext = pCurr.next;
        pCurr.next = pPrev;  // 把null给第一个节点存储,第一个节点给第二个节点储存...  不断反转
        pPrev = pCurr;       
        pCurr = pNext;
    }
    this.head = pPrev;
    return this.head;
}

// 递归实现
public Node reverseLinkByRecursion(Node head){
    if(head==null || head.next==null)
        return head;  // 5
    Node newLink = reverseLinkByRecursion(head.next);
    head.next.next = head;   // 1->2->3->4->5   这里实现5->4                          
    head.next = null;                        // 这里实现4—>null
    return newLink;                 // 返回尾结点 5
}

// 递归实现反转前n个节点
Node successor = null;
public Node reverseNLinkByRecursion(Node head, int n){
    if(n==1){                   // 1—>2->3->4->5 反转前3个,需要到3, 3->2->4,3->2->1->4
        successor = head.next;  // 记录n结点的下一个结点,4
        return head;  // 返回新的头结点
    }
    Node newlink = reverseNLinkByRecursion(head.next, n-1);
    head.next.next = head;
    head.next = successor;
    return newlink;
}

// 实现反转链表m-n之间的结点
// m和n同速走,先到达m的位置,再反转后面的结点,例如反转2-4之间的结点 m==1的时候head=2, n=3(2,3,4)
public Node reverseMN(Node head, int m, int n){
    if(m==1){
        return reverseNLinkByRecursion(head, n);
    }
    head.next = reverseMN(head.next, m-1, n-1);
    return head;
}

 

链表排序

// 链表排序(O(n^2)的时间复杂度)
public void orderList(Node head){
    Node tmp = head;
    while(tmp!=null){
        Node pNext = tmp.next;
        while(pNext!=null){
            if(tmp.data>pNext.data){
                int temp = pNext.data;
                pNext.data = tmp.data;
                tmp.data = temp;
            }
            pNext = pNext.next;
        }
        tmp = tmp.next;
    }
}
不要用选择排序,用归并

O(nlogn)时间复杂度的链表排序归并排序+找链表的中间结点+合并两条有序链表  

O(1)空间的链表插入排序:插入排序

链表的快速排序

// 方法三:快速排序
public ListNode quickSort(ListNode head, ListNode end){
    if(head==end || head.next==end) return head;
    ListNode lhead = head, utail = head, p = head.next;
    while (p != end){
        ListNode next = p.next;
        if(p.val < head.val){ // 如果p的值小于head的值,头插
            p.next = lhead;
            lhead = p;   // 维护小于head部分的头指针
        }
        else { // 如果p的值大于等于head的值,尾插
            utail.next = p;
            utail = p;   // 维护大于等于head部分的尾指针
        }
        p = next;
    }
    utail.next = end;
    ListNode node = quickSort(lhead, head);  // 小于等于head的部分
    head.next =  quickSort(head.next, end);  // 大于等于head的部分
    return node;
}
链表快排

 

快慢指针查找中间节点:

快指针的速度比慢指针的快一倍,当快指针走到尾部的时候,慢指针正好走到中间

public void findMiddleData(Node head){
    // 快慢指针,慢指针每次走一格,快指针每次走两格
    Node p = this.head, q = this.head;
    while(p!=null && p.next!=null){
        p = p.next.next;
        q = q.next;
    }
    System.out.println("Middle data: " + q.data);
}

快慢指针查找链表是否有环,并且找到环的入口:

为什么快慢指针一定会相遇

// 链表指定位置成环
// 要增加一个Node类型的成员变量记录尾结点,通过尾结点指向其他节点来成环

// 快慢指针查找链表是否有环
// 快指针的速度可以不是 2 而是其他的 3,4 之类的吗?  比较麻烦
public boolean findLoop(Node head){
    Node fast = head, slow = head;
    while(fast!=null && fast.next!=null){
        fast = fast.next.next;
        slow = slow.next;
        if(fast == slow) {return true;
        }
    }return false;
}

// 快慢指针找环的入口,相遇后慢指针再走a,起始点指针再走a,就是环的入口
public Node findLoopPort(Node head){
    Node fast = head, slow = head;
    while(fast!=null && fast.next!=null) {
        fast = fast.next.next;
        slow = slow.next;
        if(fast==slow)
            break;
    }
    slow = head;
    while(slow != fast){
        slow = slow.next;
        fast = fast.next; 
    }
    return slow;
}

 

查找倒数第K个元素(双指针):

用两个指针a、b,a先走K步,然后a,b同时同速走,a和b之间的差距为K,a到了尾部之后,b就到了倒数第K个元素;

  删除倒数第K个元素leetcode

 

删除排序链表中的重复元素

合并K路有序链表

 

从尾到头打印单链表(递归):

public void printLinkReversed(Node node){
    if(node!=null){
        // node = node.next; // 不能用这种,区别就是最后一个null会被下面的node.data所使用
        // printLinkReversed(node);
        printLinkReversed(node.next);
        System.out.println(node.data);
    }
}
View Code

 

学习来源:

链表

链表的原理及java实现

 

posted @ 2021-09-08 21:19  菠萝机  阅读(60)  评论(0编辑  收藏  举报