• 链表及常见问题


    【定义】链表是一种递归的数据结构,它或者为空(null),或者指向一个节点(node)的引用,这个节点含有泛型的元素和一个指向另一条链表的引用。

        public class Node {
            Item item;
            Node next;
        }

    【基本操作】为了维护一个链表,我们需要对链表:创建、插入、删除、遍历等四种操作。

    1. 创建(构造)链表:根据链表定义,我们只需要一个Node类型的变量就能表示一条链表,只要保证它的值是null或者指向另一个Node对象且该对象的next域指向了另一条链表即可。比如按一下代码创建链表:

            Node first = new Node();
            Node second = new Node();
            Node third = new Node();
    
            first.item = "to";
            second.item = "be";
            third.item = "or";
    
            first.next = second;
            second.next = third;

    这时third是一个含单元素的链表,second是一个含双元素(second、third)的链表,first是一个含三个元素(first、second、third)的链表。

    2. 在链表中插入元素最容易做到的地方是表头,他所需的时间与表的长度无关。简易代码:

        public Node insertFirst() {
            Node oldfirst = first;
            first = new Node();
            first.item = "not";
            first.next = oldfirst;
            return first;
        }

    3. 接下来你可能需要删除一条链表的首节点,这个操作更简单,只需返回表头节点的next节点即可。简易代码:

        public Node deleteFirst() {
            if (isEmpty()) throw new NullPointerException();
            return first.next;
        }

    4. 如何在表尾插入节点,要完成这一任务,我们需要一个指向链表最后一个节点的链接,因为该节点的链接必须被修改并指向一个含有新元素的新节点。我们不能在链接代码中草率地决定维护一个额外的链接,因为每个修改链表的操作都需要添加检查是否要修改该变量(以及作出相应修改)的代码。比如链表只含有一个元素或者链表为空链表时。简易代码:

        public Node getTail() {
            Node p = first;
            while(p.next != null) p = p.next;
            return p;
        }

    5. 如何删除尾节点,last链接帮不上忙,只能遍历整个链表找到指向last链接的节点。简易代码:

        public void deleteTail() {
            Node p = first, q;
            while(p.next != null) {q = p; p = p.next;}
         q.next = p.next.next;
    }

    6. 删除指定的节点。

        public void Node deleteNode(Node p){
            if(isEmpty()) throw new NoSuchElementException();
            if(first.next == null) {
                if (first == p) first = null;
                else throw new NoSuchElementException();
            }
            Node pPre, q = first;
            boolean find = false;
            while(q != null) {
                if (q == p) { find = true; break;}
                pPre = q;
                q = q.next;
            }
            if (!find) throw new NoSuchElementException();
            pPre = q.next;
        }

    7. 在指定节点前插入一个新节点。

        public void Node insertNode(Node p, Node q){
            if(isEmpty()) {first = last = p; }
            if (p == first) {
                q.next = first;
                first = q;            
            } else {
                Node r = findPrior(p);
                if (r == null) throw new NoSuchElementException();
                q.next = p;
                r.next = q;            
            }
        }

    【常见问题】

    1. 反转链表:输入一个链表的头结点,反转该链表,并返回反转后链表的头结点。

    首先要画清楚翻转的操作图(下图为一次翻转操作,遍历全链表即可完成整个链表的反转):

    然后依图即可写出代码: 

        public void reverse() {
            if (first == null || first.next == null) return;
            Node<Item> pPre = null, pNext, pCur = first;
            while(pCur != null)  
            {  
                pNext = pCur.next;             
                pCur.next = pPre;  
                pPre = pCur;         
                pCur = pNext;        
            }
            first = pPre;
        }

    2. 判断链表是否有环。

    这是一个经典的快慢指针问题。通过两个指针,分别从链表的头节点出发,一个每次向后移动一步,另一个移动两步,因为两个指针移动速度不一样,如果存在环,那么两个指针一定会在环里相遇。

    一个有环链表如下图示:

    相关代码: 

        public boolean hasCircle()
        {
            if (first == null || first.next == null) return false;
            Node fast, slow;
            fast = first;
            slow = first;
            while(fast != null && fast.next!= null)
            {
                if (fast.next.next == null) return false;
                fast = fast.next.next;
                slow = slow.next;
                if(fast == slow) return true;
            }
            return false;
        }

    3. 判断有环链表的入口。

    方法一: 暴力求解。先通过快慢指针,找到相遇的节点。遍历此节点得到整个环的元素。然后从链表头再次出发,每走一步与环的元素进行比较。

    方法二:假定起点p到环入口点s的距离为a,fast和slow的相交点t与环入口点s的距离为b,环的周长为P,当fast和slow第一次相遇的时候,假定slow走了n 步。那么参考下图有:

     

    slow走的长度:a+b = n;

    fast走的长度:a + b + k*P = 2*n;

    fast比slow多走了k圈环路,总路程是slow的2倍。

    根据上述公式可以得到: n = k*P = a+b,

    如果从相遇点t开始,再走 k*P-b (= a)步的话,亦即从s位置走了(k*P -b) + b步,t可以走到s的位置。

    算法:设fast回到最初的位置p,每次行进一步,这样fast走了a步的时候,t也走到了s,两者相遇。

    相关代码: 

        public Node findLoopNode()
        {
            if (first == null || first.next == null) return null;    
            Node fast, slow;
            fast = first;
            slow = first;
            while(fast != null && fast.next!= null)
            {
                if (fast.next.next == null) return null;
                fast = fast.next.next;
                slow = slow.next;
                if(fast == slow) break;
            }
            if(fast != slow) return null;
    
            fast = first;                
            while(fast != slow)          
            {
                fast = fast.next;
                slow = slow.next;
            }
            return fast;
        }

    4. 求链表相交。

    方法一:可转化为环问题求解,将一个链表链接到另一个链表后,通过快慢指针是否相交求解。

    方法二:链表相交则其尾部一定一致,因此可以通过判断尾节点是否一致判断。

    相关代码略。

    5. 两个有序链表合并为一个有序链表。

    原理与归并排序merge操作一致,代码略。

  • 相关阅读:
    键盘移动小div(js原生)
    递归好理解,可是不好用啊
    jsonp
    闭包
    json
    来个小例子(ajax)
    ajax小demo
    制作H5响应式页面注意事项、微信二次分享
    Button按钮--inject与provide
    webpack基本打包配置流程
  • 原文地址:https://www.cnblogs.com/notTao/p/6476395.html
Copyright © 2020-2023  润新知