精华内容
下载资源
问答
  • 张立昂的精品书籍,《可计算性与计算复杂性导引(第3版)》,第三版。
  • 领域驱动设计:软件核心复杂性应对之道.pdf
  • 降低软件复杂性的一般原则和方法

    千次阅读 2019-09-24 10:37:31
    分模块降低了单模块的复杂性,但是也会引入新的复杂性,例如模块与模块的交互,后面的章节会讨论这个问题。这里,我们将第三个原则确定为分模块。 最后,代码能够描述程序的工作流程和结果,却很难描述开发人员的...

    一、前言

    斯坦福教授、Tcl语言发明者John Ousterhout 的著作《A Philosophy of Software Design》[1],自出版以来,好评如潮。按照IT图书出版的惯例,如果冠名为“实践”,书中内容关注的是某项技术的细节和技巧;冠名为“艺术”,内容可能是记录一件优秀作品的设计过程和经验;而冠名为“哲学",则是一些通用的原则和方法论,这些原则方法论串起来,能够形成一个体系。正如”知行合一”、“世界是由原子构成的”、“我思故我在”,这些耳熟能详的句子能够一定程度上代表背后的人物和思想。用一句话概括《A Philosophy of Software Design》,软件设计的核心在于降低复杂性。

    本篇文章是围绕着“降低复杂性”这个主题展开的,很多重要的结论来源于John Ousterhout,笔者觉得很有共鸣,就做了一些相关话题的延伸、补充了一些实例。虽说是"一般原则“,也不意味着是绝对的真理,整理出来,只是为了引发大家对软件设计的思考。

    二、如何定义复杂性

    关于复杂性,尚无统一的定义,从不同的角度可以给出不同的答案。可以用数量来度量,比如芯片集成的电子器件越多越复杂(不一定对);按层次性[2]度量,复杂度在于层次的递归性和不可分解性。在信息论中,使用熵来度量信息的不确定性。

    John Ousterhout选择从认知的负担和开发工作量的角度来定义软件的复杂性,并且给出了一个复杂度量公式:

    子模块的复杂度cp乘以该模块对应的开发时间权重值tp,累加后得到系统的整体复杂度C。系统整体的复杂度并不简单等于所有子模块复杂度的累加,还要考虑该模块的开发维护所花费的时间在整体中的占比(对应权重值tp)。也就是说,即使某个模块非常复杂,如果很少使用或修改,也不会对系统的整体复杂度造成大的影响。

    子模块的复杂度cp是一个经验值,它关注几个现象:

    • 修改扩散,修改时有连锁反应。
    • 认知负担,开发人员需要多长时间来理解功能模块。
    • 不可知(Unknown Unknowns),开发人员在接到任务时,不知道从哪里入手。

    造成复杂的原因一般是代码依赖和晦涩(Obscurity)。其中,依赖是指某部分代码不能被独立地修改和理解,必定会牵涉到其他代码。代码晦涩,是指从代码中难以找到重要信息。

    三、解决复杂性的一般原则

    首先,互联网行业的软件系统,很难一开始就做出完美的设计,通过一个个功能模块衍生迭代,系统才会逐步成型;对于现存的系统,也很难通过一个大动作,一劳永逸地解决所有问题。系统设计是需要持续投入的工作,通过细节的积累,最终得到一个完善的系统。因此,好的设计是日拱一卒的结果,在日常工作中要重视设计和细节的改进。

    其次,专业化分工和代码复用促成了软件生产率的提升。比如硬件工程师、软件工程师(底层、应用、不同编程语言)可以在无需了解对方技术背景的情况下进行合作开发;同一领域服务可以支撑不同的上层应用逻辑等等。其背后的思想,无非是通过将系统分成若干个水平层、明确每一层的角色和分工,来降低单个层次的复杂性。同时,每个层次只要给相邻层提供一致的接口,可以用不同的方法实现,这就为软件重用提供了支持。分层是解决复杂性问题的重要原则。

    第三,与分层类似,分模块是从垂直方向来分解系统。分模块最常见的应用场景,是如今广泛流行的微服务。分模块降低了单模块的复杂性,但是也会引入新的复杂性,例如模块与模块的交互,后面的章节会讨论这个问题。这里,我们将第三个原则确定为分模块。

    最后,代码能够描述程序的工作流程和结果,却很难描述开发人员的思路,而注释和文档可以。此外,通过注释和文档,开发人员在不阅读实现代码的情况下,就可以理解程序的功能,注释间接促成了代码抽象。好的注释能够帮助解决软件复杂性问题,尤其是认知负担和不可知问题(Unknown Unknowns)。

    四、解决复杂性之日拱一卒

    4.1 拒绝战术编程

    战术编程致力于完成任务,新增加特性或者修改Bug时,能解决问题就好。这种工作方式,会逐渐增加系统的复杂性。如果系统复杂到难以维护时,再去重构会花费大量的时间,很可能会影响新功能的迭代。

    战略编程,是指重视设计并愿意投入时间,短时间内可能会降低工作效率,但是长期看,会增加系统的可维护性和迭代效率。

    设计系统时,很难在开始阶段就面面俱到。好的设计应该体现在一个个小的模块上,修改bug时,也应该抱着设计新系统的心态,完工后让人感觉不到“修补”的痕迹。经过累积,最终形成一个完善的系统。从长期看,对于中大型的系统,将日常开发时间的10-15%用于设计是值得的。有一种观点认为,创业公司需要追求业务迭代速度和节省成本,可以容忍糟糕的设计,这是用错误的方法去追求正确的目标。降低开发成本最有效的方式是雇佣优秀的工程师,而不是在设计上做妥协。

    4.2 设计两次

    为一个类、模块或者系统的设计提供两套或更多方案,有利于我们找到最佳设计。以我们日常的技术方案设计为例,技术方案本质上需要回答两个问题,其一,为什么该方案可行? 其二,在已有资源限制下,为什么该方案是最优的?为了回答第一个问题,我们需要在技术方案里补充架构图、接口设计和时间人力估算。而要回答第二个问题,需要我们在关键点或争议处提供二到三种方案,并给出建议方案,这样才有说服力。通常情况下,我们会花费很多的时间准备第一个问题,而忽略第二个问题。其实,回答好第二个问题很重要,大型软件的设计已经复杂到没人能够一次就想到最佳方案,一个仅仅“可行”的方案,可能会给系统增加额外的复杂性。对聪明人来说,接受这点更困难,因为他们习惯于“一次搞定问题”。但是聪明人迟早也会碰到自己的瓶颈,在低水平问题上徘徊,不如花费更多时间思考,去解决真正有挑战性的问题。

    五、解决复杂性之分层

    5.1 层次和抽象

    软件系统由不同的层次组成,层次之间通过接口来交互。在严格分层的系统里,内部的层只对相邻的层次可见,这样就可以将一个复杂问题分解成增量步骤序列。由于每一层最多影响两层,也给维护带来了很大的便利。分层系统最有名的实例是TCP/IP网络模型。

    在分层系统里,每一层应该具有不同的抽象。TCP/IP模型中,应用层的抽象是用户接口和交互;传输层的抽象是端口和应用之间的数据传输;网络层的抽象是基于IP的寻址和数据传输;链路层的抽象是适配和虚拟硬件设备。如果不同的层具有相同的抽象,可能存在层次边界不清晰的问题。

    5.2 复杂性下沉

    不应该让用户直面系统的复杂性,即便有额外的工作量,开发人员也应当尽量让用户使用更简单。如果一定要在某个层次处理复杂性,这个层次越低越好。举个例子,Thrift接口调用时,数据传输失败需要引入自动重试机制,重试的策略显然在Thrift内部封装更合适,开放给用户(下游开发人员)会增加额外的使用负担。与之类似的是系统里随处可见的配置参数(通常写在XML文件里),在编程中应当尽量避免这种情况,用户(下游开发人员)一般很难决定哪个参数是最优的,如果一定要开放参数配置,最好给定一个默认值。

    复杂性下沉,并不是说把所有功能下移到一个层次,过犹不及。如果复杂性跟下层的功能相关,或者下移后,能大大下降其他层次或整体的复杂性,则下移。

    5.3 异常处理

    异常和错误处理是造成软件复杂的罪魁祸首之一。有些开发人员错误的认为处理和上报的错误越多越好,这会导致过度防御性的编程。如果开发人员捕获了异常并不知道如何处理,直接往上层扔,这就违背了封装原则。

    降低复杂度的一个原则就是尽可能减少需要处理异常的可能性。而最佳实践就是确保错误终结,例如删除一个并不存在的文件,与其上报文件不存在的异常,不如什么都不做。确保文件不存在就好了,上层逻辑不但不会被影响,还会因为不需要处理额外的异常而变得简单。

    六、解决复杂性之分模块

    分模块是解决复杂性的重要方法。理想情况下,模块之间应该是相互隔离的,开发人员面对具体的任务,只需要接触和了解整个系统的一小部分,而无需了解或改动其他模块。

    6.1 深模块和浅模块

    深模块(Deep Module)指的是拥有强大功能和简单接口的模块。深模块是抽象的最佳实践,通过排除模块内部不重要的信息,让用户更容易理解和使用。

    Unix操作系统文件I/O是典型的深模块,以Open函数为例,接口接受文件名为参数,返回文件描述符。但是这个接口的背后,是几百行的实现代码,用来处理文件存储、权限控制、并发控制、存储介质等等,这些对用户是不可见的。

    int open(const char* path, int flags, mode_t permissions);
    
    

    与深模块相对的是浅模块(Shallow Module),功能简单,接口复杂。通常情况下,浅模块无助于解决复杂性。因为他们提供的收益(功能)被学习和使用成本抵消了。以Java I/O为例,从I/O中读取对象时,需要同时创建三个对象FileInputStream、BufferedInputStream、ObjectInputStream,其中前两个创建后不会被直接使用,这就给开发人员造成了额外的负担。默认情况下,开发人员无需感知到BufferedInputStream,缓冲功能有助于改善文件I/O性能,是个很有用的特性,可以合并到文件I/O对象里。假如我们想放弃缓冲功能,文件I/O也可以设计成提供对应的定制选项。

    
    FileInputStream fileStream = new FileInputStream(fileName);
    BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
    ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
    
    

    关于浅模块有一些争议,大多数情况是因为浅模块是不得不接受的既定事实,而不见得是因为合理性。当然也有例外,比如领域驱动设计里的防腐层,系统在与外部系统对接时,会单独建立一个服务或模块去适配,用来保证原有系统技术栈的统一和稳定性。

    6.2 通用和专用

    设计新模块时,应该设计成通用模块还是专用模块?一种观点认为通用模块满足多种场景,在未来遇到预期外的需求时,可以节省时间。另外一种观点则认为,未来的需求很难预测,没必要引入用不到的特性,专用模块可以快速满足当前的需求,等有后续需求时再重构成通用的模块也不迟。

    以上两种思路都有道理,实际操作的时候可以采用两种方式各自的优点,即在功能实现上满足当前的需求,便于快速实现;接口设计通用化,为未来留下余量。举个例子。

    
    void backspace(Cursor cursor);
    void delete(Cursor cursor);
    void deleteSelection(Selection selection);
    
    //以上三个函数可以合并为一个更通用的函数
    void delete(Position start, Position end);
    
    

    设计通用性接口需要权衡,既要满足当前的需求,同时在通用性方面不要过度设计。一些可供参考的标准:

    • 满足当前需求最简单的接口是什么?在不减少功能的前提下,减少方法的数量,意味着接口的通用性提升了。
    • 接口使用的场景有多少?如果接口只有一个特定的场景,可以将多个这样的接口合并成通用接口。
    • 满足当前需求情况下,接口的易用性?如果接口很难使用,意味着我们可能过度设计了,需要拆分。

    6.3 信息隐藏

    信息隐藏是指,程序的设计思路以及内部逻辑应当包含在模块内部,对其他模块不可见。如果一个模块隐藏了很多信息,说明这个模块在提供很多功能的同时又简化了接口,符合前面提到的深模块理念。软件设计领域有个技巧,定义一个"大"类有助于实现信息隐藏。这里的“大”类指的是,如果要实现某功能,将该功能相关的信息都封装进一个类里面。

    信息隐藏在降低复杂性方面主要有两个作用:一是简化模块接口,将模块功能以更简单、更抽象的方式表现出来,降低开发人员的认知负担;二是减少模块间的依赖,使得系统迭代更轻量。举个例子,如何从B+树中存取信息是一些数据库索引的核心功能,但是数据库开发人员将这些信息隐藏了起来,同时提供简单的对外交互接口,也就是SQL脚本,使得产品和运营同学也能很快地上手。并且,因为有足够的抽象,数据库可以在保持外部兼容的情况下,将索引切换到散列或其他数据结构。

    与信息隐藏相对的是信息暴露,表现为:设计决策体现在多个模块,造成不同模块间的依赖。举个例子,两个类能处理同类型的文件。这种情况下,可以合并这两个类,或者提炼出一个新类(参考《重构》[3]一书)。工程师应当尽量减少外部模块需要的信息量。

    6.4 拆分和合并

    两个功能,应该放在一起还是分开?“不管黑猫白猫”,能降低复杂性就好。这里有一些可以借鉴的设计思路:

    • 共享信息的模块应当合并,比如两个模块都依赖某个配置项。
    • 可以简化接口时合并,这样可以避免客户同时调用多个模块来完成某个功能。
    • 可以消除重复时合并,比如抽离重复的代码到一个单独的方法中。
    • 通用代码和专用代码分离,如果模块的部分功能可以通用,建议和专用部分分离。举个例子,在实际的系统设计中,我们会将专用模块放在上层,通用模块放在下层以供复用。

    七、解决复杂性之注释

    注释可以记录开发人员的设计思路和程序功能,降低开发人员的认知负担和解决不可知(Unkown Unkowns)问题,让代码更容易维护。通常情况下,在程序的整个生命周期里,编码只占了少部分,大量时间花在了后续的维护上。有经验的工程师懂得这个道理,通常也会产出更高质量的注释和文档。

    注释也可以作为系统设计的工具,如果只需要简单的注释就可以描述模块的设计思路和功能,说明这个模块的设计是良好的。另一方面,如果模块很难注释,说明模块没有好的抽象。

    7.1 注释的误区

    关于注释,很多开发者存在一些认识上的误区,也是造成大家不愿意写注释的原因。比如“好代码是自注释的"、"没有时间“、“现有的注释都没有用,为什么还要浪费时间”等等。这些观点是站不住脚的。“好代码是自注释的”只在某些场景下是合理的,比如为变量和方法选择合适的名称,可以不用单独注释。但是更多的情况,代码很难体现开发人员的设计思路。此外,如果用户只能通过读代码来理解模块的使用,说明代码里没有抽象。好的注释可以极大地提升系统的可维护性,获取长期的效率,不存在“没有时间”一说。注释也是一种可以习得的技能,一旦习得,就可以在后续的工作中应用,这就解决了“注释没有用”的问题。

    7.2 使用注释提升系统可维护性

    注释应当能提供代码之外额外的信息,重视What和Why,而不是代码是如何实现的(How),最好不要简单地使用代码中出现过的单词。

    根据抽象程度,注释可以分为低层注释和高层注释,低层次的注释用来增加精确度,补充完善程序的信息,比如变量的单位、控制条件的边界、值是否允许为空、是否需要释放资源等。高层次注释抛弃细节,只从整体上帮助读者理解代码的功能和结构。这种类型的注释更好维护,如果代码修改不影响整体的功能,注释就无需更新。在实际工作中,需要兼顾细节和抽象。低层注释拆散与对应的实现代码放在一起,高层注释一般用于描述接口。

    注释先行,注释应该作为设计过程的一部分,写注释最好的时机是在开发的开始环节,这不仅会产生更好的文档,也会帮助产生好的设计,同时减少写文档带来的痛苦。开发人员推迟写注释的理由通常是:代码还在修改中,提前写注释到时候还得再改一遍。这样的话就会衍生两个问题:

    • 首先,推迟注释通常意味着根本就没有注释。一旦决定推迟,很容易引发连锁反应,等到代码稳定后,也不会有注释这回事。这时候再想添加注释,就得专门抽出时间,客观条件可能不会允许这么做。
    • 其次,就算我们足够自律抽出专门时间去写注释,注释的质量也不会很好。我们潜意识中觉得代码已经写完了,急于开展下一个项目,只是象征性地添加一些注释,无法准确复现当时的设计思路。

    避免重复的注释。如果有重复注释,开发人员很难找到所有的注释去更新。解决方法是,可以找到醒目的地方存放注释文档,然后在代码处注明去查阅对应文档的地址。如果程序已经在外部文档中注释过了,不要在程序内部再注释了,添加注释的引用就可以了。

    注释属于代码,而不是提交记录。一种错误的做法是将功能注释放在提交记录里,而不是放在对应代码文件里。因为开发人员通常不会去代码提交记录里去查看程序的功能描述,很不方便。

    7.3 使用注释改善系统设计

    良好的设计基础是提供好的抽象,在开始编码前编写注释,可以帮助我们提炼模块的核心要素:模块或对象中最重要的功能和属性。这个过程促进我们去思考,而不是简单地堆砌代码。另一方面,注释也能够帮助我们检查自己的模块设计是否合理,正如前文中提到,深模块提供简单的接口和强大的功能,如果接口注释冗长复杂,通常意味着接口也很复杂;注释简单,意味着接口也很简单。在设计的早期注意和解决这些问题,会为我们带来长期的收益。

    八、后记

    John Ousterhout累计写过25万行代码,是3个操作系统的重要贡献者,这些原则可以视为作者编程经验的总结。有经验的工程师看到这些观点会有共鸣,一些著作如《代码大全》、《领域驱动设计》也会有类似的观点。本文中提到的原则和方法具有一定实操和指导价值,对于很难有定论的问题,也可以在实践中去探索。

    关于原则和方法论,既不必刻意拔高,也不要嗤之以鼻。指导实践的不是更多的实践,而是实践后的总结和思考。应用原则和方法论实质是借鉴已有的经验,可以减少我们自行摸索的时间。探索新的方法可以帮助我们适应新的场景,但是新方法本身需要经过时间检验。

    九、参考文档

    • John Ousterhout. A Philosophy of Software Design. Yaknyam Press, 2018.

    • 梅拉尼·米歇尔. 复杂. 湖南科学技术出版社, 2016.

    • Martin Fowler. Refactoring: Improving the Design of Existing Code (2nd Edition) . Addison-Wesley Signature Series, 2018.

    作者介绍

    政华,顺谱,陶鑫,美团打车调度系统工程团队工程师。

    招聘信息

    美团打车调度系统工程团队诚招高级工程师/技术专家,我们的目标,是与算法、数据团队密切协作,建设高性能、高可用、可配置的打车调度引擎, 为用户提供更好的出行体验。欢迎有兴趣的同学加入呦~~

    展开全文
  • Windows密码复杂性要求

    千次阅读 2019-11-08 19:44:19
    密码必须符合复杂性要求 介绍 "密码必须满足复杂性要求" 安全策略设置的最佳做法、位置、值和安全注意事项。 参考 "密码必须满足复杂性要求" 策略设置确定密码是否必须满足一系列对强密码重要的指南。 启用此策略...

    密码必须符合复杂性要求

    介绍 "密码必须满足复杂性要求" 安全策略设置的最佳做法、位置、值和安全注意事项。

    参考

    "密码必须满足复杂性要求" 策略设置确定密码是否必须满足一系列对强密码重要的指南。 启用此策略设置需要密码才能满足以下要求:

    在更改或创建密码时, 将强制执行复杂性要求。

    Windows Server 密码复杂性要求中包含的规则属于 Passfilt, 不能直接修改。

    启用默认的 Passfilt 可能会导致对锁定帐户的其他帮助台呼叫, 因为用户可能不会使用包含字母表中所示字符以外的其他字符的密码。 但是, 此策略设置非常灵活, 所有用户都可以通过次要学习曲线遵守要求。

    可包含在自定义 Passfilt 中的其他设置是使用非上层行字符。 较高的字符是通过按住 SHIFT 键并键入任何数字 from1 through10 所键入的字符。

    可能值

    最佳实践

    设置密码必须满足复杂性要求才能启用。 此策略设置与 "最小密码长度 of8" 相结合, 可确保单个密码至少有218340105584896种不同的可能性。 这使得强力攻击非常困难, 但仍不可能。

    使用 ALT 键字符组合可以大大提高密码的复杂程度。 但是, 要求组织中的所有用户遵守严格的密码要求可能会导致不满意的用户和极其繁忙的技术支持。 请考虑在你的组织中实现一个要求, 以使用0128到0159范围内的 ALT 字符作为所有管理员密码的一部分。 (此范围外的可选字符可以表示不会为密码增加额外复杂性的标准字母数字字符。)

    仅包含字母数字字符的密码可通过公共可用工具轻松受到危害。 为防止这种情况, 密码应包含其他字符并满足复杂性要求。

    位置

    计算机 Configuration\Windows Settings\Security Settings\Account Policies\Password 政策

    默认值

    下表列出了实际和有效的默认策略值。 默认值也在策略的属性页上列出。

    服务器类型或组策略对象 (GPO)默认值
    默认域策略启用
    默认域控制器策略启用
    独立服务器默认设置禁用
    域控制器有效默认设置启用
    成员服务器有效的默认设置启用
    客户端计算机上的有效 GPO 默认设置禁用

    安全注意事项

    本部分介绍攻击者如何利用一项功能或其配置,如何实施对策,以及对策实施可能产生的负面后果。

    漏洞

    仅包含字母数字字符的密码非常容易发现使用多个公开可用的工具。

    对策

    将 "密码必须满足复杂性要求" 策略设置配置为 "已启用", 并建议用户使用其密码中的各种字符。

    结合使用最小密码长度8 时, 此策略设置可确保单个密码的不同可能性的数量非常大, 以至于非常困难 (但不可能), 强力攻击才能成功。 (如果 "最小密码长度" 策略设置增加, 则成功的攻击所需的平均时间也会增加。)

    潜在影响

    如果保留默认密码复杂性配置, 则可能会发生锁定帐户的其他支持呼叫, 因为用户可能不熟悉包含非字母字符的密码, 或者可能在输入时遇到问题包含具有不同布局的键盘上的重音字符或符号的密码。 但是, 所有用户都应能够以最少的难度满足复杂性要求。

    如果您的组织具有更严格的安全要求, 则可以创建 Passfilt 文件的自定义版本, 该版本允许使用任意复杂的密码强度规则。 例如, 自定义密码筛选器可能需要使用非上层行符号。 (较高的行符号是要求您长按 SHIFT 键, 然后按1到0之间的任何数字的符号。)自定义密码筛选器还可能执行字典检查, 以验证建议的密码是否不包含常见字典单词或片段。

    使用 ALT 键字符组合可以大大提高密码的复杂程度。 但是, 这种严格的密码要求可能会导致其他帮助台请求。 或者, 你的组织可以考虑对所有管理员密码的要求, 以便在0128–0159范围中使用 ALT 字符。 (此范围外的可选字符可以表示不会为密码增加额外复杂性的标准字母数字字符。)

    1. 密码不能包含用户的 samAccountName (帐户名称) 值或整个 displayName (全名)。 这两个检查不区分大小写。

      将完全检查 samAccountName, 以确定它是否是密码的一部分。 如果 samAccountName 的长度小于3个字符, 将跳过此检查。 将分析 displayName 的分隔符: 逗号、句点、短划线或连字符、下划线、空格、井号和制表符。 如果找到这些分隔符中的任何一个, 则将拆分 displayName, 并且将确认所有已分析的分区 (标记) 不包含在密码中。 将忽略小于3个字符的标记, 并且不检查标记的子字符串。 例如, 名称 "Erin Hagens" 分为三个标记: "Erin"、"M" 和 "Hagens"。 由于第二个令牌的长度仅为一个字符, 因此将被忽略。 因此, 此用户在密码中的任何地方都不能包含 "erin" 或 "hagens" 作为子字符串的密码。

    2. 密码包含以下类别中的三个:

      • 欧洲语言的大写字母 (A 到 Z, 带有音调符号标记、希腊语和西里尔文字符)
      • 欧洲语言的小写字母 (a 到 z、半高和音调符号标记、希腊语和西里尔文字符)
      • 基数10个数字 (0 到 9)
      • 非字母数字字符 (特殊字符): (~! @ # $% ^& * _-+ = "| \ (){}\ []:;" "<>,。?/) 此政策设置不会将货币符号 (如欧元或英国镑) 计为特殊字符。
      • 归类为字母字符但不大写或小写的任何 Unicode 字符。 这包括亚洲语言的 Unicode 字符。
    • Enabled
    • 禁用
    • 未定义
    展开全文
  • 今天偶然间碰到数学界著名的七大千禧难题,关注了P和NP问题,但是里面提到关于算法的复杂性的描述已经超出了自己的知识范围,我找到了一本书来弥补这个不足,即《计算复杂性与算法分析》,希望自己在看完这本书之后...

    今天偶然间碰到数学界著名的七大千禧难题,关注了P和NP问题,但是里面提到关于算法的复杂性的描述已经超出了自己的知识范围,我找到了一本书来弥补这个不足,即《计算复杂性与算法分析》,希望自己在看完这本书之后能够对计算复杂性有完整的概念

    首先看看它的目录

    其实大部分的知识都有接触过,但是没有形成体系

    接下来就慢慢学习吧,挖个坑一直学

    • 自动机

    以基本码为单位对输入进行计算得到输出的过程

    看了一会,发现第一章和第二章的内容与编译原理的内容相似,介绍了语法分析树,语言文法

    • 图灵机

    图灵机是比自动机应用更为广泛的一种计算模型,非确定型图灵机(NTM)的移动不能为转移函数准确地决定,它的转移函数是多值的。如果将这些函数值放在仪器形成一个子集,那么也可以把它看成一个集值函数。除此之外,NTM和DTM没有区别,就是在分析下一位的输入的时候,它的输出是不确定的

    到了第5章正式介绍计算复杂性理论

    • 不同计算模型的算法复杂性的度量的尺度时不同的,但都从不同角度描述了一个算法的安度。一个算法的复杂性是由这类问题的固有安度决定的,它在某种意义上应独立于计算它的模型,但是,当我们涉及一个具体问题时,不可避免的要谈到这个问题的所谓体积,或称大小
    • 时间复杂性

    如果一个问题的大小是n,解决这一问题的某一算法所学的时间为T(n),它是n的某一函数。T(n)称为这一算法的时间复杂性。当输入量n逐渐加大时,时间复杂性的极限情形,称为算法的渐进时间复杂性。类似地,可以定义一个算法的空间复杂性和渐进空间复杂性。在算法的复杂性讨论中,人们主要是研究算法的时间复杂性,尤感兴趣的是算法的渐进时间复杂性,而对算法渐进复杂性的讨论往往只给出它的关于问题大小n的数量级。

    • RAM程序的时空耗费标准:均匀耗费标准和对数耗费标准

    均匀耗费标准是指用每执行一条RAM指令需要一个单位时间,每个数据需要占有一个单位空间的假设来分析一个RAM程序。这样做是比较粗略的,实际计算机执行不同类型的指令所需的时间一般并不相等,同一条指令的执行时间因操作数的不同也会有不同

    对数耗费标准是由于实际的计算机字长很有限和长度固定而产生的。在RAM中,假定一个内存储单元可以存放一个任意大小的整数且任何操作都可以用一条指令完成。有时这是与实际情况不相符的,因为一个整数n在存储器里要占用log2(n)+1个单位,当这个位数超过机器字长时,不仅一个存储单元放不下它,而且有关它的运算也不可能用一条指令来完成

    • 算法复杂度分析的数学基础

    算法复杂度分析中常用的一些数学基础知识,包括集合论、逻辑学、概率论以及代数领域的知识

    • p类问题

    目前人们已经证明了一些问题,它的时间复杂性时多项式的,这只须涉及一个实现它的时间复杂性时多项式阶的算法即可,例如分类问题(有成)排序问题。这样一类问题本称为P类问题

    • 顽型问题

    还有一类问题,人们已经设计出实现它的时间复杂性为指数阶的算法,并且已证明该问题不存在时间复杂性为多项式阶的算法(例如梵塔问题),这样一类问题称为顽型问题。

    • NP问题

    由这样一类问题,人们目前已设计的实现它的算法其时间复杂性为指数阶的,但还不能肯定有没有多项式阶的算法,例如m-可着色问题 。为了研究这类问题,人们又设计一种称为非确定型图灵机的计算模型,这些问题对应一个非确定型图灵机,而且可以在多项式时间内完成计算。人们称这类问题为NP问题,NP是Nondeterministic Polynomial的缩写。

    作为一本书,还有很多内容,看完是不可能的,到此为止,有个比较清晰的概念了

     

     

     

    展开全文
  • 问题的复杂性小结

    千次阅读 2017-10-29 12:21:13
    0与算法复杂性的区别 1P问题 2NP问题 3NP-Complete问题 4NP-Hard问题 5Non-NP问题 关系图0、与算法复杂性的区别 算法的复杂性是指解决问题的一个具体的算法的执行时间,这是算法的性质。 问题的复杂性是指这个问题...

    0、与算法复杂性的区别

    • 算法的复杂性是指解决问题的一个具体的算法的执行时间,这是算法的性质。
    • 问题的复杂性是指这个问题本身的复杂程度,是问题的性质。

      比如排序问题的复杂性是O(nlgn),但是排序算法有很多,冒泡法是O(n^2),快速排序平均情况下是O(nlgn)等等,排序问题的复杂性是指在所有的解决该问题的算法中最好算法的复杂性。

    • 问题的复杂性不可能通过枚举各种可能算法来得到,一般都是预先估计一个值,然后从理论上证明。

    1、P问题

    • 能在多项式时间内解决的问题。(即能找到计算复杂度可以表示为多项式的确定算法)

    2、NP问题

    • 不能在多项式时间内解决或不确定能不能在多项式时间内解决,但能在多项式时间验证的问题。
    • NP问题包括了P问题。

    3、NP-Complete问题

    • NP完全问题。这类问题中任何一个问题至今未找到多项式时间算法。

    4、NP-Hard问题

    • 如果所有NP问题都可以多项式归约到问题A,那么问题A就是NP-Hard。
    • 如果问题A既是NP-Hard又是NP,那么它就是NP-Complete。
    • NP-Hard不一定是NP问题。
    • NPC问题一定是NPH问题。

    5、Non-NP问题

    关系图

    五类问题的关系图

    展开全文
  • 算法复杂性分析及运算规则证明(一)

    千次阅读 2019-03-07 13:26:03
    我们有必要知道算法的复杂性是算法运行所需要的计算机资源的量,需要空间资源的量称为空间复杂度,同样需要时间资源的量称为时间复杂度。那麽这个量与什么有关系呢? 这个量应该是只依赖于要解决的问题的规模,算法...
  • 复杂性思维 中文第二版 翻译完成

    万次阅读 2018-04-15 11:40:53
    复杂性思维 中文第二版 来源:Think Complexity 译者:飞龙 版本:2.5 自豪地采用谷歌翻译 在线阅读 PDF格式 EPUB格式 MOBI格式 代码仓库 赞助我 协议 CC BY-NC-SA 4.0 KivyCN 学习资源 ...
  • 复杂性思维第二版 一、复杂性科学

    万次阅读 2017-10-27 21:44:26
    一、复杂性科学 原文:Chapter 1 Complexity Science 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 这本书的论点是,复杂性科学是一种“新型科学”,我借鉴自 Stephen Wolfram。2002年,Wolfram ...
  • 链接:https://pan.baidu.com/s/1kOxjQgesr7ML1N_uMHBMCQ 提取码:150a 若链接失效,请联系本人:18642984053@163.com
  • 正则表达式校验密码复杂性的规则

    千次阅读 2019-07-05 11:04:11
    最近做一个项目,需要校验密码复杂性,想了想还是用正则表达式来判断方便快捷; 因密码是数字+字母组成,就只校验数字和字母是否存在; 例: private String checkDataValid(String account, String pwd, String...
  • 复杂性研究面临的难题

    千次阅读 2018-01-02 00:00:00
    一、什么是复杂性目前无法表述清楚 在汉语中“复杂”一词的意思为“事物的种类、头绪等多而杂”。在《朗文当代英语词典》中,形容词complex被解释为:(1)难于理解、解释或处理,不清楚或不简单; (2)由许多密切...
  • 控制复杂性是计算机编程的本质

    千次阅读 2019-11-20 11:39:51
  • 密码复杂性规则

    万次阅读 2017-06-06 05:47:26
     如果使用的是数字和小写字母,那么复杂性是10位数字加上26位小写字母的6次方,即36的6次方。  如果使用数字、小写字母、大写字母,那么复杂性就是10位数字加上26位小写字母加上26位大写字母,即62的6次方。  ...
  • 算法复杂性和如何计算时间复杂度

    万次阅读 多人点赞 2017-01-17 17:16:13
    算法的复杂性有时间复杂性和空间复杂性之分 通常考虑3种情况下的时间复杂性:最坏,最好和平均情况下的计算复杂性;当然可操作性最好且最有实际价值的是最坏情况下的时间复杂性 T(n)=max(t(i)) 设i是算法A的一个输入...
  • 最近想学习一下DDD,下载了<领域驱动设计:软件核心复杂性应对之道> ,发现根本看懂,大家有好的学习路径么?我根本看不明白这本书描述了个啥。。
  • 领域驱动设计-软件核心复杂性应对之道 一本比较好的领域设计书
  • 软件系统复杂性(1/2)

    千次阅读 2018-01-05 14:07:25
    1.2节讨论软件系统的复杂性,可以说软件系统比自然界的复杂性还要高,并讨论了导致软件系统复杂的四大原因。1.3节总结了复杂系统的5大属性,读者可以结合1.1节中直观的实例对其进行思考与理解。 摘录:  某些...
  • 软件复杂性分析

    千次阅读 2015-09-27 18:53:21
     降低由复杂性引发软件错误的可能性;  确保软件产品的质量;  减少由软件设计方法和技巧使用不当而带来的复杂性。 2,产生原因(共6条):需求复杂、应用要求高;开发环境复杂;软件应用框架、结构及模型复杂...
  • 注:密码的复杂性在生产环境中一定要保留,安全第一,而且要养成习惯 但在非生产环境中,为了实验方便,一般都会取消密码的复杂性。本着实验的目的,这里我就取消了它。 开始,用图说话: 也可以使用下图的方式...
  • 软件固有的复杂性

    千次阅读 2012-08-22 16:34:37
    软件固有的复杂性 一颗垂死的恒星正处在塌缩的边缘,一名儿童在学习如何阅读,白细胞向病毒发起进攻——这是真实事件的几个例子,它们包含着真正可怕的复杂性。软件也可能包含巨大复杂性的元素,但是这里的复杂性...
  • 算法复杂性分析

    千次阅读 2013-09-25 13:07:33
    算法复杂性,这个东西,以前一直搞不大懂,很是苦恼。网上的资料不是很粗浅就是看不懂。大三了,看了门算法的课,根据老师的描述以及自己查证的资料,终于对这复杂度有了一定的了解。 算法复杂度主要体现在运行...
  • 造成软件复杂性的原因

    千次阅读 2016-04-04 09:51:34
    软件复杂性的几个原因: 1、问题域的复杂性,造成这种复杂性的主要原因,还是用户与开发者之间的“沟通问题”,用户常常对某个需求只存在一个模糊的概念,对具体要实现成一个什么样子没有特别明显的想法。并且由于...
  • public class Allpaixu { static int number=0; public static void main(String[] args) { int[] a = {26, 5, 98, 108, 28, 99, 100, 56, 34, 1 }; printArray(“排序前:”,a); MergeSort(a);...
  • 算法复杂性分析概述

    千次阅读 2019-04-25 18:03:56
    算法复杂性 = 算法所需要的计算机资源 算法的时间复杂性T(n)=T(n,i); 算法的空间复杂性S(n)= S(n,i) 。 其中n是问题的规模(输入大小)i表示算法及其输入。 算法的时间复杂度反映了程序执行时间随输入规模增长而...
  • 复杂性研究从20世纪末叶兴起,目前在国内外已成为许多学科领域内研究的前沿和热点。它涉及又一个新型的跨学科的方法论。虽然人们对“复杂性”概念还缺乏严格一致的定义,但大家都意...
  • 近似熵和样本熵都是对非稳定时间序列的复杂型的度量,其思想都是检测时间序列中的新的子序列产生概率。   近似熵和样本熵计算方法: 近似熵的计算方法 原料:具有N个均匀数据点的时间序列;需要进行切割的子片段...
  • 1.windows +R 输入:gpmc.msc 进入组策略管理界面。...4.禁用密码必须符合复杂性要求,设置密码长度最小值为0个字符(可设置为空),修改密码最短使用期限为0天(密码永远不会过期)。其他默认不变。...
  • 浅论 C++ 的复杂性

    千次阅读 2015-12-05 11:56:42
    如何应对 C++ 的复杂性 尽管 C++ 的复杂性有其产生的深刻背景,但复杂性确实是个问题。在实践上最突出的表现就是开发效率的降低,毕竟简单易用的工具能带来生产率的提高。但是 C++ 的复杂性导致了开发效率的降低只是...
  • 软件系统的复杂性

    千次阅读 2012-12-27 09:29:44
    问题域的复杂性:软件系统一般都是处理逻辑或业务规则比较复杂的问题。 管理开发过程的困难性:不存在标准的流程,且现有的各种流程都只适合特定的团队、项目或者产品。 软件实现的灵活性:软件的实现是没有标准的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,109,156
精华内容 843,662
关键字:

复杂性