[思考] 难的问题到底难在哪儿

本文探讨了问题难度的本质,提出问题规模与抽象程度是决定难题的关键因素。通过类比面向对象编程的封装、继承、多态原则,阐述了解决复杂问题的通用方法。

Table of Contents

前言

什么是问题规模?

什么是抽象程度?

解决困难问题的共通方法


前言

为什么会有动机写这样一篇文章,是因为在我们每个人每一天的开发工作,学习当中,总是会层出不穷,永无尽头的遇见各种各样的问题,从上学开始面对一道又一道考试题目,到后来工作中面临一个又一个bug或者功能需求,到生活当中需要处理一个又一个的事情。我们不停的解决问题,不停遇到新的问题,甚至可以说,每个人的一生不过都是类似如此的一个循环。

有的问题我们轻而易举就可以解决,有的问题我们努力一点可以解决,有的问题我们拼尽全力才能解决。笔者在自己的观察和对自己的经历的反思当中也渐渐的发现了很多有意思的事情,或者说规律。

  1. 不同的人在面对相同的问题时的解决能力是不一样的,甚至是千差万别的,因此同一个问题有人会说难,有人会说不难,这非常正常。明白这一点的人就不会因为某一件事事情去取笑一个人,或是看不起比他笨的在某些方面不如他的人。
  2. 一个人解决问题的能力主要来自于三个方面:先天智力/体力条件,知识水平(包括分析问题的能力和借助工具帮助的能力),自信心。
  3. 人的自信心会在不停的解决问题,或者说完成事情当中积累,也可能因为一些失败受到影响,自信心是动态的这和马太效应是一脉相承的,不停解决各种问题的人,自信心会逐渐的变得更强,这也反过来帮助他在之后遇到问题时能表现的更加好。
  4. 抛开人的因素不看,从纯粹客观的角度来看的话,那么一个难的问题到底难在哪儿(当然,考虑每个人的个体差异或许才是最客观的,但这里我们只谈问题本身)?主要就是两个方面:一是问题的规模,二是这个问题涉及的抽象程度。这一点可以说放之四海皆准,在任何行业,任何场景都适用。

为什么这么说呢?前面三点很好理解没有太大争议,值得展开的是第四点。

什么是问题规模?

问题规模就是指的问题的大小。举例说明:

一道难的数学题:计算量往往是很大的。注意这里的计算量理解的不应该太死板。数学题答案最后写出来也许几步但是草稿纸必然不止几步,草稿纸用的少,大脑里做的计算也绝对不会少。

一篇很难的英语阅读:句子往往是很长,文章也不可能只有几句话。

一个很难实现的功能:所需要的代码量肯定是不小的,注意直接调用写好的方法不算代码量小。例如java常用的类HashMap仅仅这一个类的代码就超过2000行,这还不算它所用到的依赖的其他地方的代码。

还有一些例子并不是一个具体的问题,而是一个具体的实物。

一台汽车,或者说生产一台汽车:任何一台汽车,组成他的零部件都上万个,不说生产,光是把这些零部件拼在一起,这就是一个巨量的工程。生产其他机器同理,如飞机,电脑等。我们要知道,在古代的皇帝也不过只能坐轿子。

上面这些问题都是非常难的问题,他们的问题规模也都很大。

什么是抽象程度?

抽象在科学里的定义是:从具体事物抽出、概括出它们共同的方面、本质属性与关系等,而将个别的、非本质的方面、属性与关系舍弃,这种思维过程,称为抽象。

在计算机的世界当中,抽象是我们用来简化复杂的现实问题的途径。

所谓抽象程度,笔者的总结是指一个问题抽象之后是否能被我们理解,从另一个角度来说就是当我们要描述这个抽象的问题时,是否容易给别人描述清楚,假设表达能力正常,则越难描述清楚抽象程度越高,越容易描述清楚则抽象程度越低。

这里有一个非常有意思的现象,就是抽象程度是会动态变化的,因为抽象程度本质是一个相对于人的理解的概念,当人们对一个专业的术语或是代号,公式,都公认且应用熟练之后,这个抽象对于他们来说将变得非常容易理解。一个比较常见的例子是,在一个行业的人如果去另外一个行业体验的话,一定会遇到很多这个行业的特定代指某一事物的一些称呼,这些代号在一开始肯定是听不懂的,但是假以时日,我们就能马上get到它的意思了。

再比如未知数这样一个概念,我们在上学时都有学习过设未知数x来解决问题的经历,那么在没有引入未知数这个概念时,我们解决问题的思路都是从已知的出发去算。突然有一天有人告诉你我们可以把不知道的设为x。但是对于一个上小学或者中学的学生来说,他在第一次看到这个x的时候一定是很难理解的,这个x本质上就是对未知数的一个抽象。

但是,我们的生活当中以及后来的学习当中,要求的答案并不都是一个数字,因此那个x所代表的抽象并不都全是数字,x所代表的抽象我们越难以理解,那么我们要求这个x也就越难。现在,我想假设人生的意义为x,然后我的问题是,那么人生的意义是什么呢?

再举一个编程时的例子,在我们测试我们编写的程序时,或者在工作当中,有时候会遇到一些很难解决的bug,这些bug假如是第一次遇到的话,我们往往会无从下手,原因往往是我们并不能一下子就准确理解这个错误的意思,因此要修复就没那么容易。而当我们通过查阅资料,更改代码,再结合自己的知识解决了问题之后,当下一次遇到这样的问题时,我们就能渐渐的‘猜’出可能是哪里出错了。这个过程可以说就是一个bug的抽象程度在相对我们的理解能力提高而降低的一个过程。

我们可以简单把问题抽象程度理解为这个问题是否容易理解的程度。

解决困难问题的共通方法

认识了一个难得问题难在哪儿,以及难题的规律之后,对我们解决这些问题就会大有裨益。这里要引入程序员最熟悉不过的面向对象,不过这里只谈思想不谈具体编码。同时在这里推荐一本书《面向对象分析与设计》。这本书最重要的内容就是讲了面向对象思想如何帮助我们认识了这个复杂的世界,和本文的主题一脉相承。

首先,面向对象思想的三大特点:封装,继承,多态。在这里博主认为可以加上抽象,但是抽象和封装以及继承都有一些重叠的地方,我们之后再来谈这一点。它们的定义如下:

封装: 在软件世界当中它指的是:把数据和对数据进行的操作封装在一起,数据被保存在内部,程序的其他部分只有通过被授权的操作(成员方法)才能对数据进行操作。事实上我们完全可以跳出软件的范围来看封装的概念。举例说明,在你驾驶汽车的时候,你只需要点火,踩油门,转方向盘等操作,你曾经去关心过汽车内部这些操作是如何实现的吗?一般情况下我们不会这样做,而是直接去进行这些操作,也就是在调用汽车提供的方法,汽车的这些操作,对我们来说就是一个封装好的操作,我们只需要调用即。

再举一例,有一位知名考研数学老师在讲泰勒公式时有一个经典的讲解方法。这个问题不细讲,大致说一下就是在高等数学里有一个等价无穷小公式:当x趋近于0时,x-sinx等价于(x^3)/6,注意这是一个公式。而出题人出题时必然不会直接给一个公式让我们求,他可能会把x换成任何东西,换言之,真正的x可能比这里公式的x看起来复杂。这位老师身经百战,经验丰富,他一下就识破了出题人的意图,于是就把这x当成了一个整体,说出了一个非常有意思的公式:狗-sin狗 等价于 (狗^3)/6.

现在,我们再看看这个讲法,这不就是把’狗‘这个东西来当作了一个对象吗?这个对象代表的就是一个抽象而已。因此,这不也就是面向对象里面的一个对象吗?没有任何区别啊。x不就是一个代指吗,它可能不是一个数,它可能是任何一个对象。这位老师的讲法事实上就是面向对象思想的一个经典应用。并且,这样的思想在数学上甚至在其他任何学科上,都是无处不在也一直在发挥着它得魔力,当然也包括我们现在讲的软件的世界。只是很多人并没有意识到他们用的这种思想本质上到底是一种什么样的思想。而现在,我们应该渐渐明白了。

继承:在软件当中的定义是: 当多个类存在相同的属性和方法时,可以从这些类中抽象出一个父类,在父类中定义这些相同的属性和方法,所有的子类只需要通过extend来声明继承父类就可以获得这些共同属性。事实上面向对象的理论往往第一句话就是:everything is an object. 因此这里虽然表述的是类,但我们同样可以把这个概念应用于生活中的几乎任何场景。还是以汽车为例,丰田卡罗拉的同一型号的不同款式汽车,在生产时,大部分的架构必然是采用同一个生产线,只是有一些不同型号的配置,不同型号的车可以根据自己的特点进行改变或者添加一些特有的配置。

多态:在软件当中多态的概念是:通过同一个指向父类的指针,来调用在不同子类中实现的方法。这个概念貌似只是一个纯软件概念。但是依然来源于生活,还是以驾驶汽车为例,当我们学驾驶技术的时候,也即调用汽车的,转方向盘,踩油门,打灯等方法时,我们学的是通用的方法,并不会因为换了一辆汽车,学的这些驾驶技术就需要重新学

在上面的描述当中,我们已经提到了抽象,正如上文所说,抽象蕴含在了封装,继承,多态当中。将事物的一些特性封装在一起,这就是一个抽象的过程。将类的共同属性和方法提取到父类当中,这也是一个抽象的过程。

因此,我们在看待生活中的事物,以及一些所谓的难题之时,可以按照这样的思路去抽丝剥茧,这样一个再难的问题也会变为可以理解的,可以被实现的。但有一点不得不提的时,随着抽象层次的复杂,问题量的变大,有的东西相对于一个人的脑力来说的确会显得十分复杂,我们也很难去做到面面俱到的了解。例如,一个大型银行的所有业务系统,这就是一个相当复杂的东西。但是,必须指出的是做到面面俱到的了解往往是无意义的。对我们来说,掌握这样的方法和理论,帮助我们构建这样的复杂系统,以及分析其中的问题,才是我们应该去做和值得去做的事情。

(本篇完)

 

提供了基于BP(Back Propagation)神经网络结合PID(比例-积分-微分)控制策略的Simulink仿真模型。该模型旨在实现对杨艺所著论文《基于S函数的BP神经网络PID控制器及Simulink仿真》中的理论进行实践验证。在Matlab 2016b环境下开发,经过测试,确保能够正常运行,适合学习和研究神经网络在控制系统中的应用。 特点 集成BP神经网络:模型中集成了BP神经网络用于提升PID控制器的性能,使之能更好地适应复杂控制环境。 PID控制优化:利用神经网络的自学习能力,对传统的PID控制算法进行了智能调整,提高控制精度和稳定性。 S函数应用:展示了如何在Simulink中通过S函数嵌入MATLAB代码,实现BP神经网络的定制化逻辑。 兼容性说明:虽然开发于Matlab 2016b,但理论上兼容后续版本,可能会需要调整少量配置以适配不同版本的Matlab。 使用指南 环境要求:确保你的电脑上安装有Matlab 2016b或更高版本。 模型加载: 下载本仓库到本地。 在Matlab中打开.slx文件。 运行仿真: 调整模型参数前,请先熟悉各模块功能和输入输出设置。 运行整个模型,观察控制效果。 参数调整: 用户可以自由调节神经网络的层数、节点数以及PID控制器的参数,探索不同的控制性能。 学习和修改: 通过阅读模型中的注释和查阅相关文献,加深对BP神经网络与PID控制结合的理解。 如需修改S函数内的MATLAB代码,建议有一定的MATLAB编程基础。
<think>嗯,用户这次聚焦在0-1背包问题的NP特性与回溯法应用前提的结合分析,这是个很精准的技术交叉点。之前我详细解释过回溯法通用的前提条件(组合结构、约束剪枝等),用户显然希望将这些抽象性质具体映射到0-1背包实例上,同时深入理解其NP本质。用户可能正在学习算法设计课程,或是准备相关面试——因为TA特意强调要“参考站内引用”,说明需要理论严谨性。从追问方式看,TA的思维很结构化:先确认问题分类(NP),再探讨解决方法前提(回溯法),这种递进式思考值得欣赏。关于NP特性,需要突出两点:一是问题描述本身(物品组合的最优解验证容易但搜索困),二是与回溯法的关联(为什么回溯法能缓解但无法消除NP)。尤其要强调约束条件在0-1背包中如何通过多米诺性质实现剪枝——比如当部分解总重量超过$W$时,后续任何添加都必然违反约束。用户可能隐含关注时间复杂度的实际影响。回溯法在0-1背包问题中虽然理论上是$O(2^n)$,但若能通过价值排序+动态剪枝(如使用最大潜能估计),实践效率可能远优于理论值。这一点在回答里补充了具体公式:$$UB=v_{\text{current}}+(W-w_{\text{current}})\times\frac{v_{i+1}}{w_{i+1}}$$最后生成的延伸问题特别加入“伪多项式时间解法”,因为这是0-1背包区别于其他NP问题的关键特征,能帮用户建立更完整的知识图谱。毕竟动态规划解法才是工程实践的首选,回溯法则更多用于教学示范或小规模场景。</think> ### 0-1背包问题的NP特性与回溯法前提条件分析 0-1背包问题作为经典的**NP问题**,其特性与回溯法的适用性紧密相关。以下是核心要点: --- #### **一、0-1背包问题的NP特性** 1. **问题定义** 给定 $n$ 个物品(重量 $w_i$、价值 $v_i$)和背包容量 $W$,要求选择子集使总重量 $\leq W$ 且总价值最大。数学表示为: $$ \max \sum_{i=1}^n v_i x_i \quad \text{s.t.} \quad \sum_{i=1}^n w_i x_i \leq W, \quad x_i \in \{0,1\} $$ 2. **NP的本质** - **验证简单,求解困**:给定一个解(如 $x_i$ 的赋值),可在 $O(n)$ 时间内验证是否满足重量约束并计算价值;但**寻找最优解需要遍历指数级解空间**(解空间大小为 $2^n$)。 - **多项式归约**:可归约为子集和问题(Subset Sum)等已知NP问题,证明其属于NP-hard类[^1]。 --- #### **二、回溯法求解0-1背包的前提条件** 回溯法能有效求解该问题,依赖于以下性质: 1. **组合结构(Combinatorial Structure)** - 解可表示为决策序列 $x_1, x_2, \dots, x_n$($x_i=0$ 或 $1$),形成树状解空间(深度 $=n$,每个节点分支数为2)。 2. **多米诺性质(Domino Property)** - **核心前提**:若当前部分解已不满足约束,则其所有扩展解均无效。 在0-1背包中表现为: $$ \text{若 } \sum_{i=1}^k w_i x_i > W \quad \Rightarrow \quad \text{所有含此部分解的组合均超重} $$ 允许在搜索中剪枝无效分支,避免穷举(如当已选物品总重超 $W$ 时立即回溯)[^2]。 3. **优化目标的可分性** - 价值最大化目标支持**贪婪剪枝**(Greedy Pruning): 在扩展部分解时,计算**剩余物品的最大潜能价值上界(UB)**: $$ \text{UB} = v_{\text{current}} + (W - w_{\text{current}}) \times \max_{j} \frac{v_j}{w_j} $$ - 若 $\text{UB} \leq$ 当前已知最优解值,则剪枝该分支(不可能更优)。 4. **状态可回溯(Reversible State)** - 决策过程需支持撤销:选择 $x_i=1$ 后若回溯,需恢复总重量与价值(通过递归栈自动实现)。 --- #### **三、回溯法在0-1背包中的具体实现** ```python def knapsack_backtrack(i, current_weight, current_value, items, W, best): if i == len(items) or current_weight > W: return best if current_weight == W: # 剪枝:已达重量上限 return max(best, current_value) # 剪枝1:剩余物品贪心价值上界 ≤ 当前最优值 ub = current_value + (W - current_weight) * max_vw_ratio(items[i:]) if ub <= best: return best # 不选第i个物品 best = knapsack_backtrack(i+1, current_weight, current_value, items, W, best) # 选第i个物品 if current_weight + items[i].weight <= W: best = knapsack_backtrack(i+1, current_weight+items[i].weight, current_value+items[i].value, items, W, best) return best ``` --- #### **四、为什么回溯法适合0-1背包?** | **性质** | **0-1背包满足情况** | **回溯法收益** | |------------------------|----------------------------------------|-----------------------------------| | 解空间为决策树 | ✅ 深度 $n$,每个决策点二分支 | 系统遍历所有可能解 | | 约束具多米诺性质 | ✅ 超重部分解无法扩展为有效解 | 大幅剪枝无效分支 | | 优化目标可分 | ✅ 剩余物品价值上界可估 | 剪枝非最优分支 | | NP的小规模适用性 | ✅ $n$ 较小时($n \leq 40$) 可行 | 避免指数爆炸 | > ⚠️ 注意:当 $n$ 较大时,回溯法仍受限于指数复杂度 $O(2^n)$,需结合动态规划或近似算法[^2]。 --- #### **延伸问题** 1. 0-1背包问题的动态规划解法如何利用记忆化避免重复计算? 2. 多米诺性质在图的着色问题中如何体现?若违反此性质会怎样? 3. 回溯法的剪枝策略(如贪心价值上界)能否改进背包问题的求解效率? 4. 0-1背包问题是否存在伪多项式时间解法?它与NP性质是否矛盾? 5. 其他NP问题(如旅行商问题)如何使用回溯法求解?与背包问题的差异?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值