链表

1.概述

与数组相似,链表也是一种线性数据结构。链表有两种类型:单链表双链表

下面是单链表和双链表的两个示例:

 

 

2.单链表

单链表中的每个结点不仅包含值,还包含链接到下一个结点的引用字段。在大多数情况下,使用链表的头结点(第一个结点)来表示整个列表。单链表定义如下:

1 public class SinglyListNode {
2     int val;
3     SinglyListNode next;
4     SinglyListNode(int x) { val = x; }
5 }
View Code

按索引来访问元素平均要花费O(N)时间,其中N是链表的长度。

2.1 链表的插入:

如果我们想在给定的结点 prev 之后添加新值,我们应该:

1.使用给定值初始化新结点cur;

2.将 cur 的“next”字段链接到 prev 的下一个结点 next;

3.将 prev 中的“next”字段链接到 cur 。

 

在head和尾节点插入节点需要做特殊处理,这里不赘述。

2.2 链表的删除:

如果我们想从单链表中删除现有结点 cur,可以分两步完成:

1.找到 cur 的上一个结点 prev 及其下一个结点 next;

 

2.接下来链接 prev 到 cur 的下一个节点 next。

 

删除的关键是找到cur节点的prev节点和next节点。使用cur节点很容易找出next,但是,我们必须从头结点遍历链表,以找出 prev,它的平均时间是 O(N),其中 N 是链表的长度。因此,删除结点的时间复杂度将是 O(N)。

除了上边插入和删除的操作,一般还有get(int val) 范围位置是val的节点的值。下面例子,我自己实现了一个单链表:

package leet_pro.ds;

/**
 * @Title:MyLinkedList
 * @Description:  自实现单链表
 * 设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
 *
 * 在链表类中实现这些功能:
 *
 * get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
 * addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
 * addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
 * addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
 * deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
 *
 * 注意:
 * 所有val值都在 [1, 1000] 之内。
 * 操作次数将在  [1, 1000] 之内。
 * 请不要使用内置的 LinkedList 库。
 *
 *
 * @Author: natty
 * @Version: 1.0
 * @Date: 2020-02-09 13:25
 */
public class MyLinkedList {

    /**
     * 单链表数据结构
     */
    private class Node{
        private int val;
        private Node next;
        public Node(int nodeVal){
            val = nodeVal;
        }
    }

    //MyLinkedList的长度
    private int size;
    //MyLinkedList的头节点
    private Node virtualHead;


    /** Initialize your data structure here. */
    public MyLinkedList() {
        size = 0;
        //虚拟头指针
        virtualHead = new Node(-1);
    }

    /** Get the value of the index-th node in the linked list. If the index is invalid, return -1.
     *  0 represents the first value of LinkedList
     *  1 represents the second value of LinkedList ...
     * */
    public int get(int index) {
        if (index < 0 || index >= size) return -1;
        Node cur = virtualHead.next;
        for (int i=0 ;i <index ; i++){
            cur = cur.next;
        }
        return cur.val;
    }

    /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
    public void addAtHead(int val) {
        addAtIndex(0,val);
    }

    /** Append a node of value val to the last element of the linked list. */
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }

    /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
    public void addAtIndex(int index, int val) {
        if (index > size) return;
        if (index < 0) addAtHead(val);   //index小于0在头指针前加。
        Node prev = virtualHead;
        for(int i=0;i<index;i++){
            prev = prev.next;
        }
        Node addNode = new Node(val);
        addNode.next = prev.next;
        prev.next = addNode;
        size++;
    }

    /** Delete the index-th node in the linked list, if the index is valid. */
    public void deleteAtIndex(int index) {
        if(index < 0 || index >= size) return;  //无效标记,坐标从0开始
        Node prev = virtualHead;
        for(int i=0;i<index;i++){
            prev = prev.next;
        }
        Node reNode = prev.next;
        prev.next = reNode.next;
        reNode.next = null;
        size --;
    }

    public String toString(){
        Node curr = virtualHead;
        if (size ==0) return "NULL";
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<size;i++){
            curr =curr.next;
            sb.append(curr.val + "->");
        }
        sb.append("NULL");
        return sb.toString();
    }

    public static void main(String[] args) {
        MyLinkedList linkedList = new MyLinkedList();
        linkedList.addAtHead(4);
        linkedList.get(1);
        System.out.println(linkedList);
        linkedList.addAtIndex(0,20);
        linkedList.addAtIndex(1,30);
        System.out.println(linkedList);
    }

}
View Code

 

2.3 常见的链表的操作:

下面从leetcode上导出的一些应用,来查看一些常用的链表操作:

1. 环链表:判断链表是否存在环,下面是一个存在环的链表的示例(head节点指向3):

 

环链表判断程序:

package leet_pro.al.node;

/**
 * @Title:CheckCircleNode
 * @Description:  https://leetcode-cn.com/problems/linked-list-cycle/
 *  判断一个链表是否是"环形链表":
 *  验证方法:快慢指针,指定2个指针,一个指针的滑动步长是2 ,一个指针的滑动步长是1 。
 *      如果链表有环的话,这2个指针一定会相遇。
 *          如果快慢指针相遇就是有环的链表,如果步幅大的链表到达终点的话,就表示没有环。
 *
 * @Author: natty
 * @Version: 1.0
 * @Date: 2020-02-10 12:06
 */
public class CheckCircleNode {

    class ListNode {
      int val;
      ListNode next;
      ListNode(int x) {
          val = x;
          next = null;
      }
    }

    public class Solution {
        public boolean hasCycle(ListNode head) {
            if (head == null || head.next == null){
                return false;
            }
            ListNode stepOne = head.next;
            ListNode stepTwo = head.next;
            while (stepOne != null && stepTwo != null){
                stepTwo = stepTwo.next;
                if (stepTwo == null) return false;
                if (stepOne == stepTwo) return true;
                stepOne = stepOne.next;
                stepTwo = stepTwo.next;
            }
            return false;
        }
    }

    public static void main(String[] args) {

    }
}
View Code

 

posted @ 2020-02-08 10:41  葛洪俊  阅读(238)  评论(0)    收藏  举报