精华内容
下载资源
问答
  • 本文通过讲解3个基本问题应用,提供了分治算法设计范式实践。第一个例子是对数组中逆序对进行计数算法(第3.2节)。这个问题与测量两个有序列表相似性有关,适用于根据自己知识向其他人按照他们偏好提供...

    本文通过讲解3个基本问题的应用,提供了分治算法设计范式的实践。第一个例子是对数组中的逆序对进行计数的算法(第3.2节)。这个问题与测量两个有序列表的相似性有关,适用于根据自己的知识向其他人按照他们的偏好提供优质的推荐(称为“协同筛选”)。第二个分治算法的例子是Strassen所发明的令人兴奋的矩阵相乘递归算法,它与迭代方法(第3.3节)相比,性能提升非常明显。第三个算法属于高级的选修知识,用于解决计算几何学的一个基本问题:计算平面上最近的点对(第3.4节)。

    3.1 分治法规范

    我们已经看过分治算法的一个经典例子:MergeSort(第1.4节)。概括地说,分治算法设计范式一般具有3个概念步骤。

    分治范式

    1.把输入划分为更小的子问题。

    2.递归地治理子问题。

    3.把子问题的解决方案组合在一起,形成原始问题的解决方案。

    例如,在MergeSort中,“划分”步骤把输入数组分成左半部分和右半部分,“治理”步骤是由Merge子程序(第1.4.5节)所实现的。在MergeSort和许多其他算法中,需要用到巧妙思维的时机正是在这最后一步。也有些分治算法的巧妙之处出现在第一个步骤(参见第5章的QuickSort)或者出现在递归调用的规格说明中(参见第3.2节)。

    3.2 以O(n log n)时间计数逆序对

    3.2.1 问题

    本节研究对一个数组中的逆序对计数的问题。所谓数组的逆序对,就是指一对元素“乱了序”,也就是出现在数组较前位置的元素比出现在较后位置的元素更大。

    问题:对逆序对进行计数

    输入:一个包含不同整数的数组A

    输出:A中的逆序对数量,即数组中符合i < j并且A[i] > A[j]的(i, j)对的数量。

    例如,已经排序的数组A没有任何逆序对。反过来的说法也是对的,未排序的数组至少有1对逆序对。

    3.2.2 一个例子

    考虑下面这个长度为6的数组:

    d8ac01ca7ab9b80a19002de482b7c259.png

    这个数组有几个逆序对呢?显而易见的一个例子就是5和2(分别对应于i=3和j=4)。这个数组还有另外2对逆序对:3和2以及5和4。

    小测验3.1

    包含6个元素的数组最多可能出现几对逆序对?

    (a)15

    (b)21

    (c)36

    (d)64

    (关于正确答案和详细解释,参见第3.2.13节)

    3.2.3 协同筛选

    为什么需要对数组中的逆序对进行计数呢?一个原因是想要计算一种数值相似度,该数值相似度用于对两个已排序列表之间的相似程度进行量化。例如,读者邀请一位朋友一起对两人都看过的10部电影按照从最喜欢到最不喜欢的顺序进行排列。怎么衡量两人的选择是“相似”或“不同”呢?解决这个问题的一种量化方法是通过一个包含10个元素的数组AA[1]表示读者的朋友从电影列表中所选择的最喜欢的电影,A[2]表示他其次喜欢的电影,以此类推,A[10]表示他最不喜欢的电影。这样,如果读者最喜欢的电影是《星球大战》,而这部电影在读者朋友的列表中只是出现在第5位,那么A[1] = 5。如果两人的排序是相同的,这个数组就是已经排序的,不存在逆序对。这个数组包含的逆序对越多,读者和朋友之间对电影评价的分歧就越多,对电影的偏好也更加不同。

    对已排序列表进行相似性测量的一个原因是进行协同筛选,这是一种用于生成推荐方案的方法。网站怎么推出关于产品、电影、歌曲、新闻故事等内容的建议呢?在协同筛选中,其思路是寻找其他具有相似偏好的用户,然后推荐他们所喜欢的内容。因此协同筛选需要用户之间“相似性”的形式定义,而计算逆序对能够捕捉到这个问题的一些本质。

    3.2.4 穷举搜索法

    计算数组的逆序对数量的速度有多快?如果对此缺乏概念,那可以尝试使用穷举搜索法。

    用穷举搜索法对逆序对进行计数

    输入:包含n个不同整数的数组A

    输出:A中逆序对的数量。

    numInv := 0for i := 1 to n − 1 do  for j := i + 1 to n do    if A[i] > A[j] then      numInv := numInv + 1return numInv

    显然,这是一种正确的算法。它的运行时间是什么?根据小测验3.1的答案,我们知道循环的迭代次数与输入数组的长度n的平方成正比。由于这种算法每次迭代时执行的操作数量是常数级的,因此它的渐进性运行时间是Θ(n2)。记住,经验丰富的算法设计师的座右铭是:“还能做得更好吗?”

    3.2.5 分治法

    答案是肯定的,解决方案是运行时间为O(n log n)的分治算法,它的性能较之穷举搜索法有了很大的提高。它的“划分”步骤和MergeSort算法的完全一样,一个递归调用作用于数组的左半边,另一个递归调用作用于数组的右半边。为了理解这两个递归调用之外所需要完成的剩余工作,我们把一个长度为n的数组A中的逆序对(i, j)分为3类。

    (1)左逆序对:逆序对的ij都位于数组的左半部分(即

    07fd9256760aa6a147673335cf9eb073.gif

    )。

    (2)右逆序对:逆序对的ij都位于数组的右半部分(即

    5b1a96612c7075346f8c051c0014e6e7.gif

    )。

    (3)分离逆序列:逆序对的i位于数组的左半部分,j位于数组的右半部分(即

    06cd9932bdbd23d50c7e2df3ad391202.gif

    )。

    例如,在第3.2.2节的那个6元素数组例子中,3个逆序对都是分离逆序对。

    第1个递归调用作用于输入数组的左半部分,它采用递归的方式对左逆序对进行计数(没有其他任何操作)。类似,第2个递归调用对所有的右逆序对进行计数。剩余的任务是对那些并没有被这两个递归调用所计数的逆序对(即分离逆序对)进行计数。这是这个算法的“组合”步骤,我们需要为它实现一种特殊的线性时间的子程序,类似于MergeSort算法中的Merge子程序。

    3.2.6 高级算法

    我们的分治算法可以翻译为下面的伪码,用于计数分离逆序对的CountSplitInv子程序目前还没有实现。

    CountInv

    输入:包含n个不同整数的数组A

    输出:A中逆序对的数量。

    if n = 0 or n = 1 then // 基本条件  return 0else  leftInv := CountInv(first half of A)  rightInv := CountInv(second half of A)  splitInv := CountSplitInv(A)  return leftInv + rightInv + splitInv

    第一个和第二个递归调用分别对左逆序对和右逆序对进行计数。假如CountSplitInv子程序可以正确地对分离逆序对进行计数,CountInv就可以正确地计算逆序对的总数。

    3.2.7 关键思路:站在MergeSort的肩膀上

    要想使对数组的分离逆序对进行计数的算法具有线性运行时间是个很有雄心的目标。分离逆序对的数量可能很多:如果A按顺序包含了

    ea97ba20715dcb0dd4e7b10a38960049.gif

    ,然后按顺序又包含了

    90014c84747d8df4042c71797d62a38c.gif

    ,那么一共就有

    27627ac4d6a5f6811fe4d7458153ce08.gif

    个分离逆序对。我们怎么才能在线性工作时间内完成平方级数量的工作呢?

    思路就是在设计递归式计数逆序对的算法时站在MergeSort算法的肩膀之上。它除了递归调用之外还需要完成一些任务,才能更方便地计数分离逆序对的数量[2]。每个递归调用不仅负责对指定部分的数组中的逆序对进行计数,而且要返回该数组的排序版本。我们已经知道(通过定理1.2)排序是一种可以尽情使用的基本操作,其运行时间为O(n log n)。因此,如果我们所争取的运行时间上限为O(n log n),那么有什么理由不进行排序呢?我们很快就会看到,对两个已经排序的子数组进行归并这个任务简直就是为对数组中的分离逆序对进行计数这个任务量身定做的。

    下面是第3.2.6节的伪码经过修订的版本,它在计数的同时还对数组进行排序。

    Sort-and-CountInv

    输入:包含n个不同整数的数组A

    输出:包含与A中相同整数的、已经排序的数组B,以及数组A中的逆序对的数量。

    if n = 0 or n = 1 then // 基本条件  return (A; 0)else  (C; leftInv) := Sort-and-CountInv(first half of A)  (D; rightInv) := Sort-and-CountInv(second half of A)  (B; splitInv) := Merge-and-CountSplitInv(C;D)  return (B; leftInv + rightInv + splitInv) 

    我们仍然需要实现Merge-and-CountSplitInv子程序。我们知道如何用线性时间对两个已经排序的列表进行归并,但是怎么才能利用这个成果对分离逆序对进行计数呢?

    3.2.8 重温Merge

    为了观察为什么合并已经排序的数组可以自然地发现分离逆序对,我们重新回顾一下Merge子程序的伪码。

    Merge

    输入:已经排序的数组CD(长度分别为n/2)。

    输出:已经排序的数组B(长度为n)。

    用于简化问题的先决条件:n是偶数。

    i := 1, j := 1for k := 1 to n do  if C[i] < D[j] then    B[k] := C[i], i := i + 1  else             // D[j] < C[i]    B[k] := D[j], j := j + 1

    重温一下,Merge子程序根据索引平行地(用i访问C,用j访问D)访问每个已经排序的子数组,并按从左向右的排序顺序生成输出数组B(使用索引k)。

    在循环的每次迭代中,这个子程序寻找目前为止尚未被复制到B中的最小元素。由于CD都已经排序,所以C[i]和D[j]之前的所有元素都已经被复制到B中,仅有的两个候选元素就是C[i]和D[j]。Merge子程序判断这两个元素哪个更小,并把它复制到输出数组的下一个位置。

    如果需要计算分离逆序对的数量,Merge子程序需要做些什么呢?我们首先讨论一种特殊的情况,就是数组A中不包含任何分离逆序对,A中的每个逆序对要么是左逆序对,要么是右逆序对。

    小测验3.2

    假设输入数组A不存在分离逆序对,那么已经排序的子数组CD之间存在什么关系?

    (a)C包含A中最小的元素,D包含第二小的,C包含第三小的,依次类推。

    (b)C的所有元素都小于D的任何元素。

    (c)C的所有元素都大于D的任何元素。

    (d)没有足够的信息可以回答这个问题。

    (关于正确答案和详细解释,参见第3.2.13节)

    在解决了小测验3.2之后,我们可以看到Merge在数组不存在分离逆序对时会执行一些特别无聊的操作。由于C的每个元素都小于D的每个元素,所以最小的元素总是出现在C中(除非C中不再剩下任何元素)。因此Merge子程序只是把CD连接在一起,它首先复制C的所有元素,然后复制D的所有元素。这是不是意味着当D的一个元素被复制到输出数组时,分离逆序对与C中剩余元素的数量有关呢?

    3.2.9 Merge和分离逆序对

    为了进一步证明自己的直觉,我们考虑对一个包含6个元素的数组A={1, 3, 5, 2, 4, 6}(来自第3.2.2节)运行MergeSort算法,参见图3.1。这个数组的左半部分和右半部分都已经排序,因此不存在左逆序对和右逆序对,两个递归调用都返回0。在Merge子程序的第1次迭代时,C的第1个元素(1)被复制到B。此时没有任何与分离逆序对有关的信息,事实上这个元素也与分离逆序对没有任何关系。但是,在第2次迭代时,“2”被复制到输出数组中,但此时C中仍然剩下元素3和5。这就说明了A中有2个分离逆序对,也就是与2相关联的两个逆序对。在第3次迭代时,3从C被复制到B,此时没有其他分离逆序对与这个元素有关。当4从D被复制到B时,数组C中仍然还有一个元素5,提示A中还有第3个也就是最后一个分离逆序对(元素5和元素2)。

    b1cf850a90c2e266b3566455433b3e53.png

    图3.1 Merge子程序的第4次迭代面对的是已经排序的子数组{1, 3, 5}和{2, 4, 6}。从D复制元素“4”,此时“5”仍然留在C中,显示了与这两个元素相关的分离逆序对

    下面这个辅助结论表示上面这个例子的模式可以推及到一般情况:在Merge子程序把第2个子数组D中的元素y复制到输出数组的当次迭代时,与y有关的分离逆序对的数量就是此时C中仍然剩余的元素的数量。

    辅助结论3.1  假设A是个数组,CD分别是该数组左半部分和右半部分已经排序的子数组。A中左半部分的元素xA中右半部分的元素y当且仅当下面这种情况成立时才能构成一对逆序对:在Merge子程序中输入CDyx之前被复制到输出数组。

    证明:由于输出数组是按从左向右的顺序生成的,因此xy中较小的那个先被复制。由于x位于A的左半部分,y位于右半部分,因此当且仅当x > yxy才会构成一对逆序对,也就是当且仅当yx之前被复制到输出数组中时xy才会构成一对逆序对。Q.e.d.

    3.2.10 Merge_and_CountSplitInv

    根据辅助结论3.1所提供的结论,我们可以对Merge的实现进行扩展,实现Merge-and-CountSplitInv。

    我们用一个变量记录分离逆序对的当前计数,每次当一个元素从右半部分的子数组D复制到输出数组B时,就把当前计数加上左半部分的子数组C中仍然剩余的元素数量。

    Merge-and-CountSplitInv

    输入:已经排序的数组CD(长度均为n/2)。

    输出:已经排序的数组B(长度为n)以及分离逆序对的数量。

    用于简化问题的先决条件:n是偶数。

    i := 1, j := 1, splitInv := 0for k := 1 to n do  if C[i] < D[j] then    B[k] := C[i], i := i + 1  else              // D[j] < C[i]    B[k] := D[j], j := j + 1    splitInv := splitInv + 
    dd24c7da871d1b70fecc63aef502c458.gif
                   #C中剩余元素的数量return (B; splitInv)

    3.2.11 正确性

    Merge-and-CountSplitInv的正确性是由辅助结论3.1所保证的。每个分离逆序对只涉及第2个子数组中的1个元素,并且当y被复制到输出数组时,这个逆序对正好被计数1次。整个Sort-and-CountInv算法(第3.2.7节)的正确性取决于下面的条件是否都得到满足:第1个递归调用正确地计算左逆序对的数量,第2个递归调用正确地计算右逆序对的数量,Merge-and- CountSplitInv返回剩余逆序对(分离逆序对)的正确数量。

    3.2.12 运行时间

    我们还可以借助前面已经完成的MergeSort算法运行时间的分析,对Sort-and- CountInv算法的运行时间进行分析。首先考虑单次调用Merge-and-CountSplitInv的运行时间,提供给它的是2个长度为

    7cf9990653c38affbc2d0024eb502352.gif

    的子数组。和Merge子程序一样,它在循环的每次迭代时执行常数级的操作,另外还有一些常数级的其他操作,运行时间为O(

    dd55d071927324a2b304921fd474d8b1.gif

    )。

    回顾第1.5节对MergeSort算法运行时间的分析,我们可以看到这个算法具有3个重要的属性,导致它的运行时间上界是O(n log n)。首先,这个算法的每次调用都产生两个递归调用。其次,每一层递归调用的输入长度只有上一层的一半。最后,每个递归调用所完成的工作与输入长度呈正比(不包括下层递归调用所完成的工作)。

    由于Sort-and-CountInv算法具有这些属性,所以第1.5节的分析对它也是适用的,因此它的运行时间上界是O(n log n)。

    定理3.2(计数逆序对)对于长度大于等于1的数组A,Sort-and-CountInv算法计算A中逆序对的数量运行时间是O(n log n)。

    3.2.13 小测验3.1~3.2的答案

    小测验3.1的答案

    正确答案(a)。这个问题的正确答案是15。逆序对的最大可能数量就是

    a44e167a8d99f62f8b7a0659bc8a372c.gif

    中满足i<j的( ij ) 对的数量。这个数量用

    876f2dfcb46c5c30488f0f8052750cc6.gif

    表示,意思是“6中选2”。一般而言

    093f38f7aa57c42a44cdc90256ce285c.gif

    ,因此

    876f2dfcb46c5c30488f0f8052750cc6.gif

    =15[3]。在一个反序排列的6元素数组(6, 5, 4, 3, 2, 1)中,每一对元素都是逆序的,因此这个数组一共有15个逆序对。

    小测验3.2的答案

    正确答案(b)。在不包含分离逆序对的数组中,左半部分数组中的所有元素都小于右半部分数组中的所有元素。如果左半部分数组中的某个元素A[i](

    4a9c0f059f8ebdd2ffa6f26cc11260d9.gif

    )大于右半部分数组中的某个元素A[j](

    33107b12f3b1e266c38dba5ed45c6dfa.gif

    ),则(i, j)就构成分离逆序对。

    3.3 Strassen的矩阵相乘算法

    本节把分治算法设计范式应用于矩阵相乘这个问题,这类算法的巅峰无疑是Strassen的矩阵相乘算法,它的运行时间令人吃惊,竟然低于立方级。这个算法是精巧的算法设计发挥神奇威力的一个典型例子。它可以让我们看到精妙的算法是怎样把简单解决方案远远抛在身后的,即使是对于极端基本的问题。

    3.3.1 矩阵相乘

    假设XY

    4f71f24db96ed6f92d6e5e14fedf8df3.gif

    的整数矩阵,每个矩阵包含

    b52efdafa7be015cc31de0c9ca5e88e5.gif

    个元素。在矩阵的乘积·中,Z中第i行第j列的元素Zij被定义为X的第i行和Y的第j列的数量积[4] (见图3.2)。即

    5ab886f4c57d89c7c684ee38cd52db6f.gif

    (3.1)

    468f3103a9b99bcef7140dbde2039329.png

    图3.2 矩阵乘积X · Y的(i, j)项是X的第i行和Y的第j列的数量积

    3.3.2 例子(n = 2)

    现在我们来深入探讨n = 2这种情况。我们可以用8个参数来描述两个2×2的矩阵:

    ccba0157a9728f3770e9f5c29563f022.gif

    在矩阵的乘积X · Y中,左上角的元素是X的第1行和Y的第1列的数量积,即

    0b2f4148f8adbb024f8c18f7852c8256.gif

    。一般而言,对于像上面这样的矩阵XY

    eb75d442a54814f629053f1fd6c54165.gif

    (3.2)

    959bddddebbe25c1923370497d9c9806.png

    《算法详解(卷1)——算法基础》

    作者:[美] 科里•奥尔索夫(Cory Althoff)

    这本书在美亚评分4.7,在作者的在线算法课程的基础之上编写的,是四卷本系列的第1卷。这个在线课程2012年起就定期更新,它建立在作者在斯坦福大学教授多年的本科课程的基础之上。也许你有所耳闻,这本书就是《算法详解(卷1)——算法基础》。如果你更喜欢听和看,可以在YouTobe上搜索这本书的主题课程,免费观看。

    《算法详解(卷1)——算法基础》作者蒂姆·拉夫加登(Tim Roughgarden)是斯坦福大学计算机科学系的教授,也是该校管理科学和工程系的客座教授,他从2004年开始教授和研究算法。本书是他的《算法详解》四部曲的第一卷。

    这本书详细讲解算法基础,展现算法本质 ,是一本囊括基本算法知识的详解指南。集斯坦福大学教授多年教学经验,深入浅出,通俗易懂。

    展开全文
  • 模板方法模式(Template Method Pattern)又简称模板模式,属于行为型设计模式,它主要是在父类中定义一个统一操作算法框架,而将具体实现延迟到子类中,使得不同子类按照父类已定义好框架结构进行实现不同...

    1 定义

    模板方法模式(Template Method Pattern)又简称模板模式,属于行为型设计模式,它主要是在父类中定义一个统一的操作的算法框架,而将具体实现延迟到子类中,使得不同的子类按照父类已定义好的框架结构进行实现不同的算法细节。这类行为的思想实现也就是控制反转Inversion of Control,缩写IOC的体现。例如现实场景中煮饭,所有人都公知同样的步骤:洗米、放水和煮法,不同的人或不同的工具煮出来味道也是会不一样的。

    2 实现

    模板方法模式一般包含了2个角色,分别是:

    1. 抽象父类(AbstractClass):用于定义算法的骨架。例如上述定义举例煮饭中的洗米、放水和煮法。
    2. 具体实现子类(ConcreteClass):继承抽象父类,实现父类中的抽象方法。例如上述定义举例的煮饭的实际实现。

    抽象父类,提供一个统一的调用入口方法,方法内进行所有抽象方法的调用:

    public abstract class Cook {
        protected abstract void wash();
        protected abstract void addWater();
        protected abstract void mode();
        public void cook() {
            wash();
            addWater();
            mode();
        }
    }

    具体实现子类,实现父类所声明的抽象方法,它是具体算法的实现:

    public class CasseroleCook extends Cook {
        @Override
        protected void wash() {
            System.out.println("用砂锅洗米");
        }
        @Override
        protected void addWater() {
            System.out.println("凭感觉加适量的水");
        }
        @Override
        protected void mode() {
            System.out.println("猛火煮出锅巴");
        }
    }
    
    public class RiceCookerCook extends Cook {
        @Override
        protected void wash() {
            System.out.println("用电饭煲洗米");
        }
        @Override
        protected void addWater() {
            System.out.println("对应刻度加适量的水");
        }
        @Override
        protected void mode() {
            System.out.println("一键香甜柴火饭");
        }
    }

    客户端:

    public class Main {
        public static void main(String[] args) {
            Cook riceCookerCook = new RiceCookerCook();
            riceCookerCook.cook();
    
            Cook casseroleCook = new CasseroleCook();
            casseroleCook.cook();
        }
    }

    输出结果:

    用电饭煲洗米
    对应刻度加适量的水
    一键香甜柴火饭
    
    用砂锅洗米
    凭感觉加适量的水
    猛火煮出锅巴

     2.1 钩子函数

    钩子就是给子类一个授权,让子类自己可以来决定模板方法中的某此逻辑是否一定要执行。就比如上述示例中,我们可以让去砂锅煮饭时加上一些腊味来实现特色煲仔饭。

    抽象父类:

    public abstract class Cook {
        protected abstract void wash();
        protected abstract void addWater();
        protected abstract void mode();
        protected abstract boolean isAddAccessories();
        protected abstract void addAccessories();
        public void cook() {
            wash();
            addWater();
            if (isAddAccessories()) {
                addAccessories();
            }
            mode();
        }
    }

    具体实现子类:

    public class CasseroleCook extends Cook {
        @Override
        protected void wash() {
            System.out.println("用砂锅洗米");
        }
        @Override
        protected void addWater() {
            System.out.println("凭感觉加适量的水");
        }
        @Override
        protected void mode() {
            System.out.println("猛火煮出锅巴");
        }
        @Override
        protected boolean isAddAccessories() {
            return true;
        }
        @Override
        protected void addAccessories() {
            System.out.println("加一些腊味");
        }
    }
    
    public class RiceCookerCook extends Cook {
        @Override
        protected void wash() {
            System.out.println("用电饭煲洗米");
        }
        @Override
        protected void addWater() {
            System.out.println("对应刻度加适量的水");
        }
        @Override
        protected void mode() {
            System.out.println("一键香甜煲火饭");
        }
        @Override
        protected boolean isAddAccessories() {
            return false;
        }
        @Override
        protected void addAccessories() {
        }
    }

    客户端:

    public class Main {
        public static void main(String[] args) {
            Cook riceCookerCook = new RiceCookerCook();
            riceCookerCook.cook();
    
            Cook casseroleCook = new CasseroleCook();
            casseroleCook.cook();
        }
    }

    输出结果:

    用电饭煲洗米
    对应刻度加适量的水
    一键香甜煲火饭
    
    用砂锅洗米
    凭感觉加适量的水
    加一些腊味
    猛火煮出锅巴

    3 总结

    模板方法模式其实就是当不变和可变的行为在方法的子类实同中混合在一起时,将相同一系列行为步骤搬移到父类进行定义,去除子类中重复的代码,子类只保留不变的行为的实现细节逻辑。

    模板方法模式其实是控制反转思想的实现。通过父类调用其子类的操作,子类对父类进行扩展行为,所以往往这样也会导致一定程度上代码阅读的难度。就如传统的程序开发中往往是从main 函数开始,调用各种各样的库来完成一个程序,这样开发者控制着整个运行过程。而如果使用的是框架开发时,框架就变成了控制着整个运行过程。正如Android开发中,我们大多情况下不必关心main函数什么时候开始,只需要知道Activity的onCreate、onStart等生命周期的回调,onCreate 名称是不能修改的,但我们需要对其进行实现逻辑,因为使用了框架,享受框架带来福利的同时,就要遵循框架的规则。所以说,模板方法模式是所有框架最基本的特征。

     

     

    展开全文
  • 决策树本质上是通过一系列规则对数据进行...决策树的基本概念首先我们看一下下面这张表,这个表富含的信息是:用天气、温度,湿度等状况来决定是否出去玩。Outlook、Temperature、Humidity、Windy是一个个特征,这...

    决策树本质上是通过一系列规则对数据进行分类的过程。

    它是一个类似于流程图的树结构,其中每一个树节点表示一个属性上的测试,每一个分支代表一个属性的输出,每一个树叶节点代 表一个类或者类的分布,树的最顶层是树的根节点。

    决策树的基本概念

    首先我们看一下下面这张表,这个表富含的信息是:用天气、温度,湿度等状况来决定是否出去玩。

    a822120202ff77d4afc15689df46a8bb.png

    Outlook、Temperature、Humidity、Windy是一个个特征,这些特征由信息量组成,是否Play是根据这些特征所预测出的一个结果。

    可以说,决策树是通过给出的信息量去预测一个结果。

    优缺点

    优点:计算量少,显示清晰

    缺点:过拟合

    (过拟合:指为了得到一致假设而使假设变得过度严格。避免过拟合是分类器设计中的一个核心任务。通常采用增大数据量和测试样本集的方法对分类器性能进行评价。)

    决策树算法的分类

    ·ID3算法:使用信息增益作为分裂的规则,信息增益越大,则选取该分裂规则。

    ·C4.5算法:使用信息增益率作为分裂规则,此方法避免了ID3算法中的归纳偏置问题,因为ID3算法会偏向于选择类别较多的属性(形成分支较多会导致信息增益大)。

    ·CART算法:既可以做分类,也可以做回归。只能形成二叉树。

    信息熵

    在信息论中,熵(entropy)是随机变量不确定性的度量,也就是熵越大,则随机变量的不确定性越大。

    过拟合是因为我们在所选择的信息对于结果的过度严格出现的现象,因此熵是用来描述信息源对结果的影响程度。

    设X是一个取有限个值得离散随机变量,其概率分布为:

    fbebefb9ce00a5bb206b2b03da2f0c60.png

    信息熵的公式:

    5c52bd60af05b2b4d70da2b9643f4075.png

    由上式知道,信息量越小,熵越小,信息量越大,熵越大。

    信息增益

    我们知道决策树是由一个节点出发依次判断的,最开始的那个节点称为根节点,那么如何确定根节点呢?

    根节点一般是对结果影响最大的那个,例如在你报考大学的时候,有你的朋友、周边亲戚、父母给你建议,那么谁的影响更大呢?是父母(大多数情况下),所以父母此时就作为根节点。

    特征对结果的影响程度用信息增益来衡量。

    信息增益表示由于得知特征A的信息后而使得数据集D的分类不确定性减少的程度,定义为:

    Gain(D,A) = H(D) – H(D|A)

    即集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(H|A)之差。

    选择划分后信息增益大的作为划分特征,说明使用该特征后划分得到的子集纯度越高,即不确定性越小。

    因此我们总是选择当前使得信息增益最大的特征来划分数据集。

    决策树的构造过程

    决策树的构造过程一般分为三个部分,特征的选择,决策树的生成以及剪枝。

    01 特征的选择

    特征选择表示从众多的特征中选择一个特征作为当前节点分裂的标准,如何选择特征有不同的量化评估方法,从而衍生出不同的决策树。

    如ID3通过信息增益选择特征;C4.5通过信息增益比选择特征;CART通过Gini指数选择特征······

    ·计算系统熵

    一共有14条记录,值为yes的9条,值为no的记录为5条,称这个样本里有9个正例,5个负例,熵记为:E(S)

    b0a099b7ed841181a9a18efa1f06537d.png

    ·以wind为例计算熵和信息增益

    在wind中,值为false的记录有8条,正例6个,负例2个,值为true的记录有6条,正例3个,负例3个,计算相应的熵:

    89cce2e7d848b3aabd09195364f74665.png

    计算wind信息增益:

    720f6912fbd7d9a190f2d052a0012d1a.png

    接下来我们用同样的方法计算其余特征的信息增益:

    ·Humidity

    e026c33104ae00b80b86f00780e708c5.png

    ·Outlook

    1e140948368a60d4dca3aca8e039b677.png

    ·Temperature

    961469ebe471c56680ae15821d28c0bc.png

    可以看到天气(Outlook)的信息增益最大,因此天气是决定出不出去玩的重要影响因素,因此Outlook是根节点。

    02 决策树的生成

    根据选择的特征评估标准,从上至下递归地生成子节点,直到数据集不可分则停止决策树停止生长。

    这个过程实际上就是使用满足划分准则的特征不断的将数据集划分成纯度更高,不确定行更小的子集的过程。

    对于当前数据集的每一次划分,都希望根据某个特征划分之后的各个子集的纯度更高,不确定性更小。

    根据特征选择的数据,由上至下可以画出这棵决策树。

    ·由特征选择的结果可知,信息增益最大的特征是Outlook,得出第一步如下图所示;

    1ef1886a695bd8dadeba1638d94d3311.png

    ·上图中,针对sunny中的子训练数据集分支,有两个类别,该分支中有3个实例属于no类,有2个实例属于yes类,求类别属性新的信息熵以及三个类别的信息增益,选出信息增益最大的为Humidity。(计算方法与特征选择一样,不再一一展示)

    445ea0e8cbb63fd703ec5a855e974106.png

    ·在mild对应的数据子集中,Humidity和windy的信息增益是相同的,因为在该分组中,yes元组的比例比no元组的大,所以直接写yes;在cool对应得子集中,直接将两个结果分开即可,最终得到的决策树图如图所示:

    1feeff5dbbf35904c6f04e9b26cf45af.png

    03 决策树的裁剪

    决策树容易过拟合,一般需要剪枝来缩小树结构规模、缓解过拟合。

    在上面的例子当中,我们使用的是ID3的算法,以信息熵为度量,用于决策树节点的属性选择,每次优选信息量最多的属性,以构造一颗熵值下降最快的决策树,到叶子节点处的熵值为0,此时每个叶子节点对应的实例集中的实例属于同一类。

    ·先剪枝:当分到一定程度,就不向下增长树了

    ·后剪枝:把树完全建好后,根据类的纯度来进行树的裁剪

    b8978a4e3f563e365d523b26a4f0f7a2.png

    想要获取数据科学相关知识,请关注我们的公众号:DC学习助手

    展开全文
  • 2021计算机考研:计算机操作系统的五大块复习第一:操作系统概述。操作系统概述这部分内容不会...重点是要掌握进程的基本特征、进程的状态及其相互转换的条件和过程;进程与线程的区别和联系;进程通信的基本类型...

    1cc176a0db8c788a15b4936809c8db6c.png

    2021计算机考研:计算机操作系统的五大块复习法

    第一:操作系统概述。操作系统概述这部分内容不会出现大题。一般是以基本原理和概念的形式为主,属于识记形式的题目。重点是操作系统的定义、操作系统的特征和主要功能等。

    第二:进程管理。进程管理是考试的热门,考点即可以出现在选择题中,也可出在综合应用题中。重点是要掌握进程的基本特征、进程的状态及其相互转换的条件和过程;进程与线程的区别和联系;进程通信的基本类型;调度的基本概念、时机、切换过程,掌握各种调度算法及其环境,并会用算法进行计算;进程同步相关概念、同步互斥机制;死锁的定义、死锁产生的四个必要条件、熟练掌握死锁的预防、死锁的避免,死锁解除的原理与方法。

    从往年试题来看,在综合应用题中,考了一道信号量机制的应用题,这部分是操作系统科目的难点,也是考试的热点。

    第三:内存管理。内存管理这部分内容也要作为重点进行复习,这部分内容的考查方式也很灵活。建议重点复习内存连续分配算法、非连续分配管理方式、虚拟内存请求分页的基本原理,页面置换算法等。虚拟内存这部分内容可以结合计算机组成原理科目中虚拟存储器来进行复习。

    第四:文件管理。文件管理这部分内容的重点是文件的几种逻辑物理结构,目录的管理和磁盘管理,重点掌握磁盘管理各种调度算法的基本原理及其应用。

    第五:输入/输出管理。输入/输出管理这部分重点掌握四种输入/输出控制方式特点及相互比较、中断处理、SPOOLing技术,提高性能的缓冲策略等。

    展开全文
  • 基本方法: 输入空间为n维向量集合,输出空间为标记类集合。训练数据集T={(x1,y1), ...朴素贝叶斯实际上学习到生成数据机制,所以属于生成模型。条件独立假设等于是说用于分类的特征在类确定条件下都是条件
  • 当保形场属于高维表示时,即携带两个以上内部对称指数时,人们可能还会研究泛化。 在这里,我们考虑3-基本(“ 3-plet”)表示情况。 一种动机是与多个M5膜理论推测联系:启发式论点表明,它可能与具有AdS...
  • 1 引言在机器视觉检测中,轻微凹凸检测一直是个难题,这类特征在传统的均匀光照条件下,成像对比度很低甚至难以成像,因其只在投射的光源边界处能被观测得到,...其属于主动测量法的一种,它的硬件一般由工业相机、...
  • 简介 K近邻(knn)是一种基本的分类与回归方法...算法思路:如果一个样本在特征空间中k个最相似(即特征空间中最邻近)样本中大多数属于某一个类别,则该样本也属于这个类别。 k近邻模型三个基本要素: ...
  • 阈值分割方法:利用图像中要提取目标物体和背景在灰度上差异,选择一个合适阈值,通过判断每一个像素点的特征属性来确定该像素点属于目标区还是背景区域,从而产生二值图像。确定一个最优阈值是分割关键  ...
  • 线性回归原理及实践(牛顿

    千次阅读 2019-03-21 20:53:36
    回归算法和分类算法都属于监督学习算法,不同的是分类算法中标签的是一些离散的值,代表不同的类别,而在回归算法中,标签是一些连续的值,回归算法需要训练得到样本特征到这些连续标签之间的映射。线性回归是一类...
  • 简介 K近邻(knn)是一种基本的分类与回归方法。...如果一个样本在特征空间中k个最相似(即特征空间中最邻近)样本中大多数属于某一个类别,则该样本也属于这个类别。 k近邻模型三个基本要素: k值选...
  • 本文所研究计算机图形学若干基本算法,包括:裁剪算法、多边形 布尔运算、曲线边多边形分割算法、曲线边多边形面积算法、高维空间距 离算法和主成分回归分析(PCR),具体工作如下: 平面多边形各种分解表示方法...
  • K近邻

    2018-01-04 16:12:09
    K近邻法的三个基本要素是k值的选择、距离度量和分类决策规则。(推导公式太麻烦,偷懒了,详细证明过程或者kd树推荐查看李航的《统计学习方法》) 主要思路: 在训练数据集中找到与该实例最邻近的k个实例,这k个...
  • k近邻

    2017-11-25 15:58:02
    1.综述k近邻(k-nearest neighbor,kNN)是一种基本的分类与回归方法。接下来只叙述分类方法。kNN是一种多分类方法,没有显示学习过程,它是利用训练数据集对特征向量空间进行划分,作为分类模型。 kNN描述...
  • 应用水压致裂对新集矿区原地应力进行了测量,确定原地系统深部围岩应力状态,即原地应力量值和方向,研究获得了矿区地应力赋存规律和基本特征。测量结果分析表明:新集矿区地应力场以水平应力为主导,属于构造...
  • R语言 k近邻

    千次阅读 2018-03-12 22:28:10
    基本思路:如果一个样本在特征空间中k个最相似(即特征空间中最邻近)样本中大多数属于一个类别,则该样本也属于这个样本。在k近邻算法中,所选择邻居都是已经正确分类来决定待分样本所属类别。 k临近...
  • k-d树与特征匹配

    千次阅读 2017-02-28 13:49:06
    特征匹配算子大致可以分为两类。一类是线性扫描即将数据集中点与查询点逐一进行距离比较,也就是穷举,缺点...索引树属于第二类,其基本思想就是对搜索空间进行层次划分。 根据划分空间是否有混叠可以分为Clippi
  • 机器学习入门之K近邻

    千次阅读 2016-11-10 16:50:31
    K-近邻法的输入为实例特征向量,对应于特征空间的点;输出为实例的类别,可以去多类。 K-近邻算法直观、简单:给定一个训练数据集,对新的输入实例,在训练实例中找到与该实例最邻近的k个实例,这k个实例多数属于某...
  • K近邻法的输入为实例的特征向量(特征空间的点),输出为实例的类别,可以取多类。 K近邻算法假设给定一个训练数据集,其训练数据集实例的类别已定,对新的输入实例,找出新实例K个最近邻的训练点,根据K个最近邻...
  • 机器学习-k近邻

    2019-02-28 21:09:50
    k近邻:是一种基本分类与回归方法。给定一个实例,在训练数据中找到与该实例最邻近k个实例,这k个实例多数属于某个类,就把该实例分为这个类。 k近邻三要素:k值选择,距离度量方式和分类决策规则。 ...
  • 第三章 K近邻

    2018-05-25 18:45:29
    kkk近邻(k−NN)(k−NN)(k-NN)是一种基本分类与回归方法。这里只讨论分类问题中kkk近邻。 输入:实例的特征向量,对应于特征空间点; 输出:实例类别,可以取多类 直观解释:给定一个训练数据集,对新...
  • 1.模型:k近邻使用模型对应于对特征空间划分。 k近邻中,当训练集、k值、距离度量(如欧式距离)及分类决策规则确定后,对于任何一个新输入实例,它所属类唯一地确定。 模型三个基本要素:k值选择、...
  • 第三章 k近邻

    2018-07-01 16:55:11
    第三章 kkk 近邻 ...思想:给定一个训练数据集,对新输入实例,在训练数据集中找到与该实例最邻近 kkk 个实例,这 kkk 个实例多数属于某个类,就把该输入实例分为这个类。 输入:训练数据集 T={...
  • k近邻法的输入为实例的特征向量,对应于特征空间的点;输出为实例的类别,可以取多类。 (1)直观上:给定k个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的k个实例,这 个实例的多数属于某个类,...
  • 对于分类问题,k近邻法的输入为实例的特征向量,对应于特征空间的点;输出为实例的类别,属于监督学习。KNN算法实际上利用训练数据集对特征空间进行划分,并作为其分类的“模型”,不具有显式的...
  • 朴素贝叶斯法 总述 朴素贝叶斯法是基于贝叶斯定理与特征条件独立性假设的分类方法。对于给定的训练数据集,首先基于...1 朴素贝叶斯法的学习与分类 1.1 基本方法 设输入空间xxx包含于RnR^nRn为nnn维向量的集合,输出...
  • k近邻输入是实例的特征向量,对应特征空间点,输出为实例类别,可以取多类。 3.1 k近邻算法 给定一个训练数据集,对于新输入实例,找到与该实例最相近k个实例,这k个实例又属于某个类,就把该输入实例...
  • 参考文献:机器学习与数据挖掘参考文献 ...k近邻法的特殊情况是k=1的情形,称为最近邻算法。对于输入的实例点(特征向量)x,最近邻法将训练数据集中与x最邻近点的类作为x的类。 k近邻法没有显式的学习

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 174
精华内容 69
关键字:

属于法的基本特征的是