15、递归学习与代码优化:利用惰性和缓存技术

递归学习与代码优化:利用惰性和缓存技术

在编程领域,递归和代码优化是提升程序性能和可维护性的重要手段。递归能让我们更便捷地迭代序列,而代码优化则能让程序运行得更快、更高效。本文将详细探讨递归的相关知识,以及如何利用惰性和缓存技术对代码进行优化。

递归的基本概念

递归是一种编程技巧,它允许函数调用自身。在递归中,有一个基本情况(base case),它定义了递归链的结束条件。传统的递归模型中,递归调用先执行,然后返回值,最后计算结果,结果要等递归调用结束后才会显示。而尾递归在递归之后不会进行任何操作,它有两种风格:APS 和 CPS。除了直接递归,还有间接递归,间接递归涉及至少两个函数。

我们还可以将递归应用到函数式编程中,使用 Aggregate LINQ 运算符。以下是一个使用 Aggregate 方法连接字符串的示例:

strAll = "The quick brown fox jumps over the lazy dog";
stringAggregate = str;

从上述流程可以看出,我们可以使用 Aggregate 方法连接 listString 中的所有字符串,这证明了不仅 int 数据类型可以处理,string 数据类型也可以处理。

代码优化:利用惰性和缓存技术

为了让代码更高效,我们可以使用惰性和缓存技术。惰性技术包括惰性枚举、惰性求值、非严格求值和惰性初始化,而缓存技术则可以通过预计算和记忆化来缓存昂贵的资源。

惰性枚举

在 .NET 框架中,有一些枚举数据集合的技术,如数组和 List ,但它们是急切求值的。数组需要先定义大小,然后填充分配的内存才能使用;List 采用了数组机制,不过它比数组更容易扩展大小。

相反,.NET 框架中的 IEnumerable 接口可以进行惰性求值。虽然数组和 List 实现了 IEnumerable 接口,但由于需要填充数据,它们仍然是急切求值的。IEnumerable 接口只有一个方法:GetEnumerator(),该方法返回 IEnumerator 数据类型。IEnumerator 类型有三个方法和一个属性:
- Current:存储枚举器当前位置的集合元素。
- Reset():将枚举器设置到集合第一个元素之前的初始位置(索引通常为 -1)。
- MoveNext():将枚举器移动到下一个集合元素。
- Dispose():释放、释放或重置非托管资源。

下面我们通过斐波那契算法来证明 IEnumerable 是惰性求值的。斐波那契算法通过将前两个元素相加来生成序列,其公式为:$F_n = F_{n-1} + F_{n-2}$,计算该算法的前两个数字可以是 0 和 1 或 1 和 1。

以下是实现斐波那契数列的代码:

public partial class Program
{
  public class FibonacciNumbers
    : IEnumerable<Int64>
  {
    public IEnumerator<Int64> GetEnumerator()
    {
      return new FibEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
      return GetEnumerator();
    }
  }

  public class FibEnumerator
    : IEnumerator<Int64>
  {
    public FibEnumerator()
    {
      Reset();
    }

    public Int64 Current { get; private set; }
    Int64 Last { get; set; }
    object IEnumerator.Current
    {
      get
      {
        return Current;
      }
    }
    public void Dispose()
    {
      ; // Do Nothing
    }
    public bool MoveNext()
    {
      if (Current == -1)
      {
        Current = 0;
      }
      else if (Current == 0)
      {
        Current = 1;
      }
      else
      {
        Int64 next = Current + Last;
        Last = Current;
        Current = next;
      }
      return true;
    }
    public void Reset()
    {
      Current = -1;
    }
  }

  private static void GetFibonnacciNumbers(
    int totalNumber)
  {
    FibonacciNumbers fibNumbers =
      new FibonacciNumbers();
    foreach (Int64 number in
      fibNumbers.Take(totalNumber))
    {
      Console.Write(number);
      Console.Write("\t");
    }
    Console.WriteLine();
  }
}

由于 FibonacciNumbers 类会枚举无限数量,我们需要使用 Take() 方法来避免无限循环。例如,要枚举 40 个数字,可以调用:

GetFibonnacciNumbers(40);

因为 IEnumerable 是惰性求值的,只有在需要时才会调用 MoveNext() 方法计算结果,否则代码会陷入无限循环。

惰性求值

惰性求值的一个简单例子是处理两个布尔语句并进行比较。以下是一个演示惰性求值的代码:

public partial class Program
{
  private static MemberData GetMember()
  {
    MemberData member = null;
    try
    {
      if (member != null || member.Age > 50)
      {
        Console.WriteLine("IF Statement is TRUE");
        return member;
      }
      else
      {
        Console.WriteLine("IF Statement is FALSE");
        return null;
      }
    }
    catch (Exception e)
    {
      Console.WriteLine("ERROR: " + e.Message);
      return null;
    }
  }
}

public class MemberData
{
  public string Name { get; set; }
  public string Gender { get; set; }
  public int Age { get; set; }
}

在上述代码中,使用 || 运算符时,如果 member 为 null,访问 member.Age 会抛出异常。而使用 && 运算符时,当第一个表达式 member != null 为 FALSE 时,编译器不会再计算 member.Age > 50 表达式,从而避免了异常的抛出。

public partial class Program
{
  private static MemberData GetMemberANDOperator()
  {
    MemberData member = null;
    try
    {
      if (member != null && member.Age > 50)
      {
        Console.WriteLine("IF Statement is TRUE");
        return member;
      }
      else
      {
        Console.WriteLine("IF Statement is FALSE");
        return null;
      }
    }
    catch (Exception e)
    {
      Console.WriteLine("ERROR: " + e.Message);
      return null;
    }
  }
}
非严格求值

有些人可能认为惰性求值和非严格求值是同义词,但实际上不是。在惰性求值中,如果某个表达式不需要,则会忽略其求值;而在非严格求值中,会应用求值的缩减。

以下是区分严格求值和非严格求值的代码:

public partial class Program
{
  private static int OuterFormula(int x, int yz)
  {
    Console.WriteLine(
      String.Format(
        "Calculate {0} + InnerFormula({1})",
        x,
        yz));
    return x * yz;
  }
  private static int InnerFormula(int y, int z)
  {
    Console.WriteLine(
      String.Format(
        "Calculate {0} * {1}",
        y,
        z
        ));
    return y * z;
  }

  private static void StrictEvaluation()
  {
    int x = 4;
    int y = 3;
    int z = 2;
    Console.WriteLine("Strict Evaluation");
    Console.WriteLine(
      String.Format(
        "Calculate {0} + ({1} * {2})",x, y, z));
    int result = OuterFormula(x, InnerFormula(y, z));
    Console.WriteLine(
      String.Format(
        "{0} + ({1} * {2}) = {3}",x, y, z, result));
    Console.WriteLine();
  }

  private static int OuterFormulaNonStrict(
    int x,
    Func<int, int, int> yzFunc)
  {
    int y = 3;
    int z = 2;
    Console.WriteLine(
      String.Format(
        "Calculate {0} + InnerFormula ({1})",
        x,
        y * z
        ));
    return x * yzFunc(3, 2);
  }

  private static void NonStrictEvaluation()
  {
    int x = 4;
    int y = 3;
    int z = 2;
    Console.WriteLine("Non-Strict Evaluation");
    Console.WriteLine(
      String.Format(
        "Calculate {0} + ({1} * {2})",x, y, z));
    int result = OuterFormulaNonStrict(x, InnerFormula);
    Console.WriteLine(
      String.Format(
        "{0} + ({1} * {2}) = {3}",x, y, z, result));
    Console.WriteLine();
  }
}

在严格求值中,先计算 (3 * 2) 的结果,再将其加到 4 上;而在非严格求值中,先处理 + 运算符,再处理内部公式 (y * z),求值从外到内进行。

惰性初始化

惰性初始化是一种优化技术,它将对象的创建推迟到使用时。C# 4.0 引入了 Lazy 类,可以用于惰性初始化对象。以下是演示惰性初始化的代码:

public partial class Program
{
  private static void LazyInitName(string NameOfPerson)
  {
    Lazy<PersonName> pn =
      new Lazy<PersonName>(
        () =>
          new PersonName(NameOfPerson));
    Console.WriteLine(
      "Status: PersonName has been defined.");
    if (pn.IsValueCreated)
    {
      Console.WriteLine(
        "Status: PersonName has been initialized.");
    }
    else
    {
      Console.WriteLine(
        "Status: PersonName hasn't been initialized.");
    }
    Console.WriteLine(
      String.Format(
        "Status: PersonName.Name = {0}",
        (pn.Value as PersonName).Name));
    if (pn.IsValueCreated)
    {
      Console.WriteLine(
        "Status: PersonName has been initialized.");
    }
    else
    {
      Console.WriteLine(
        "Status: PersonName hasn't been initialized.");
    }
  }
}

public class PersonName
{
  public string Name { get; set; }
  public PersonName(string name)
  {
    Name = name;
    Console.WriteLine(
      "Status: PersonName constructor has been called."
      );
  }
}

调用 LazyInitName(“Matthew Maxwell”) 时,我们可以看到 PersonName 对象在定义时并未初始化,只有在访问其 Name 属性时才会调用构造函数进行初始化。

惰性的优缺点

使用惰性技术有以下优点:
- 无需为不使用的功能支付初始化时间。
- 程序执行更高效,因为在函数式编程中,执行顺序有时不如命令式编程重要。
- 促使程序员编写更高效的代码。

然而,惰性也有一些缺点:
- 应用程序的流程难以预测,有时会失去对应用程序的控制。
- 惰性代码的复杂性可能会导致簿记开销。

总结

通过本文的介绍,我们了解了递归的基本概念,以及如何利用惰性和缓存技术对代码进行优化。惰性技术包括惰性枚举、惰性求值、非严格求值和惰性初始化,它们可以让代码更高效地运行。同时,我们也认识到了惰性的优缺点,在实际编程中需要根据具体情况合理使用。

下面是一个简单的 mermaid 流程图,展示了惰性初始化的过程:

graph TD;
    A[定义 PersonName 对象] --> B{检查是否初始化};
    B -- 未初始化 --> C[访问 Name 属性];
    C --> D[调用构造函数初始化];
    D --> E[获取 Name 属性值];
    B -- 已初始化 --> E;

通过合理运用递归和代码优化技术,我们可以编写出性能更优、可维护性更好的程序。在实际开发中,我们应该根据具体需求选择合适的技术,以达到最佳的编程效果。

递归学习与代码优化:利用惰性和缓存技术

缓存技术:预计算和记忆化

除了惰性技术,缓存技术也是代码优化的重要手段。缓存技术主要通过预计算和记忆化来缓存昂贵的资源,避免重复计算,从而提高代码的执行效率。

预计算

预计算是指在程序运行之前或在需要使用某些数据之前,提前计算好这些数据并存储起来。这样,在后续需要使用这些数据时,就可以直接从缓存中获取,而不需要再次进行计算。例如,在计算斐波那契数列时,如果需要多次使用某个范围内的斐波那契数,我们可以提前计算并存储这些数,避免每次都重新计算。

以下是一个简单的预计算斐波那契数列的示例代码:

using System;
using System.Collections.Generic;

class FibonacciCache
{
    private static List<long> fibCache = new List<long> { 0, 1 };

    public static long GetFibonacci(int n)
    {
        while (fibCache.Count <= n)
        {
            int count = fibCache.Count;
            fibCache.Add(fibCache[count - 1] + fibCache[count - 2]);
        }
        return fibCache[n];
    }
}

class Program
{
    static void Main()
    {
        int n = 10;
        for (int i = 0; i < n; i++)
        {
            Console.WriteLine(FibonacciCache.GetFibonacci(i));
        }
    }
}

在上述代码中,我们创建了一个静态的 fibCache 列表来存储斐波那契数列。 GetFibonacci 方法会检查缓存中是否已经有需要的斐波那契数,如果没有,则计算并添加到缓存中。这样,后续再次需要相同的斐波那契数时,就可以直接从缓存中获取,避免了重复计算。

记忆化

记忆化是一种特殊的缓存技术,它主要用于缓存函数的返回值。当一个函数被多次调用,并且传入相同的参数时,记忆化会直接返回之前缓存的结果,而不是再次执行函数体。

以下是一个使用记忆化技术的示例代码:

using System;
using System.Collections.Generic;

class MemoizationExample
{
    private static Dictionary<int, int> memo = new Dictionary<int, int>();

    public static int Factorial(int n)
    {
        if (n == 0 || n == 1)
        {
            return 1;
        }
        if (memo.ContainsKey(n))
        {
            return memo[n];
        }
        int result = n * Factorial(n - 1);
        memo[n] = result;
        return result;
    }
}

class Program
{
    static void Main()
    {
        int n = 5;
        Console.WriteLine(MemoizationExample.Factorial(n));
    }
}

在上述代码中,我们使用一个 Dictionary 来存储已经计算过的阶乘结果。在 Factorial 函数中,首先检查 memo 字典中是否已经有当前参数 n 的结果,如果有,则直接返回;否则,计算结果并将其存储到 memo 字典中。

综合应用:递归与代码优化

在实际编程中,我们可以将递归与惰性、缓存技术结合起来,以实现更高效的代码。例如,在处理复杂的数据结构或算法时,递归可以帮助我们更简洁地表达逻辑,而惰性和缓存技术可以优化代码的性能。

以下是一个结合递归和缓存技术的示例,用于计算二叉树节点的和:

using System;

class TreeNode
{
    public int Value { get; set; }
    public TreeNode Left { get; set; }
    public TreeNode Right { get; set; }

    public TreeNode(int value)
    {
        Value = value;
        Left = null;
        Right = null;
    }
}

class TreeSumCalculator
{
    private static Dictionary<TreeNode, int> sumCache = new Dictionary<TreeNode, int>();

    public static int CalculateTreeSum(TreeNode node)
    {
        if (node == null)
        {
            return 0;
        }
        if (sumCache.ContainsKey(node))
        {
            return sumCache[node];
        }
        int sum = node.Value + CalculateTreeSum(node.Left) + CalculateTreeSum(node.Right);
        sumCache[node] = sum;
        return sum;
    }
}

class Program
{
    static void Main()
    {
        TreeNode root = new TreeNode(1);
        root.Left = new TreeNode(2);
        root.Right = new TreeNode(3);
        root.Left.Left = new TreeNode(4);
        root.Left.Right = new TreeNode(5);

        int treeSum = TreeSumCalculator.CalculateTreeSum(root);
        Console.WriteLine("Tree Sum: " + treeSum);
    }
}

在上述代码中,我们定义了一个二叉树节点类 TreeNode ,并使用递归函数 CalculateTreeSum 来计算二叉树节点的和。同时,我们使用一个 Dictionary 来缓存已经计算过的节点的和,避免重复计算。

总结与展望

通过本文的介绍,我们深入了解了递归的基本概念,以及如何利用惰性和缓存技术对代码进行优化。递归可以帮助我们更简洁地处理复杂的问题,而惰性和缓存技术则可以提高代码的执行效率。

在实际编程中,我们需要根据具体的问题和场景,合理选择和运用这些技术。例如,在处理大数据集合时,惰性枚举可以避免不必要的内存开销;在处理重复计算的问题时,缓存技术可以显著提高代码的性能。

以下是一个总结表格,对比了不同优化技术的特点:
| 优化技术 | 特点 | 适用场景 |
| ---- | ---- | ---- |
| 惰性枚举 | 延迟计算,按需生成数据 | 处理大数据集合,避免一次性加载所有数据 |
| 惰性求值 | 避免不必要的计算,根据条件决定是否计算 | 布尔表达式判断,避免异常 |
| 非严格求值 | 从外到内进行求值,减少不必要的中间计算 | 复杂表达式计算 |
| 惰性初始化 | 延迟对象创建,直到使用时才初始化 | 初始化开销大的对象 |
| 预计算 | 提前计算并存储数据,避免重复计算 | 多次使用相同数据的场景 |
| 记忆化 | 缓存函数返回值,避免重复执行函数体 | 多次调用相同参数的函数 |

同时,我们也应该认识到这些技术的局限性和潜在问题。例如,惰性技术可能会导致代码的执行流程难以理解,缓存技术可能会占用额外的内存空间。因此,在使用这些技术时,我们需要权衡利弊,确保代码的性能和可维护性达到最佳平衡。

下面是一个 mermaid 流程图,展示了综合应用递归、惰性和缓存技术的代码执行流程:

graph TD;
    A[开始] --> B{是否使用递归};
    B -- 是 --> C[递归处理问题];
    C --> D{是否使用惰性技术};
    D -- 是 --> E[延迟计算或初始化];
    E --> F{是否使用缓存技术};
    F -- 是 --> G[检查缓存是否有结果];
    G -- 有 --> H[返回缓存结果];
    G -- 无 --> I[计算结果并缓存];
    I --> J[返回结果];
    F -- 否 --> J;
    D -- 否 --> F;
    B -- 否 --> F;

通过不断学习和实践这些优化技术,我们可以编写出更加高效、健壮和可维护的代码,提升自己的编程能力和解决问题的能力。在未来的编程工作中,我们应该持续关注和探索新的优化技术和方法,以适应不断变化的需求和挑战。

【SCI复现】基于纳什博弈的多微网主体电热双层共享策略研究(Matlab代码实现)内容概要:本文围绕“基于纳什博弈的多微网主体电热双层共享策略研究”展开,结合Matlab代码实现,复现了SCI级别的科研成果。研究聚焦于多个微网主体之间的能源共享问题,引入纳什博弈理论构建双层优化模型,上层为各微网间的非合作博弈策略,下层为各微网内部电热联合优化调度,实现能源高效利用经济性目标的平衡。文中详细阐述了模型构建、博弈均衡求解、约束处理及算法实现过程,并通过Matlab编程进行仿真验证,展示了多微网在电热耦合条件下的运行特性共享效益。; 适合人群:具备一定电力系统、优化理论博弈论基础知识的研究生、科研人员及从事能源互联网、微电网优化等相关领域的工程师。; 使用场景及目标:① 学习如何将纳什博弈应用于多主体能源系统优化;② 掌握双层优化模型的建模求解方法;③ 复现SCI论文中的仿真案例,提升科研实践能力;④ 为微电网集群协同调度、能源共享机制设计提供技术参考。; 阅读建议:建议读者结合Matlab代码逐行理解模型实现细节,重点关注博弈均衡的求解过程双层结构的迭代逻辑,同时可尝试修改参数或扩展模型以适应不同应用场景,深化对多主体协同优化机制的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值