• 01玩转数据结构_05_链表和递归


    链表回顾:

    leetcode 题目(No.203 移除链表元素):

    不使用虚拟头节点:

     1 class Solution {
     2     public ListNode removeElements(ListNode head, int val) {
     3         //1,head.val 就是应该删除的节点
     4 //        if(head != null && head.val == val ){
     5         while(head != null && head.val == val ){ //之所以使用while是因为可能有多个。
     6             ListNode delNode = head;
     7             head = delNode.next;
     8             delNode.next = null;
     9         }
    10         //2,head.val 不是应该删除的节点
    11         if(head ==null){
    12             return null;
    13         }
    14         ListNode prePtr = head;   //删除时应该要知道前一个节点。
    15         while (prePtr.next != null){
    16             if(prePtr.next.val == val){
    17                 ListNode delNode = prePtr.next;
    18                 prePtr.next = delNode.next;
    19                 delNode.next = null;
    20             }else{
    21                 prePtr = prePtr.next;
    22             }
    23         }
    24         return head;
    25     }
    26 }
    View Code

    使用虚拟头节点:

     1 class Solution {
     2     public ListNode removeElements(ListNode head, int val) {
     3         //使用虚拟头节点  将代码处理逻辑统一。
     4         ListNode dummyNode = new ListNode(0);
     5         dummyNode.next = head;  
     6         ListNode prePtr = dummyNode;
     7         while(prePtr.next != null){
     8             if(prePtr.next.val == val){
     9                 ListNode delNode = prePtr.next; 
    10                 prePtr.next = delNode.next; 
    11                 delNode.next = null;     
    12             }else{
    13                 prePtr = prePtr.next;  
    14             }
    15         }
    16         return dummyNode.next;  
    17         
    18      
    19     }
    20 }
    使用虚拟头节点 将代码处理逻辑统一。

    链表和递归:

    递归是一个很重要的概念,它甚至是 初级程序员和高级程序员 拉开距离的分水岭。

    递归基础和递归的宏观语意

    它的本质

    将原来的问题,转化为更小的同一问题

    下面我们用递归来实现一下 数组求和。

     1 package cn.zcb.demo01;
     2 
     3 public class MySum {
     4     public static void main(String[] args) {
     5         int [] arr = {1,2,3,4,5,6,7,8,9,10};
     6         int ret = sum(arr);
     7         System.out.println(ret);
     8 
     9     }
    10     public static int sum(int []arr){
    11         //为用户设计
    12         return sum(arr,0); // 初始索引是0 
    13     }
    14     private static int sum(int []arr,int leftIdx){
    15         //真正的递归函数,它额外需要一个左边界的索引 。
    16         //终止条件
    17         if(leftIdx == arr.length ){
    18             return 0;
    19         }
    20         //递归链条
    21         return arr[leftIdx] + sum(arr,leftIdx+1);
    22     }
    23 }
    递归 进行数组求和。

    上面递归分两处:

    1,终止条件。终止条件一般比较简单,

    2,递归链条。一般难的是下面的递归链表的创建(即 把原问题 转化成更小的问题)。

    其实有的时候,我们不必关系递归的具体每一步骤,

    而仅仅知道 该函数是干嘛的就行了(即递归函数的宏观语意)。然后,围绕它来构建终止条件  和 递归链条。  

    总结:宏观语意很重要,可以直接将 函数中调用自身的函数当做是已经解决的函数。

    链表的天然递归结构:

     所以,对于链表来说 ,绝大多数都是可以通过递归来进行解决的。

    下面使用递归来解决上面的LeetCode题目。

     1 class Solution {
     2     public ListNode removeElements(ListNode head, int val) {
     3         //终止条件
     4         if(head == null)
     5             return null;
     6         
     7         //递归链条 
     8         ListNode res = removeElements(head.next,val);  //假设 res 是已经清理好的链表。
     9         if(head.val == val){ //如果head 需要清理 直接返回 res
    10             return res;  
    11         }else{
    12             head.next = res;
    13             return head; 
    14         }
    15     }
    16 }
    把函数中调用自己函数的返回值当做是 已经解决好的答案。
     1 package cn.zcb.demo01;
     2 class Solution {
     3     public ListNode removeElements(ListNode head, int val) {
     4         //终止条件
     5         if(head == null)
     6             return null;
     7 
     8         //递归链条
     9         head.next  = removeElements(head.next,val); //假设函数的返回值是 已经清理好的链表。
    10         if(head.val == val){ //如果head 需要清理 直接返回head.next   
    11             return head.next;
    12         }else{
    13             return head;
    14         }
    15     }
    16 }
    version better
     1 package cn.zcb.demo01;
     2 class Solution {
     3     public ListNode removeElements(ListNode head, int val) {
     4         //终止条件
     5         if(head == null)
     6             return null;
     7 
     8         //递归链条
     9         head.next  = removeElements(head.next,val); //假设函数的返回值是 已经清理好的链表。
    10         return head.val == val?head.next:head;  
    11     }
    12 }
    再次优化。更加简洁,不过可读性就降低了

    递归运行的机制:递归的“微观”解读:

    就解决问题本身而言,我们只需要掌握 “宏观”语意,基本上问题都能解决。 

    下面我们也看下微观发生了什么!

    另一题:

    递归的代价:

    对于线性结构来说,我们是可以直接使用循环来解决的。所以,我们不能发现递归的很多好处。

    但是对于非线性结构(树啊,图啊之类的)来说,我们就会发现递归的好处。它的书写逻辑是清晰的。而且是简单的。

    递归算法的调试(程序中调试):

    上面是在图纸上写的。对于平时写程序来说,如果每个都写在纸上也挺浪费时间,虽然它的学习效果是最好的。

    其实,我们是完全可以通过打印输出进行递归调试 的 。

     1 package cn.zcb.demo01;
     2 class Solution {
     3     public ListNode removeElements(ListNode head, int val,int depth) { //depth 是递归的深度
     4         //终止条件
     5         //1,一进来就输出个 关于深度的信息。 这里用- 表示, - 表示一个深度。
     6         String depthString = generateDepthString(depth); //专门用来生成深度信息的函数
     7         System.out.print(depthString);
     8         System.out.println("Call: remove " + val + " in "+head);
     9 
    10         if(head == null) {
    11             //2, 结束递归时候的输出。
    12             System.out.print(depthString);
    13             System.out.println("0Return: "+head);
    14             return null;
    15         }
    16         //递归链条
    17         ListNode res = removeElements(head.next,val,depth+1);
    18         //3 处理完小问题之后输出一下。
    19         System.out.print(depthString);
    20         System.out.println("After remove "+val +": "+res);
    21 
    22         ListNode ret;
    23         if(head.val == val){
    24             ret = res;
    25         }else{
    26             head.next = res;
    27             ret = head;
    28         }
    29         System.out.print(depthString);
    30         System.out.println("1Return: "+ret);
    31         return ret;
    32     }
    33     private String generateDepthString(int depth){
    34         StringBuilder builder = new StringBuilder();
    35         for (int i=0;i<depth;i++){
    36             builder.append("-");
    37         }
    38         return builder.toString();
    39 
    40 
    41     }
    42     public static void main(String[] args) {
    43         int [] nums = {1,2,6,3,4,5,6};
    44         ListNode head = new ListNode(nums);
    45         System.out.println(head);
    46 
    47         //构建链表完毕,下面为测试 removeElements() 代码
    48         ListNode res =  (new Solution()).removeElements(head,6,0);
    49         System.out.println(res);
    50     }
    51 
    52 }
    Solution.java
     1 package cn.zcb.demo01;
     2 
     3 public class ListNode {
     4     public int val;
     5     public ListNode next;
     6     public ListNode(int val){
     7         this.val = val;
     8     }
     9     //int [] 数组作为参数的构造器
    10     public ListNode(int [] arr){
    11         if(arr == null || arr.length ==0 ){
    12             throw new IllegalArgumentException("数组不能为空 !");
    13         }
    14         this.val  = arr[0];
    15 
    16         ListNode temp = this;
    17         for (int i=1;i<arr.length;i++){
    18             temp.next = new ListNode(arr[i]);
    19             temp = temp.next;
    20         }
    21     }
    22     //重写toString()
    23     @Override
    24     public String toString(){
    25         StringBuilder builder = new StringBuilder();
    26         ListNode temp =this;
    27         while (temp != null){
    28             builder.append(temp.val +"->");
    29             temp = temp.next;
    30         }
    31         builder.append("null");
    32         return builder.toString();
    33     }
    34 }
    ListNode.java

    原本只有四行的代码,被我们整成了 这么多行, 它说明 牛逼的算法可能是需要上百上千的代码才可以透彻的理解的。才能潇洒的写出那四行代码的。

    更多的 和 链表相关的话题:

    链表的形态了解:

    1,双链表:

    我们之前的问题,在对队列尾端删除的时候,即使有tail ,也需要O(n) 的时间复杂度。

    其实,我们可以用双链表来 解决这个事情。

    不过,这样带给我们的代价是,由于有两个指针,所以维护起来会比较麻烦。

    当然,双链表也可以通过使用 虚拟头节点 去使得处理逻辑统一。

    2,循环链表:

    进一步,有循环链表(双向),它可避免使用tail 指针了就。

    链表也可以用数组来实现:

    3,数组链表:

    至此,之前的内容都是关于线性的。下面将进入非线性。首先的就是二分搜索树。

  • 相关阅读:
    CSS的三种定位方式介绍(转载)
    CSS背景颜色透明
    去除网页滚动条的方法
    es6
    Android复习
    caculater
    字符流
    字节流
    File类
    泛型继承
  • 原文地址:https://www.cnblogs.com/zach0812/p/11872101.html
Copyright © 2020-2023  润新知