欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  数据库

你必须背下的几个经典算法[2nd]

程序员文章站 2022-05-05 10:41:20
...

(三) 红黑树 红黑树 自身具有优秀的平衡性,具有很高效的检索速度,很适于对有权重的数据进行组织和查找。红黑树首先是一种二叉搜索树,因而具有“左下最

(三)红黑树

红黑树自身具有优秀的平衡性,具有很高效的检索速度,很适于对有权重的数据进行组织和查找。红黑树首先是一种二叉搜索树,因而具有“左下最小、右下最大”的性质。红黑树的每个节点(node)至少包括了5个域: 父节点指针、左孩子指针、右孩子指针、关键字、颜色,红黑树具有如下特性,才使得它具有如此优秀的性质:

[1]红黑树的节点要么是黑色,要么是红色。
[2]根节点是黑色
[3]叶子节点是黑色(根节点和叶子节点一般用一个唯一的域值为null的null节点表示)
[4]红色节点的孩子必须是黑色
[5]从任意节点到达叶子节点,所经过的黑色节点数目相等
(其中黑高可以用来特指某个节点到达它的叶子节点的路径上黑色节点数目,不包括其自身)
一个红黑树的建树时间是O(n*lg(n)),插入时间是O(lg(n)),删除时间是O(lg(n)),检索时间是O(lg(n))。
二叉搜索树的旋转操作:
X
/ \
a Y
/ \
b r
旋转为:
Y
/ \
X r
/ \
a b


不需要额外的指针就能进行旋转操作,步骤如下
[1] y = x.right 这里用到了两个指针,包括一个参数
[2] x.p.left_or_right = y; y.p = x.p;
[3] x.right = y.left; y.left.p = x;
[3] y.left = x; x.p = y;
红黑树的插入:
步骤一: 根据二叉搜索树的方法插入节点并且标记为红色,不考虑节点插入对4个性质的违背
步骤二: 由于可能违背了性质2、(非根)必须违背性质4,进行着色调整。调用RB-INSERT-FIXUP,分三种情况进行处理:
(1)叔节点为红色,自身为左孩子或者右孩子均可
(2)叔节点为黑色,自身为左孩子
(3)叔节点为黑色,自身为右孩子


(1)将祖父节点的黑色变为红色,将黑色分给父节点和叔节点,继续针对祖父节点递归,这样黑高向下“移”而不变,而且操作仅仅使着色发生变化

(2)对父节点进行右旋操作,并且保持树的颜色分布,这样颜色分布不变,仅仅是父节点进行了右旋


(四)基数排序

基数排序的思想是将n个待排元素对应到一个多位的整数,一共有d位,每一位都可以取值k个值; 对每一位为标准位进行一次稳定的排序,整个元素的顺序按照这个标准位进行排列,继续从低位排序到高位,并对这个元素重新排序; 如果每一次稳定排序的时间是O(n+k),那么最终时间是O(d*(n+k))。


(五)桶排序

桶排序是针对在一定区间内均匀分布(或近似于均匀分布)的数据进行排序的,所以是一种特殊(非普适)的算法。我们假设分布的区间是[0, 1),对这个长度为1的区间分成n个小区间(我们称之为桶),每个区间长度为1/n,每一个小区间对应一个链表,当新数据输入时,我们将输入数据逐个地和桶的标准分界值比较,当输入数据落入某个区间时,将它插入到对应的链表中去,在插入的时候又可以进行比较从而使得链表内数据有序。当所有输入完成后,将所有链表(除了头节点)链接起来,形成一个总的链表。

(六)动态规划

算法基本思想是分治:将问题分解为若干个子问题,但是要进行中间结果(即子问题的解)的记录,在继续计算时,会多次使用前面的中间结果,将这些子问题的解有序地组合起来,一般可以组成一个二维数组,备存便用,这成为了动态规划的最大特征。可见动态规划是一种牺牲空间换取时间为代价的,中间结果放在存储表中。利用这种思想对递归方法进行改进得到了记忆型递归,即每次递归到子问题时先查询是否有中间解,没有继续递归,否则直接使用已得结果,如矩阵链乘法。

动态规划的使用还有一个重要的原则:即子问题的原问题的最优解的必要条件,即如果最终的解是最优的,那么解的每一个细分(按照子问题划分)或每一个步骤都是最优的。比如最短路径问题,它的子步骤是到达终点的路径分段,即包含终点的子路径的选择(从后向前),要确保每一个字分段都是最优的。即如果你必须背下的几个经典算法[2nd]是最优的,则有你必须背下的几个经典算法[2nd]是最优的。这里0-1背包问题和最短路径问题都是最优解问题。

(七)0-1背包问题

算法的思想简单,是基本的递归思路,只不过在每一层递归时,考虑下一个物品的取舍,以及下一物品是否“超重”,总是遵循“累计”价值最大以及“超重不取”的原则,而不是“能装则取”。

不过在构造存储表时需要更多的技巧,这是一个矩阵(二维数组),i下标代表剩余的物品数,j下标代表剩余的容量。由于二维数组下标是连续变化的,所以对每一个单位的剩余重量都要分配存储空间和初始化值,这里考虑的临界条件比较巧妙,对数组元素赋的初值经过了精心设计:a.对于取最后一个物品的,即剩余n个物品的初值,假设W[n] M[n][W[n]...C] = W[n],;这里进一步考虑对于第n个物品,元素下标j为0到W[n]-1的情况,虽然在目前看来不可能,因为我们的物品n重量W[n]M[n][0...W[n]-1] = 0,即不能选取物品,考虑物品n后,总的最大的价值是0。b.如果W[n]>C,装不下最后一个物品,对于则只能对这些值赋0,即考虑第n个物品的取舍之后,得到的总的最大价值仍然是0,也就是M[n][0...C] = 0。归纳以上两种情况,令MaxJ = min(W[n] - 1, C),有M[n][0...MaxJ] = 0; M[n][W[n]...C] = W[n]; (注意:这里W[n]...C在遇到W[n] > C的情况时,认为赋值不会被执行)

重复以上思路,对于0...C(...表“到”)即全部的重量范围,无论还剩余多少个n-1以内的物品,都无法估算出,考虑了这些物品后,能够获得的最大价值;因为在考虑这些物品时,至少还要考虑对是否选择第n个物品,由此产生的最大价值的比较问题(第n个物品有可能选有可能在装不下时不选),从而无法赋初值。其中又可以分为两种情况:a.W[i] C,放不下物品i,M[i][j] = M[i+1][j],j = 0...C。归纳来说,就是MaxJ = min(W[i] - 1, C),有M[i][j] = M[i+1][j],j = 0...MaxJ;M[i][j] = max(M[i+1][j-W[i]] + V[i], M[i+1][j]),j = W[i]...C。

在这里,又有一个细节性的关键问题,在这个算法中,取第i件物品影响的不是还没有的取得物品,而是自身的问题最大价值,这里要考虑的是选取物品i使得剩余空间减少的情况下子问题的最大值 和 不选取物品i的剩余空间下子问题的最大值,而不是第i+1件物品已经确定,剩余容量大小和i的增减之间也没有正相关的关系,只是第i+1件物品的子问题提供了在不同剩余空间下最大价值的子问题解的选择而已;具体地说,这里用到的反向思维方式,即我们的问题的最优解取决于子问题的最优解,而子问题的最优解是已经解决的问题的解和当前考虑的新元素对应的情况处理组合,而不是仅仅是在子问题解中选择最大价值的解,这里我们要在i+1物品的考虑点上,对两个不同的剩余容量存储值对应的价值之间进行选择,即选择采用M[i+1][j]还是M[i+1][j-W[i]]。这有悖于正常思维,因为我们可能认为第i+1个物品已经选过,而对它的选择取还是不取,考虑时剩余的背包容量空间应该固定的,而这里却受到第i个物品的取舍的影响。这里,正是这个算法的精髓所在,即在考虑某个物品时,要考虑所有的容量空间,在这些容量空间下,取舍该物品所能得到的总的价值都要计算。 这样,在选取之后的第i件物品时,第i件物品的取舍直接影响到对于第i+1个子问题解的选择范围,这里的选择范围是取或不取第i件物品造成的剩余容量的限制,具体地说,就是选取第i件物品时在剩余容量下取最大价值的子问题的解,和在不取第i件物品时在剩余容量下取最大价值的子问题的解,在这基础上再考虑第i件物品的选取带来的价值,由此作出选择。

用数学表达式表示如下:

你必须背下的几个经典算法[2nd]