數據結構_5:鏈表與遞歸

鏈表與遞歸

鏈表元素刪除問題的解答

問題描述:在鏈表[1, 2, 6, 3, 4, 5, 6] 中刪除值爲 6 的元素

  • ListNode.java 結構說明

    public class ListNode {
        int val;
        ListNode next;
    
        ListNode(int x) {val = x;}
    
        /**
        * 鏈表節點構造函數,自定義
        * @param arr
        */
        ListNode(int[] arr) {
            if (arr == null || arr.length == 0) {
                throw new IllegalArgumentException("arr can not be empty");
            }
            this.val = arr[0];
            ListNode cur = this;
            for (int i = 1; i < arr.length; i++) {
                cur.next = new ListNode(arr[i]);
                cur = cur.next;
            }
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            ListNode cur = this;
            while (cur != null) {
                sb.append(cur.val).append(" -> ");
                cur = cur.next;
            }
            sb.append("NULL");
            return sb.toString();
        }
    }
    
  • 常規方式,對鏈表的三個部分[頭、中、尾]進行分別處理

    public ListNode removeElement(ListNode head, int val) {
            // 鏈表頭部節點刪除
            while (head != null && head.val == val)
                head = head.next;
    
            // 鏈表尾部節點刪除
            if (head == null) {
                return null;
            }
    
            // 鏈表中間部分節點刪除
            ListNode prev = head;
            while (prev.next != null) {
                if (prev.next.val == val)
                    prev.next = prev.next.next;
                else
                    prev = prev.next;
            }
    
            return head;
        }
    }
    
  • 虛擬頭結點方式,使得每個鏈表節點均含有前置節點,改進代碼

     public ListNode removeElement(ListNode head, int val) {
        // 建立虛擬頭結點,保證鏈表中每一個節點前面均有節點
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
    
        // 鏈表節點刪除
        ListNode prev = dummyHead;
        while (prev.next != null) {
            if (prev.next.val == val)
                prev.next = prev.next.next;
            else
                prev = prev.next;
        }
    
        return dummyHead.next;
    }
    
  • 測試一下!

    public static void main(String[] args) {
        int[] arr = {1, 2, 6, 3, 4, 5, 6};
        ListNode res = new ListNode(arr);
        System.out.println(res);
        new Solution2().removeElement(res, 6);
        System.out.println(res);
    }
    ------------------------------------------
    1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6 -> NULL
    1 -> 2 -> 3 -> 4 -> 5 -> NULL
    

遞歸:計算機中的很重要的組件邏輯機制

  • 本質上,將原來的問題,轉化爲更小的同一問題,比如數組求和!

    SUM(arr[0...n-1]) = arr[0] + SUM(arr[1...n-1])

    SUM(arr[1...n-1]) = arr[1] + SUM(arr[2...n-1])

    SUM(arr[n-1...n-1]) = arr[n-1] + SUM(arr[]) = arr[n-1] + 0

  • 遞歸算法的組成

    • 求解最基本的問題
    • 把原問題轉化成更小的問題
  • 鏈表具有遞歸性質

    • 鏈表可以理解爲多個節點的連接體,也可以看做一個節點和一個鏈表的連接體。
    • NULL也是是一個最基本的鏈表。
    • 爲了方便理解,畫了一張圖!
      在這裏插入圖片描述
    • 根據圖,我們可以改寫代碼!
    public ListNode removeElementNew(ListNode head, int val) {
        // 基礎問題 one
        if (head == null) {
            return null;
        }
        // 處理子鏈表,分解問題 two
        head.next = removeElementNew(head.next, val);
        // 處理結果,若當前返回子鏈表滿足條件,便跳過節點 three
        return head.val == val ? head.next : head;
    }
    
    • 舉個栗子,現在有鏈表1, 2, 3,想要刪除元素2 上述方法是怎麼執行的呢?
      • step1 入參【1, 2, 3】 以 1 爲頭結點的鏈表
        • one head != null
        • two head.next = ? ,進入第一次遞歸,step2
      • step2 入參【2, 3】 以 2 爲頭結點的鏈表
        • one head != null
        • two head.next = ?,進入第二次遞歸,step3
      • step3 入參【3】以3 爲頭結點的鏈表
        • one head != null
        • two head.next = ? ,進入第三次遞歸,step4
      • step4 入參【NULL】,NULL 鏈表
        • one head == null,返回 null基本問題出現了!!!
      • step5 回到step3 two
        • two head.next = 【null】
        • three head.val == 2? head.next : head
        • return head ,此時鏈表爲【3】,回到step2 two
      • step6 回到step2 two
        • two head.next = 【3】
        • three head.val == 2? head.next : head, 此時條件滿足,爲true
        • return head.next,此時鏈表爲【3】,回到step1 two
      • step7 回到step1 two
        • two head.next = 【3】
        • three head.val == 2? head.next : head
        • return head,此時鏈表爲【1, 3】,回到step1 ,已經執行完了onetwothree,方法返回,結束。
  • 遞歸調用時有代價的:函數調用 + 系統棧空間(記錄當前執行位置、變量狀態、時間消耗),若不處理基礎問題,即沒有遞歸出口,方法執行一直佔內存,直到內存佔滿,或溢出,導致系統over了。一個算法必須總是在有限次的執行後結束,且每一步都能在有限時間內完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章