算法(第4版)练习题 1.1.27 的三种解法

news2024/11/23 15:45:31

本文列举了对于 算法 : 第4版 / (美) 塞奇威客 (Sedgewick, R.) , (美) 韦恩 (Wayne, K.) 著 ; 谢路云译. -- 北京 : 人民邮电出版社, 2012.10 (2021.5重印)(以下简称原书或书)中的练习题 1.1.27 的三种解法(C++ 实现),并对包含原书题中的递归方法在内的四种解法的执行时间进行了统计和对比。

◆ 要求

原书中的练习题 1.1.27 要求对如下二项分布递归过程中的值保存在数组中,

b(n,k,p) = 1.0  ( n == 0 && k == 0 )
b(n,k,p) = 0.0  (  n < 0 ||  k < 0 )
b(n,k,p) = (1.0-p) * b(n-1,k,p) + p * b(n-1,k-1,p)

◆ 解一

依然采用递归的方式,但使用二维数组保存中间结果。

如下代码所示,

static long double binomial1(int N, int K, long double p)    // #1
{
    long double x;

    long double** b = new long double*[N+1];               // #2
    long double* data = new long double[(N+1)*(K+1)];

    ...

    x = binomial1_impl(b, N, K, p);         // #3

    ...

    return x;
}

static long double binomial1_impl(long double** b, int N, int K, long double p)
{
    if (N == 0 && K == 0) return 1.0L;
    if (N < 0 || K < 0) return 0.0L;
    if (b[N][K] == -1) {
        ...                                 // #4
        b[N][K] = (1.0L-p) * binomial1_impl(b, N-1, K, p) + p * binomial1_impl(b, N-1, K-1, p);
    }
    return b[N][K];
}

保持对外的接口不变(#1),创建一个二维数组 b[0..N][0..K] 保存中间计算结果(#2),并将其传给算法实现(#3)。算法虽然还是用递归调用(#4),但由于中间结果保存在全局的二维数组中,不用频繁地压栈和弹栈去获取中间数据。此解法网络上也见于 [github] reneargento/algorithms-sedgewick-wayne 和 [github] aistrate/AlgorithmsSedgewick 。

◆ 解二

使用二维数组保持中间结果,但同时将递归改进为递推。若以横向为 x 轴,纵向为 y 轴,左上角为坐标原点,则坐标轴上的 (x,y) 点则代表二维数组的 b[y][x] 单元。

以 N = K = 4 为例,

    0   1   2   3   4

0   *   *   *   *   *  <-- * 代表待计算的单元

1   *   *   *   *   *

2   *   *   *   *   *

3   *   *   *   *   *

4   *   *   *   *   ?  <-- 最终计算结果的单元 (4,4)

仔细考察递归关系式的特点,b(-1,*,p) = 0.0, b(*,-1,p) = 0.0。由

b(0,1,p) = (1.0-p) * b(-1,1,p) + p * b(-1,0,p)
         = (1.0-p) * 0.0 + p * 0.0
         = 0.0
b(0,2,p) = (1.0-p) * b(-1,1,p) + p * b(-1,1,p)
         = (1.0-p) * 0.0 + p * 0.0
         = 0.0
...

可推论出,二维数组中的第 0 行中的所有单元(不含b[0][0])均为 0.0;由

b(1,0,p) = (1.0-p) * b(0,0,p) + p * b(0,-1,p)
         = (1.0-p) * 1.0 + p * 0.0
         = 1.0-p
b(2,0,p) = (1.0-p) * b(1,0,p) + p * b(1,-1,p)
         = (1.0-p) * (1.0-p) + p * 0.0
         = (1.0-p)^2
...

可推论出,二维数组中的第 0 列的单元为 (1.0-p)^y。

因为每个单元 b[n][k] 结果(n 代表行号,k 代表列号),依赖于 b[n-1][k-1] 和 b[n-1][k] 的结果。为了减少计算量,递推过程可仅用到二维数组的部分单元。笔者设置一个 G 点,将待计算单元的区域划分为 '#' 和 '*' 两部分,G 点在 '#' 区域中。分为以下三种情况,

第一种情况,N < K:(如 N = 4, K = 6)

    0   1   2   3   4   5   6

0   -   -   G   #   #   #   #  <-- G 点所在单元为 0.0

1   -   -   -   *   *   *   *  <-- '-' 代表不用计算的单元

2   -   -   -   -   *   *   *

3   -   -   -   -   -   *   *

4   -   -   -   -   -   -   ?  <-- 最终结果的存储单元

G 点为 b(0,K-N)。按照递推关系式容易推导出,'#' 和 '*' 区域均为 0.0,所以最终结果即 0.0。

第二种情况,N = k:(如 N = 6, K = 6)

    0   1   2   3   4   5   6

0   G   #   #   #   #   #   #  <-- G 点所在单元为 1.0

1   -   *   *   *   *   *   *

2   -   -   *   *   *   *   *

3   -   -   -   *   *   *   *

4   -   -   -   -   *   *   *

5   -   -   -   -   -   *   *

6   -   -   -   -   -   -   ?

G 点为 b(0,0)。按照递推关系式容易推导出,数组中 n = k 的单元为 p^n。所以最终结果即 p^N。

第三种情况,N > K:(如 N = 6, K = 4)

    0   1   2   3   4

0   #   #   #   #   #

1   #   #   #   #   #

2   G   #   #   #   #  <-- G 点所在单元为 (1.0-p)^2

3   -   *   *   *   *

4   -   -   *   *   *

5   -   -   -   *   *

6   -   -   -   -   ?

G 点为 b(N-K,0)。可先计算 '#' 区域中的单元,再计算 '*' 区域中的单元,得出最终结果。处理'#'区域时,为避免大量的数组下标越界判断,可以考虑先计算 0 行和 0 列的所有单元。

如下代码所示,

static long double binomial2(int N, int K, long double p)
{
    long double x;

    if (N < K) {                       // #1

        x = 0.0L;

    } else if (N == K) {                // #2

        x = powl(p, N);

    } else {                       // #3

        ...

        b[0][0] = 1.0L;

        // process '#' area                      // #4
        // calcuate [1..N-K][0]
        for (i = 1; i <= N-K; ++i)
            b[i][0] = powl(1.0L-p, i);
        // calcuate [0][1..K]
        for (j = 1; j <= K; ++j)
            b[0][j] = 0.0L;
        // calcuate [1..N-K][1..K]
        for (i = 1; i <= N-K; ++i)
            for (j = 1; j <= K; ++j)
                b[i][j] = (1.0L-p) * b[i-1][j] + p * b[i-1][j-1];

        // process '*' area                            // #5
        for (i = N-K+1; i <= N; ++i)
            for (j = i-(N-K); j <= K; ++j)
                b[i][j] = (1.0L-p) * b[i-1][j] + p * b[i-1][j-1];

        x = b[N][K];                       // #6

        ...

    }

    return x;
}

三条分支(#1、#2、#3)分别对应前述三种情况。在第三种情况下,再先处理 '#' 区域(#4),然后采用递推求值的方式处理 '*' 区域(#5),最后得到结果(#6)。

◆ 解三

此方法是从递推解法中引申出来了。进一步探究这个此二项分布的递归式,以 N = 4 且 K = 4 为例,

   0          1                2                    3                4

0  1.0


1  1.0-p      p


2  (1.0-p)^2  2*(1.0-p)*p      p^2


3  (1.0-p)^3  3*[(1.0-p)^2]*p  3*(1-p)*(p^2)        p^3


4  (1.0-p)^4  4*[(1.0-p)^3]*p  6*[(1.0-p)^2]*(p^2)  4*(1.0-p)*(p^3)  p^4

可以发现,从第 0 行到第 N 行的非零单元即“杨辉三角形”,第 n 行中的非零单元之和构成 [(1.0-p) + p]^k 的展开式。因此,解二中的第三种情况,可结合利用通项公式 C(N,K)*[(1.0-p)^(N-K)]*(p^K) 来解决。

如下代码所示,

static long double binomial3(int N, int K, long double p)
{
    long double x;

    if ...

    } else {

        x = combination(N, K) * powl(1.0L-p, N-K) * powl(p, K);

    }

    return x;
}

◆ 测试

编译并执行程序,

$ g++ -std=c++11 -o 27.out 27.cpp
$ ./27.out 10 5 0.25

为易于显示两者之间的差异,笔者选择了硬件配置偏低的测试环境。

  • 硬件配置:Raspberry Pi 3 Model B
    • Quad Core 1.2GHz 64bit
    • 1G RAM
    • 16G MicroSD
    • 100 Base Ethernet
  • 软件配置:Raspbian Stretch
    • g++ (Raspbian 6.3.0-18+rpi1+deb9u1) 6.3.0 20170516

测试并记录了 (N, K, p) 为 (10, 5, 0.25), (20, 10, 0.25), (40, 20, 0.25), (80, 40, 0.25), (100, 50, 0.25) 的情况下,原递归、解一、解二、解三执行时所消耗的时间。

结果如下图所示,

 

gist

对比可以看出,不同的解法在执行时间上的差异随着计算量的增加而逐步扩大。 

◆ 最后

完整的代码和测试结果请参考 [gitee] cnblogs/17328989 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/879861.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

朴素贝叶斯(右心室肥厚的辅助识别)

现在我们判断右心室是否肥厚通常的做法都是借助心电图来识别&#xff0c;左侧的是右心室肥厚的&#xff0c;右侧的是右心室厚度正常&#xff0c;那接下来就要按照给出的图像来处理特征&#xff0c;提取出正常组和肥厚组的不同特征。 根据上图我们可以得出通过图像提取出了年龄…

〔AI 绘画〕Stable Diffusion 之 VAE 篇

✨ 目录 &#x1f388; 什么是VAE&#x1f388; 开启VAE&#x1f388; 下载常见的VAE&#x1f388; 对比不同VAE生成的效果 &#x1f388; 什么是VAE VAE&#xff1a;是 Variational Auto-Encoder 的简称&#xff0c;也就是变分自动编码器可以把它理解成给图片加滤镜&#xff…

KCC@深圳开源读书会即将举办,来与行业大咖面对面交流

KCC&#xff0c;全称 KAIYUANSHE City Community&#xff08;中文&#xff1a;开源社城市社区&#xff09;是由开源社发起&#xff0c;旨在让开源社区在每个城市落地生根的地域性开源组织。 自2023年2月份发起以来&#xff0c;我们已经在南京、上海、深圳、北京、硅谷、新加坡、…

WebRTC | ICE详解

目录 一、Candidate种类与优先级 二、ICE策略 1. iceServers 2. iceTransportPolicy 三、P2P连接 1.Nat类型 &#xff08;1&#xff09;完全锥型NAT &#xff08;2&#xff09;IP限制锥型NAT &#xff08;3&#xff09;端口限制锥型NAT &#xff08;4&#xff09;对称…

HCIP的MPLS实验

题目 拓扑图 IP地址及环回配置 注&#xff1a;R2的g0/0/1口和g0/0/2口还有R4的g0/0/0口和g0/0/2口都先不配置IP&#xff0c;因为后面这些接口的IP需要放入vpn空间中 R1 <Huawei>sys Enter system view, return user view with CtrlZ. [Huawei]sysname r1 [r1]int l0 […

《Linux从练气到飞升》No.13 Linux进程状态

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

通过 Amazon SageMaker JumpStart 部署 Llama 2 快速构建专属 LLM 应用

来自 Meta 的 Llama 2 基础模型现已在 Amazon SageMaker JumpStart 中提供。我们可以通过使用 Amazon SageMaker JumpStart 快速部署 Llama 2 模型&#xff0c;并且结合开源 UI 工具 Gradio 打造专属 LLM 应用。 Llama 2 简介 Llama 2 是使用优化的 Transformer 架构的自回归语…

正确治理窜货的方法

所有违规行为的形成原因都离不开利益&#xff0c;窜货是如此&#xff0c;低价也是如此&#xff0c;窜货与低价又是一体的&#xff0c;因为有更多的利润空间&#xff0c;所以经销商或者非授权愿意承担风险去窜货&#xff0c;同样的也是因为窜货可以将价格压更低&#xff0c;上升…

冠达管理:险资最新重仓股曝光!加仓这些股票

随着上市公司半年报陆续发表&#xff0c;险资最新重仓持股状况也逐渐清晰。 到8月14日&#xff0c;在已发表2023年半年报的上市公司中&#xff0c;超越60家上市公司的前十大流通股东中呈现险资身影。 从职业来看&#xff0c;制造业成为险资的“心头好”。在险资重仓个股中&…

ClickHouse(十九):Clickhouse SQL DDL操作-1

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术&#xff0c;IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &…

学习助手(安卓)

首先&#xff0c;这是一款人工智能的学习软件&#xff0c;功能非常的强大&#xff0c;进入软件就能看见多种功能&#xff0c;它可以根据大家提供的主题&#xff0c;环境&#xff0c;文体&#xff0c;语言等要求进行写作&#xff0c;还有诗歌创作&#xff0c;也可以帮我们进行内…

如何精准预测天气?火山引擎ByteHouse与大地量子这么做

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 伴随着气象技术的发展以及城市气象设施的完善&#xff0c;气象监测服务能力在不断提高&#xff0c;实现短期甚至中长期的气象预测成为可能。 短期、长期的天气形势预…

opencv+ffmpeg+QOpenGLWidget开发的音视频播放器demo

前言 本篇文档的demo包含了 1.使用OpenCV对图像进行处理&#xff0c;对图像进行置灰&#xff0c;旋转&#xff0c;抠图&#xff0c;高斯模糊&#xff0c;中值滤波&#xff0c;部分区域清除置黑&#xff0c;背景移除&#xff0c;边缘检测等操作&#xff1b;2.单纯使用opencv播放…

crm客户管理系统的功能有哪些?

阅读本文&#xff0c;您可以了解&#xff1a;1、CRM客户管理系统的定义&#xff1b;2、CRM客户管理系统的功能。 CRM客户管理系统是一个工具或软件&#xff0c;能够帮助企业更好地与客户进行沟通、理解客户需求&#xff0c;以及有效地处理客户信息和互动。通俗地说&#xff0c…

选择正确的液压密封件的综合指南

在液压系统中&#xff0c;选择正确的密封件对于确保较佳性能和防止潜在泄漏至关重要。由于有许多选择&#xff0c;因此需要选择较合适的液压密封件。在本文中&#xff0c;我们将讨论选择液压密封件时应考虑的关键因素&#xff0c;以帮助您做出明智的决定。 1、了解您的系统要求…

一篇文章告诉你,为什么要使用Javascript流程图来可视化进程?(上)

DHTMLX Diagram库是有各种类型的图组成的&#xff0c;其中最广泛使用的是JavaScript流程图&#xff0c;它可以显示任何类型的的工作流、过程或系统&#xff0c;您可以下载DHTMLX Diagram的评估版并亲自试用。 DHTMLX Diagram正式版下载 Javascript流程图是用来干什么的&#…

项目经理如何做好多项目管理?

多项目管理一直是一项极具挑战性的工作&#xff0c;之所以难&#xff0c;不仅在于项目数量的增加&#xff0c;而且在于项目资源分配不均、项目进度比较难监控、沟通不良&#xff0c;协作不畅。 项目经理应该如何做好多项目管理呢&#xff1f; 1、明确的项目组织结构 一个清晰…

纯js点击按钮切换首页部分页面

像我这种大数据的&#xff0c;不会前端的&#xff0c;懒得学框架&#xff0c;现在有gpt了&#xff0c;前端对于我来说&#xff0c;用原生的更加友好&#xff0c;毕竟算法gpt都能优化。 首页我有个页面&#xff0c;然后我现在想点击gm替换上面的统计&#xff0c;点击用户替换回…

低代码平台轻松玩转业务规则

规则&#xff0c;是运行、运作规律所遵循的法则。规则无处不在&#xff0c;社会活动中的规则通常由传统、公共认识形成&#xff0c;表现为该干什么或是不该干什么。在计算机应用系统中&#xff0c;经常会面对这样的场景&#xff0c;处理执行一个动作时&#xff0c;需要另外的条…

【从零学习python 】29. 「函数参数详解」——了解Python函数参数的不同用法

文章目录 函数参数详解一、缺省参数二、不定长参数三、缺省参数在*args后面可变、不可变类型总结 进阶案例 函数参数详解 一、缺省参数 调用函数时&#xff0c;缺省参数的值如果没有传入&#xff0c;则取默认值。 下例会打印默认的age&#xff0c;如果age没有被传入&#xf…