算法设计与分析
文章平均质量分 81
《算法设计与分析》以算法设计为核心,详细系统地介绍了数据结构和算法学的相关理论。在需要代码实践的部分,本系列使用了伪代码或Python代码实现。
von Neumann
技术日新月异,人类生活方式正在快速转变,这一切给人类历史带来了一系列不可思议的奇点。我们曾经熟悉的一切,都开始变得陌生。
展开
-
算法设计与分析——布隆过滤器(Bloom Filter)
布隆过滤器(Bloom Filter)是1970年由布隆提出的,是非常经典的以空间换时间的算法。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。具体的操作流程:假设集合里面有x个元素,且一共设置y个哈希函数。y查询某元素是否存在集合中的时,用同样的方法将待查询元素通过y个哈希映射到位数组上的y个点。如果y个点中任意一个点不为 1,则可以判断该元素一定不存在集合中。反之,如果y。原创 2022-10-06 17:24:56 · 10311 阅读 · 0 评论 -
算法设计与分析——位图(BitMap)
同时,若需查找或判断的数据类型为字符串等非整型数据,需要先通过Hash函数将其映射到整型数据,在这个过程中由于Hash函数的设定,可能将不同的数据映射成相同的整数,在判断某个字符串是否存在时就会导致假阳性。通常可以用来判断某个数据是否存在、某个ID是否登录等情况。位图(Bitmap),即位(Bit)的集合,可用于记录大量的。BitMap 的是用一个位来存放某种状态,适用于。,那么我们需要申请内存空间的大小为。假设需要查找的总数为。原创 2022-10-06 16:56:00 · 10130 阅读 · 0 评论 -
算法设计与分析——并查集(Union-find Disjoint Sets)
一些应用涉及将nnn个不同的元素分成一组不相交的集合。这些应用经常需要进行两种特别的操作:寻找包含给定元素的唯一集合合并两个集合一个不相交集合数据结构维护了一个不相交动态集的集合S={S1,S2,⋯ ,Sk}\mathbb{S}=\{S_1, S_2, \cdots, S_k\}S={S1,S2,⋯,Sk}。我们用一个代表来标识每个集合,它是这个集合的某个成员。在一些应用中,我们不关心哪个成员被用来作为代表,仅仅关心的是2次查询动态集合的代表中,如果这些查询没有修改动态集合,则这两次查询应该原创 2022-02-25 23:41:44 · 12490 阅读 · 0 评论 -
算法设计与分析——哈夫曼树/赫夫曼树(Huffman Tree)和哈夫曼编码/赫夫曼编码(Huffman Coding)
赫夫曼编码可以很有效地压缩数据:通常可以节省20%~90%的空间,具体压缩率依赖于数据的特性。我们将待压缩数据看做字符序列。根据每个字符的出现频率,赫夫曼贪心算法构造出字符的最优二进制表示。原创 2021-12-26 15:06:56 · 12224 阅读 · 1 评论 -
算法设计与分析——顺序统计量:最坏情况为线性时间的选择算法
我们现在来看一个最坏情况运行时间为O(n)O(n)O(n)的选择算法。像randomized_select(arr, low, high, i)一样, 下文所述的选择算法通过对输入数组的递归划分来找出所需元素,但是,在该算法中能够保证得到对数组的一个好的划分。 SELECT使用的也是来自快速排序的确定性划分算法 PARTITION(见7.1节),但做了修改,把划分的主元也作为输入参数。通过执行下列步骤,算法 SELECT可以确定一个有n>1个不同元素的输入数组中第i小的元素。(如果n=1,则 SE原创 2021-09-25 16:14:15 · 13078 阅读 · 0 评论 -
算法设计与分析——顺序统计量:期望为线性时间的选择算法
一般选择问题看起来要比找最小值这样的简单问题更难。但令人惊奇的是,这两个问题的渐近运行时间却是相同的:Θ(n)\Theta(n)Θ(n)。本文将介绍一种解决选择问题的分治算法。randomized_select(arr, low, high, i)算法是以《排序算法:快速排序-[基础知识]》中的快速排序算法为模型的。与快速排序一样,我们仍然将输入数组进行递归划分。但与快速排序不同的是,快速排序会递归处理划分的两边,而randomized_select(arr, low, high, i)只处理划分的一边。原创 2021-09-25 14:04:51 · 13434 阅读 · 0 评论 -
算法设计与分析——顺序统计量:最大值与最小值
在一个有nnn个元素的集合中,我们可以很容易地给出n−1n-1n−1次比较这个上界来确定其最小元素:依次遍历集合中的每个元素,并记录下当前最小元素。当然,最大值也可以通过n−1n-1n−1次比较找出来。这的确就是我们能得到的最好结果吗。对于确定最小值问题,我们可以得到其下界就是O(n)O(n)O(n)次比较。对于任意一个确定最小值的算法,可以把它看成是在各元素之间进行的一场锦标赛。每次比较都是锦标赛中的一场比赛,两个元素中较小的获胜。需要注意的是,除了最终获胜者以外,每个元素都至少要输掉一场比赛。因此,我原创 2021-09-19 21:13:00 · 13986 阅读 · 0 评论 -
算法设计与分析——排序算法:十大排序算法总结
排序算法我们已经通过前序文章全部详细说明了,值的一提的是,我们说述的排序算法都指代的是内部排序算法。而实际上,排序算法可以分为内部排序和外部排序。内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等,这些算法我们在前序文章都意义详细的阐述了,现用一张图概括前序文章的十大排序算法:...原创 2021-09-19 18:01:42 · 13882 阅读 · 0 评论 -
算法设计与分析——排序算法(十):桶排序
桶排序假设输入数据服从均匀分布,平均情况下它的时间代价为O(n)O(n)O(n)。与计数排序类似,因为对输入数据作了某种假设,桶排序的速度也很快。具体来说,计数排序假设输入数据都属于一个小区间内的整数,而桶排序则假设输入是由一个随机过程产生,该过程将元素均匀、独立地分布在[0,1)[0, 1)[0,1)区间上。桶排序将[0,1)[0, 1)[0,1)区间划分为nnn个相同大小的子区间,或称为桶。然后,将nnn个输入数分别放到各个桶中。因为输入数据是均匀、独立地分布在[0,1)[0, 1)[0,1)区间上,原创 2021-09-19 15:07:53 · 14333 阅读 · 0 评论 -
算法设计与分析——排序算法(九):基数排序
基数排序是一种用在卡片排序机上的算法,现在你只能在博物馆找到这种卡片排序机了。博物馆中的一张卡片有80列,在每一列上机器可以选择在12个位置中的任一处穿孔。通过机械操作,我们可以对排序机“编程”来检查每个卡片中的给定列,然后根据穿孔的位置将它们分别放人12个容器中。操作员就可以逐个容器地来收集卡片,其中第一个位置穿孔的卡片在最上面,其次是第二个位置穿孔的卡片,依此类推。对十进制数字来说,每列只会用到10个位置(另两个位置用于编码非数值字符)。一个ddd位数将占用ddd列。因为卡片排序机一次只能查看一列,所原创 2021-09-19 14:32:01 · 10932 阅读 · 0 评论 -
算法设计与分析——排序算法(八):计数排序
计数排序假设nnn个输入元素中的每一个都是在000到kkk区间内的一个整数,其中kkk为某个整数。当k=O(n)k=O(n)k=O(n)时,排序的运行时间为Θ(n)\Theta(n)Θ(n)。计数排序的基本思想是:对每一个输入元素xxx,确定小于xxx的元素个数。利用这一信息,就可以直接把xxx放到它在输出数组中的位置上了。例如,如果有17个元素小于xxx,则xxx就应该在第18个输出位置上。当有几个元素相同时,这一方案要略做修改。因为不能把它们放在同一个输出位置上。在计数排序算法的代码中,假设输入是一原创 2021-09-05 21:53:10 · 19308 阅读 · 3 评论 -
算法设计与分析——排序算法:比较排序算法的下界
我们在《排序算法》系列的开头介绍了几种能在O(nlgn)O(n\lg n)O(nlgn)时间内排序nnn个数的算法。归并排序和堆排序达到了最坏情况下的上界;快速排序在平均情况下达到该上界。而且,对于这些算法中的每个,我们都能给出nnn个输入数值,使得该算法能在Ω(nlgn)\Omega(n\lg n)Ω(nlgn)时间内完成。《排序算法》的1-7节的算法都有一个有趣的性质:在排序的最终结果中,各元素的次序依赖于它们之间的比较。我们把这类排序算法称为比较排序。下文将证明对包含nnn个元素的输入序列来说原创 2021-09-05 16:17:33 · 12628 阅读 · 0 评论 -
算法设计与分析——排序算法(七):快速排序-[快速排序的分析]
在《快速排序-[快速排序的性能]》中,我们给出了在最坏情况下快速排序性能的直观分析,以及它速度比较快的原因。在本文中,我们要给出快速排序性能的更严谨的分析。我们首先从最坏情况分析开始,其方法可以用于原版和随机化版本的quick_sort(arr,low,high)的分析,然后给出随机化版本的quick_sort(arr,low,high)的期望运行时间。在《快速排序-[快速排序的性能]》中,我们得到在最坏情况下,快速排序的每一层递归的时间复杂度是Θ(n2)\Theta(n^2)Θ(n2)。我们也从直观上了原创 2021-09-04 23:44:35 · 11686 阅读 · 0 评论 -
算法设计与分析——排序算法(七):快速排序-[快速排序的随机化]
在讨论快速排序的平均情况性能的时候,我们的前提假设是:输入数据的所有排列都是等概率的。但是在实际工程中,这个假设并不会总是成立(见练习7.2-4)。正如在5.3节中我们所看到的那样,有时我们可以通过在算法中引入随机性,从而使得算法对于所有的输入都能获得较好的期望性能。很多人都选择随机化版本的快速排序作为大数据输入情况下的排序算法。在5.3节中,我们通过显式地对输入进行重新排列,使得算法实现随机化。当然,对于快速排序我们也可以这么做。但如果采用一种称为随机抽样( random sampling)的随机化技术原创 2021-09-04 19:15:55 · 11294 阅读 · 0 评论 -
算法设计与分析——排序算法(七):快速排序-[快速排序的性能]
快速排序的运行时间依赖于划分是否平衡,而平衡与否又依赖于用于划分的元素。如果划分是平衡的,那么快速排序算法性能与归并排序一样。如果划分是不平衡的,那么快速排序的性能就接近于插入排序了。在本文中,我们将给出划分为平衡或不平衡时快速排序性能的非形式化的分析。最坏情况划分当划分产生的两个子问题分别包含了n−1n-1n−1个元素和000个元素时,快速排序的最坏情况发生了。不妨假设算法的每一次递归调用中都出现了这种不平衡划分。划分操作的时间复杂度是Θ(n)\Theta(n)Θ(n)。由于对一个大小为000的数组进原创 2021-09-04 18:58:07 · 12790 阅读 · 1 评论 -
算法设计与分析——字典树(前缀树、Trie树)
字典树又称单词查找树、前缀树、Trie树等,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它可以利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。它还有很多变种,如后缀树、Radix Tree、PATRICIA tree、crit-bit tree等等。定义在计算机科学中,Trie树(前缀树/字典树)是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键原创 2021-09-04 10:39:38 · 10447 阅读 · 0 评论 -
算法设计与分析——堆(四):优先队列
本文我们要介绍堆的一个常见应用:作为高效的优先队列。和堆一样,优先队列也有两种形式:最大优先队列和最小优先队列。这里,我们关注于如何基于最大堆实现最大优先队列。优先队列是一种用来维护由一组元素构成的集合S的数据结构,其中的每一个元素都有一个相关的值,称为关键字。一个最大优先队列支持以下操作:NSERT(S,x):把元素x插入集合S中。这一操作等价于S=SU{x}MAXIMUM(S):返回S中具有最大键字的元素。EXTRACT-MAX(S):去掉并返回S中的具有最大键字的元素。INCREASE-K原创 2021-08-30 00:18:14 · 20779 阅读 · 4 评论 -
算法设计与分析——排序算法(七):快速排序-[基础知识]
对于包含nnn个数的输入数组来说,快速排序是一种最坏情况时间复杂度为Θ(n2)\Theta(n^2)Θ(n2)的排序算法。虽然最坏情况时间复杂度很差,但是快速排序通常是实际排序应用中最好的选择,因为它的平均性能非常好:它的期望时间复杂度是O(nlgn)O(n\lg n)O(nlgn),而且O(nlgn)O(n\lg n)O(nlgn)中隐含的常数因子非常小。另外,它还能够进行原址排序,甚至在虚存环境中也能很好地工作。《排序算法(七):快速排序-[基础知识]》将描述快速排序算法及它的一个重要的划分子程序原创 2021-08-22 22:33:37 · 12124 阅读 · 0 评论 -
算法设计与分析——排序算法(六):希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。def shell_sort(arr): n = len(arr) gap = int(n/2) while gap > 0: for i in range(gap,n): temp =原创 2021-08-20 22:49:31 · 11108 阅读 · 0 评论 -
算法设计与分析——排序算法(五):冒泡排序
冒泡排序也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。def bubble_sort(arr): n = len(arr) # 遍历所有数组元素 for i in range(n): # Last i elements are already in pla原创 2021-08-20 22:44:21 · 13403 阅读 · 0 评论 -
算法设计与分析——排序算法(四):选择排序
选择排序是一种简单直观的排序算法。它的工作原理如下:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。def selection_sort(A): for i in range(len(A)): min_idx = i for j in range(i+1, len(A)): if A[min_idx] > A[j]:原创 2021-08-20 22:35:49 · 12178 阅读 · 0 评论 -
算法设计与分析——排序算法(三):堆排序
堆排序与归并排序一样,但不同于插入排序的是,堆排序的时间复杂度是O(nlogn)O(n\log n)O(nlogn)。而与插入排序相同,但不同于归并排序的是,堆排序同样具有空间原址性:任何时候都只需要常数个额外的元素空间存储临时数据。因此,堆排序是集合了我们目前已经讨论的两种排序算法优点的一种排序算法。堆排序引入了另一种算法设计技巧:使用一种我们称为“堆”的数据结构来进行信息管理。堆不仅用在堆排序中,而且它也可以构造一种有效的优先队列。在后续的章节中,我们还将多次在算法中引入堆。虽然“堆”这一词源自堆原创 2021-08-15 13:47:18 · 12653 阅读 · 1 评论 -
算法设计与分析——堆(三):建堆
我们可以用自底向上的方法利用过程heapify(arr, i)把一个大小为n=A.lengthn=A. lengthn=A.length的数组AAA转换为最大堆。子数组A(⌊n2⌋+1⋯n)A(\lfloor\frac{n}{2}\rfloor+1\cdots n)A(⌊2n⌋+1⋯n)中的元素都是树的叶结点。每个叶结点都可以看成只包含一个元素的堆。过程 BUILD-MAX-HEAP对树中的其他结点都调用一次heapify(arr, i)def max_heap(A): for i in range(原创 2021-08-14 23:26:49 · 10894 阅读 · 0 评论 -
算法设计与分析——堆(二):维护堆的性质
heapify(arr, i)是用于维护最大堆性质的重要过程。它的输入为一个数组AAA和一个下标iii。在调用heapify(arr, i)的时候,我们假定根结点为left(i)和right(i)的二叉树都是最大堆,但这时A[i]A[i]A[i]有可能小于其孩子,这样就违背了最大堆的性质。heapify(arr, i)通过让A[i]A[i]A[i]的值在最大堆中“逐级下降”,从而使得以下标iii为根结点的子树重新遵循最大堆的性质。def heapify(arr, i): largest = i原创 2021-08-14 21:50:43 · 11210 阅读 · 0 评论 -
算法设计与分析——堆(一):基础知识
如下图,堆是一个数组,它可以被看成一个近似的完全二叉树,树上的每一个结点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且是从左向右填充。表示堆的数组AAA包括两个属性:A.lengthA. lengthA.length给出数组元素的个数,A.heap−sizeA.heap-sizeA.heap−size表示有多少个堆元素存储在该数组中。也就是说,虽然A[1⋯A.length]A[1\cdots A.length]A[1⋯A.length]可能都存有数据,但只有A[1⋯A.heap−size]A[1原创 2021-08-14 21:19:23 · 11595 阅读 · 0 评论 -
算法设计与分析——散列表/哈希表(Hash Table):完全散列
使用散列技术通常是个好的选择,不仅是因为它有优异的平均情况性能,而且当关键字集合是静态时,散列技术也能提供出色的最坏情况性能。所谓静态,就是指一旦各关键字存入表中,关键字集合就不再变化了。一些应用存在着天然的静态关键字集合,如程序设计语言中的保留字集合,或者CD-ROM上的文件名集合。一种散列方法称为完全散列,如果该方法进行查找时,能在最坏情况下用O(1)O(1)O(1)次访存完成。我们采用两级的散列方法来设计完全散列方案,在每级上都使用全域散列。下图描述了该方法:第一级与带链接的散列表基本上是一样的原创 2021-08-08 17:13:45 · 16245 阅读 · 1 评论 -
算法设计与分析——散列表/哈希表(Hash Table)(五):开放寻址法
在开放寻址法中,所有的元素都存放在散列表里。也就是说,每个表项或包含动态集合的一个元素,或包含NoneNoneNone。当查找某个元素时,要系统地检查所有的表项,直到找到所需的元素,或者最终查明该元素不在表中。不像链接法,这里既没有链表,也没有元素存放在散列表外。因此在开放寻址法中,散列表可能会被填满,以至于不能插入任何新的元素。该方法导致的一个结果便是装载因子aaa绝对不会超过1。开放寻址法的核心思想是,如果出现了散列冲突,我们就重新探测一一个空闲位置,将其插入。比如,我们可以使用线性探测法。当我们往散原创 2021-08-08 16:23:26 · 13415 阅读 · 2 评论 -
算法设计与分析——散列表/哈希表(Hash Table):散列函数
本文将讨论一些关于如何设计好的散列函数的问题,并介绍三种具体方法。其中的两种方法(用除法进行散列和用乘法进行散列)本质上属于启发式方法,而第三种方法(全域散列)则利用了随机技术来提供可证明的良好性能。好的散列函数的特点一个好的散列函数应(近似地)满足简单均匀散列假设:每个关键字都被等可能地散列到mmm个槽位中的任何一个,并与其他关键字已散列到哪个槽位无关。遗憾的是,一般无法检查这一条件是否成立,因为很少能知道关键字散列所满足的概率分布,而且各关键字可能并不是完全独立的。有时,我们知道关键字的概率分布。原创 2021-08-08 14:57:44 · 11997 阅读 · 0 评论 -
算法设计与分析——散列表/哈希表(Hash Table)(三):散列表原理
直接寻址技术的缺点是非常明显的:如果全域U很大,则在一台标准的计算机可用内存容量中,要存储大小为∣U∣|U|∣U∣的一张表TTT也许不太实际,甚至是不可能的。还有,实际存储的关键字集合KKK相对UUU来说可能很小,使得分配给TTT的大部分空间都将浪费掉。当存储在字典中的关键字集合KKK比所有可能的关键字的全域UUU要小许多时,散列表需要的存储空间要比直接寻址表少得多。特别地,我们能将散列表的存储需求降至Θ(∣K∣)\Theta(|K|)Θ(∣K∣),同时散列表中查找一个元素的优势仍得到保持,只需要O(1)原创 2021-08-08 00:38:37 · 11853 阅读 · 0 评论 -
算法设计与分析——散列表/哈希表(Hash Table):直接寻址表
当关键字的全域UUU比较小时,直接寻址是一种简单而有效的技术。假设某应用要用到一个动态集合,其中每个元素都是取自于全域U={0,1,⋯ ,m−1}U=\{0, 1, \cdots, m-1\}U={0,1,⋯,m−1}中的一个关键字,这里mmm不是一个很大的数。另外,假设没有两个元素具有相同的关键字。为表示动态集合,我们用一个数组,或称为直接寻址表,记为T[0,1,⋯ ,m−1]T[0, 1, \cdots, m-1]T[0,1,⋯,m−1]。其中每个位置,或称为槽,对应全域UUU中的一个关键字。下图描绘原创 2021-08-07 21:25:57 · 11703 阅读 · 0 评论 -
算法设计与分析——散列表/哈希表(Hash Table)(一):基础知识
许多应用都需要一种动态集合结构,它至少要支持插入、删除和查找字典操作。例如,用于程序语言编译的编译器维护了一个符号表,其中元素的关键字为任意字符串,它与程序中的标识符相对应。散列表是实现字典操作的一种有效数据结构。尽管最坏情况下散列表中查找一个元素的时间与链表中查找的时间相同,达到了O(n)O(n)O(n)。然而在实际应用中,散列查找的性能是极好的。在一些合理的假设下,在散列表中查找一个元素的平均时间是O(1)O(1)O(1)。散列表是普通数组概念的推广。由于对普通数组可以直接寻址,使得能在O(1)O(原创 2021-08-07 17:35:33 · 11863 阅读 · 0 评论 -
算法设计与分析——树
本文中,我们专门讨论用链式数据结构表示有根树的问题。我们将首先讨论二叉树,然后给出针对结点的孩子数任意的有根树的表示方法。树的结点用对象表示。与链表类似,假设每个结点都含有一个关键字keykeykey。其余我们感兴趣的属性包括指向其他结点的指针,它们随树的种类不同会有所变化。二叉树下图展示了在二叉树TTT中如何利用属性ppp、leftleftleft和rightrightright存放指向父结点、左孩子和右孩子的指针。如果x.p=Nonex.p=Nonex.p=None,则xxx是根结点。如果结点xx原创 2021-08-07 17:16:39 · 10738 阅读 · 0 评论 -
算法设计与分析——链表
链表是一种这样的数据结构,其中的各对象按线性顺序排列。数组的线性顺序是由数组下标决定的,然而与数组不同的是,链表的顺序是由各个对象里的指针决定的。链表为动态集合提供了一种简单而灵活的表示方法,并且能支持《算法设计与分析——栈和队列》中列出的所有操作。如下图所示,双向链表LLL的每个元素都是一个对象,每个对象有一个关键字keykeykey和两个指针:nextnextnext和prevprevprev。对象中还可以包含其他的辅助数据(或称卫星数据)。设xxx为链表的一个元素,则x.nertx.nertx.ne原创 2021-08-07 12:42:21 · 10804 阅读 · 0 评论 -
算法设计与分析——栈和队列
栈和队列都是动态集合,且在其上进行删除操作所移除的元素是预先设定的。在栈中,被删除的是最近插入的元素:栈实现的是一种后进先出策略。类似地,在队列中,被删去的总是在集合中存在时间最长的那个元素:队列实现的是一种先进先出策略。在计算机上实现栈和队列有几种有效方式。本文将介绍如何利用一个简单的数组实现这两种结构。栈栈上的插入操作称为压入,而无元素参数的删除操作称为弹出。这两个名称使人联想到现实中的栈,比如餐馆里装有弹簧的摞盘子的栈。盘子从栈中弹出的次序刚好同它们压入的次序相反,这是因为只有最上面的盘子才能被取原创 2021-08-06 23:52:03 · 11031 阅读 · 0 评论 -
算法设计与分析——二叉搜索树(四):随机构建二叉搜索树的期望高度
我们已经证明了二叉搜索树上的每个基本操作都能在O(h)O(h)O(h)时间内完成,其中hhh为这棵树的高度。然而,随着元素的插入和删除,二叉搜索树的高度是变化的。例如,如果nnn个关键字按严格递增的次序被插入,则这棵树一定是高度为n−1n-1n−1的一条链。遗憾的是,当一棵二叉搜索树同时由插入和删除操作生成时,我们对这棵树的平均高度了解的甚少。当树是由插入操作单独生成时,分析就会变得容易得多。因此,我们定义nnn个关键字的一棵随机构建二叉搜索树为按随机次序插入这些关键字到棵初始的空树中而生成的树,这里输入原创 2021-08-01 23:21:48 · 11570 阅读 · 0 评论 -
算法设计与分析——二叉搜索树(三):插入和删除
插入和删除操作会引起由二叉搜索树表示的动态集合的变化。一定要修改数据结构来反映这个变化,但修改要保持二叉搜索树性质的成立。正如后面将看到的,插入一个新结点带来的树修改要相对简单些,而删除的处理有些复杂。插入要将一个新值vvv插人到一棵二叉搜索树TTT中,需要调用过程 TREE-INSERT。该过程以结点zzz作为输入,其中z.key=vz.key=vz.key=v,z.left=Nonez.left=Nonez.left=None,z.right=Nonez.right=Nonez.right=None原创 2021-08-01 21:54:26 · 11179 阅读 · 0 评论 -
算法设计与分析——二叉搜索树(二):查询二叉搜索树
我们经常需要查找一个存储在二叉搜索树中的关键字。查找我们使用下面的过程在一棵二叉搜索树中查找一个具有给定关键字的结点。输入一个指向树根的指针和一个关键字kkk,如果这个结点存在,tree_search(x, k)返回一个指向关键字为kkk的结点的指针;否则返回None。def tree_search(x, k): if x == None or k == x.key: return x if k < x.key: return tree_search(原创 2021-08-01 20:04:46 · 11228 阅读 · 0 评论 -
算法设计与分析——二叉搜索树(一):基础知识
搜索树数据结构支持许多动态集合操作,包括查找、最大元素、最小元素、 前驱、后继、插入和删除等。因此,我们使用一棵搜索树既可以作为一个字典又可以作为一个优先队列。二叉搜索树上的基本操作所花费的时间与这棵树的高度成正比。对于有nnn个结点的一棵完全二叉树来说,这些操作的最坏运行时间为Θlg(n)\Theta\lg (n)Θlg(n)。然而,如果这棵树是一条nnn个结点组成的线性链,那么同样的操作就要花费Θ(n)\Theta(n)Θ(n)的最坏运行时间。后续的文章中,我们将看到一棵随机构造的二叉搜索树的期望高原创 2021-08-01 13:27:39 · 11785 阅读 · 0 评论 -
算法设计与分析——动态规划(五):最长公共子序列
分类目录:《算法设计与分析》总目录在生物应用中,经常需要比较两个(或多个)不同生物体的DNA。一个DNA串由一串称为碱基的分子组成,碱基有腺嘌呤、鸟嘌呤、胞嘧啶和胸腺嘧啶4种类型。我们用英文单词首字母表示4种碱基,这样就可以将一个DNA串表示为有限集A,C,G,T{A,C,G,T}A,C,G,T上的一个字符串。例如,某种生物的DNA可能为S1=ACCGGTCGAGTGCGCGGAAGCCGGCCGAAS_1= ACCGGTCGAGTGCGCGGAAGCCGGCCGAAS1=ACCGGTCGAGTGCGC原创 2021-07-25 17:16:42 · 19526 阅读 · 7 评论 -
算法设计与分析——动态规划(四):动态规划详解
分类目录:《算法设计与分析》总目录虽然我们已经用动态规划方法解决了钢条切割和矩阵链乘法两个问题,但你可能还是弄不清应该在何时使用动态规划。我们关注适合应用动态规划方法求解的最优化问题应该具备的两个要素:最优子结构和子问题重叠。我们还会再次讨论备忘方法,更深入地讨论在自顶向下方法中如何借助备忘机制来充分利用子问题重叠特性。最优子结构用动态规划方法求解最优化问题的第一步就是刻画最优解的结构。如前文所述,如果一个问题的最优解包含其子问题的最优解,我们就称此问题具有最优子结构性质。因此,某个问题是否适合应用动原创 2021-07-24 15:44:29 · 13609 阅读 · 0 评论