zoukankan      html  css  js  c++  java
  • 【数据结构&算法】07-链表技巧&参考源码


    前言

    1. 指针或引用的含义
    2. 指针丢失和内存泄漏
    3. 哨兵简化实现难度
    4. 边界条件处理
    5. 多看代码多练

    李柱明博客:https://i.cnblogs.com/posts/edit-done;postId=15487326

    指针或引用的含义

    指针和引用都是一个意思,都是存储所指对象的内存地址。

    理解指针非常重要,对于后期使用指针、函数传参等等都发挥着理论指导作用。

    指针:

    • 变量的地址即为该变量的指针 ,理解为地址(指针)指向了该变量,也就是通过地址(指针)可以找到该变量。
    • 如果一个变量专门用来存放另一个变量的地址(指针),则称它为指针变量
    • 如一个 32bit 系统中,指针的值只是 4 个 byte 的数据而已。

    指针丢失和内存泄漏

    指针丢失&内存泄漏例子:

    • 如单向链表,插入节点时,注意指针的更新:

    • // 错误示范
      p->next = x;  // 将 p 的 next 指针指向 x 结点;
      x->next = p->next;  // 将 x 的结点的 next 指针指向 b 结点;
      
    • 上述代码中一个错误点就是:p->netx 已经由原来的 b 更新为 x 了,到第二行代码时原意为 x 的下一个想指向 b 的,但是指针丢失了,导致 x->next 指向了 x 本身,链表后面的节点全部丢失。后面丢失的节点就是内存泄漏了。

    • 注意:

      • 实时留意指针的状态。
      • 在操作链表时,必要时可以备份节点。如在删除单向链表的某个节点时,备份下前一个节点。
      • 删除节点时,释放节点内存。若有对应的指针变量,记得赋空值。

    注意野指针:

    • 指针变量创建时记得赋空值。
    • 释放内存时,记得赋空值。

    哨兵简化实现难度

    哨兵为了处理边界问题。如首节点。

    哨兵不参与逻辑业务。

    编程建议:链表尽量弄个链表句柄,方便管理。首节点的指针可以当做链表的句柄使用。

    在链表中,第一个节点都需要特殊处理,如删除单向链表的第一个节点,就得把指向该链表的指针更新为第二个节点。

    这样每次操作链表都需要检查是否是第一个节点,如果是还要特殊处理,显得繁琐。

    所以我们固定一个首节点,实际数据的节点从第二个节点开始。这样就不用管链表是否为空。

    首节点还可以记录链表的一些数据。

    首节点的指针可以当做链表的句柄使用。

    含首节点的链表交带头链表,相反叫不带头链表。

    参考以下两段代码:

    代码一:

    // 在数组 a 中,查找 key,返回 key 所在的位置
    // 其中,n 表示数组 a 的长度
    
    int find(char* a, int n, char key) {
      // 边界条件处理,如果 a 为空,或者 n<=0,说明数组中没有数据,就不用 while 循环比较了
      if(a == null || n <= 0) {
        return -1;
      }
    
      int i = 0;
      // 这里有两个比较操作:i<n 和 a[i]==key.
      while (i < n) {
        if (a[i] == key) {
          return i;
        }
        ++i;
      }
      return -1;
    }
    

    代码二:

    // 在数组 a 中,查找 key,返回 key 所在的位置
    // 其中,n 表示数组 a 的长度
    // 我举 2 个例子,你可以拿例子走一下代码
    // a = {4, 2, 3, 5, 9, 6}  n=6 key = 7
    // a = {4, 2, 3, 5, 9, 6}  n=6 key = 6
    
    int find(char* a, int n, char key) {
    
      if(a == null || n <= 0) {
        return -1;
      }
    
      // 这里因为要将 a[n-1] 的值替换成 key,所以要特殊处理这个值
      if (a[n-1] == key) {
        return n-1;
      }
    
      // 把 a[n-1] 的值临时保存在变量 tmp 中,以便之后恢复。tmp=6。
      // 之所以这样做的目的是:希望 find() 代码不要改变 a 数组中的内容
      char tmp = a[n-1];
    
      // 把 key 的值放到 a[n-1] 中,此时 a = {4, 2, 3, 5, 9, 7}
      a[n-1] = key;
    
      int i = 0;
      // while 循环比起代码一,少了 i<n 这个比较操作
      while (a[i] != key) {
        ++i;
      }
    
      // 恢复 a[n-1] 原来的值, 此时 a= {4, 2, 3, 5, 9, 6}
      a[n-1] = tmp;
    
      if (i == n-1) {
        // 如果 i == n-1 说明,在 0...n-2 之间都没有 key,所以返回 -1
        return -1;
      } else {
        // 否则,返回 i,就是等于 key 值的元素的下标
        return i;
      }
    }
    

    在字符串 a 很长的时候,执行效率更高的是代码二。

    因为代码二的字符串检索是比代码一少了一个判断。

    • 代码一:O(2n)

    • 代码二:O(n)

      • 代码一比代码 二多了个字符串最大限度遍历限制。
      • 备份字符串最后一个字符,并把其改为遍历结束标志。
      • 完成遍历后再恢复该字符。

    注意:上面代码只是给个例子,实际开发中,若不追求极致效率,为了代码可读性,不建议上面这个代码的实现。

    边界条件处理

    在处理链表时,先用大脑走一遍,主要是边界问题,如:

    • 链表为空时,代码是否正常工作?
    • 链表只有一个节点时,代码是否正常工作?
    • 链表有两个节点时,代码是否正常工作?
    • 处理链表第一个节点或最后一个节点时,代码是否正常工作?
  • 相关阅读:
    学习笔记(二)Eclipse设置 Servlet配置及初始化参数读取 及Servlet应用
    html实例
    如何使用JDBC调用存储在数据库中的函数或存储过程 */
    如何使用 JDBC 调用存储在数据库中的函数或存储过程
    c3p0数据库连接池使用--创建JDBCTools 公共类
    创建DBCP数据源
    使用Batch批量添加数据
    sqlserver事务隔离级别
    事务处理
    得到数据库自动生成的主键值
  • 原文地址:https://www.cnblogs.com/lizhuming/p/15487326.html
Copyright © 2011-2022 走看看