首页

▼ 算法合集

▼ 加密算法

▼ 凯撒密码

凯撒密码

▼ Hill密码算法

Hill密码算法

▼ 多项式哈希算法

多项式哈希算法

▼ Rail Fence Cipher

Rail Fence Cipher

▼ Graph算法

▼ 关节点

关节点

▼ Bellman-Ford算法

Bellman-Ford算法

▼ 广度优先搜索

广度优先搜索

▼ GraphBridges

桥接模式

▼ 深度优先搜索

深度优先搜索

▼ 检测循环

检测循环

▼ Dijkstra算法

Dijkstra算法

▼ Eulerian Path

欧拉路径

▼ Floyd-Warshall算法

Floyd-Warshall算法

▼ HamiltonianCycle

Hamiltonian Cycle

▼ Kruskal算法

Kruskal算法

▼ Prim算法

Prim算法

▼ 强连通分量

强连通分量

▼ 拓扑排序

拓扑排序

▼ Travelling Salesman Problem

Travelling Salesman

▼ 图像处理算法

▼ Seam Carving算法

内容感知缩放算法

▼ 链表

▼ 反向遍历

反向遍历

▼ GraphTraversal

GraphTraversal

▼ 数学算法

▼ 二进制浮点数

BinaryFloatingPoint

▼ 位操作

位操作算法

▼ 复数

复数

▼ 欧几里得算法

Euclidean Algorithm

▼ Euclidean Distance

欧几里得距离

▼ 阶乘算法

阶乘算法

▼ 快速幂算法

快速幂算法

▼ Fibonacci数列

斐波那契数列

▼ 傅里叶变换

Fourier变换

▼ Horner法

霍纳法则

▼ 整数划分

整数划分

▼ 判断是否为2的幂

判断是否为2的幂

▼ 最小公倍数

最小公倍数

▼ Liu Hui

Liu Hui

▼ 矩阵

Matrix

▼ Pascal三角形

Pascal三角形

▼ Primality Test

素数测试

▼ 质因数

质因数

▼ 弧度计算

弧度计算

▼ 埃拉托色尼筛法

埃拉托色尼筛法

▼ SquareRoot

SquareRoot

▼ MachineLearning

▼ K均值算法

K均值算法

▼ K近邻算法

K近邻算法

▼ 搜索算法

▼ 二分查找算法

二分查找

▼ 插值搜索算法

插值搜索算法

▼ 跳跃搜索算法

跳跃搜索算法

▼ 线性搜索

线性搜索算法

▼ 集合

▼ 笛卡尔积

笛卡尔积

▼ 组合总和

组合总和

▼ 组合算法

组合算法

▼ Fisher-Yates洗牌算法

Fisher-Yates洗牌算法

▼ 背包问题

背包问题

▼ 最长公共子序列

最长公共子序列

▼ 最长递增子序列

最长递增子序列

▼ 最大子数组

最大子数组

▼ 排列组合

排列组合

▼ 幂集

幂集算法

▼ 最短公共超序列

最短公共超序列

▼ Sorting Algorithms

▼ 冒泡排序

冒泡排序

▼ 桶排序算法

桶排序算法

▼ 计数排序算法

计数排序

▼ 堆排序算法

堆排序

▼ 插入排序

插入排序

▼ 归并排序

归并排序

▼ 快速排序算法

快速排序算法

▼ 基数排序

基数排序

▼ 选择排序算法

选择排序算法

▼ 希尔排序

希尔排序

▼ 统计学

▼ 加权随机

加权随机算法

▼ 字符串算法

▼ Hamming距离

Hamming距离

▼ KnuthMorrisPratt算法

Knuth-Morris-Pratt算法

▼ LevenshteinDistance

Levenshtein距离

▼ 最长公共子串

最长公共子串

▼ 回文检测算法

回文检测算法

▼ Rabin-Karp算法

Rabin-Karp算法

▼ 正则表达式匹配

正则表达式匹配

▼ Z算法

Z算法

▼ Tree Data Structure

▼ 广度优先搜索

广度优先搜索

▼ 深度优先搜索

深度优先搜索

▼ 未分类

▼ 最佳买卖股票时机

最佳买卖股票时机

▼ 汉诺塔算法

HanoiTower

▼ 跳跃游戏算法

跳跃游戏

▼ KnightTour

骑士巡逻

▼ N皇后问题

N皇后问题

▼ 雨水收集

雨水收集

▼ 递归楼梯问题

递归楼梯问题

▼ 方阵旋转

方阵旋转

▼ 独特路径

UniquePaths

▼ 数据结构

▼ BloomFilter算法

布隆过滤器

▼ 不相交集数据结构

Disjoint Set

▼ 双向链表

双向链表

▼ Graph

Graph算法

▼ 哈希表

哈希表

▼ Heap数据结构

Heap数据结构

▼ 链表

链表

▼ LRU缓存

LRU缓存

▼ 优先队列

优先队列

▼ 队列

队列

▼ 栈结构

栈结构

▼ Tree Data Structure

树结构

▼ AVL树

AVL树

▼ 二叉搜索树

二叉搜索树

▼ Fenwick树

Fenwick树

▼ 红黑树

红黑树

▼ 线段树

SegmentTree

▼ Trie数据结构

Trie数据结构

多项式滚动哈希

哈希函数

哈希函数用于将任意长度的键元素集映射到固定长度的指纹元素集。

哈希的基本应用是通过比较它们的指纹来高效测试键的相等性。

当两个不同的键具有相同的指纹时,就会发生冲突。在大多数哈希应用中,冲突处理的方式至关重要。哈希特别适用于构建高效的实用算法。

滚动哈希

滚动哈希(也称为递归哈希或滚动校验和)是一种哈希函数,其中输入在窗口中移动时进行哈希计算。

一些哈希函数允许非常快速地计算滚动哈希——仅根据以下数据即可迅速计算新的哈希值:

  • 旧的哈希值,
  • 从窗口中移除的旧值,
  • 和添加到窗口中的新值。

多项式字符串哈希

理想的字符串哈希函数显然应该依赖于键中存在的符号的多重集以及符号的顺序。这类哈希函数最常见的一族将字符串的符号视为多项式的系数,其中有一个整数变量p,并计算其在整数常数M模下的值:

拉宾-卡普字符串搜索算法通常使用一个非常简单的滚动哈希函数来解释,该函数只使用乘法和加法——多项式滚动哈希

H(s0, s1, ..., sk) = s0 * pk-1 + s1 * pk-2 + ... + sk * p0

其中p是一个常数,(s1, ... , sk)是输入字符。

例如,我们可以通过将数字代码乘以常数的幂,将短字符串转换为键编号。三个字母的单词ace可以通过计算得到一个数字:

key = 1 * 262 + 3 * 261 + 5 * 260

为了避免操作巨大的H值,所有数学运算都在M模下完成。

H(s0, s1, ..., sk) = (s0 * pk-1 + s1 * pk-2 + ... + sk * p0) mod M

仔细选择参数Mp对于获得哈希函数的“好”属性很重要,即低冲突率。

这种方法具有涉及输入字符串中所有字符的理想属性。然后可以按常规方式将计算出的键值哈希到数组索引中:

function hash(key, arraySize) {
  const base = 13;

  let hash = 0;
  for (let charIndex = 0; charIndex < key.length; charIndex += 1) {
    const charCode = key.charCodeAt(charIndex);
    hash += charCode * (base ** (key.length - charIndex - 1));
  }

  return hash % arraySize;
}

hash()方法并不像它可能那样高效。除了字符转换外,循环中有两次乘法和一次加法。我们可以通过使用霍纳方法消除一次乘法:

a4 * x4 + a3 * x3 + a2 * x2 + a1 * x1 + a0 = (((a4 * x + a3) * x + a2) * x + a1) * x + a0

换句话说:

Hi = (P * Hi-1 + Si) mod M

hash()无法处理长字符串,因为哈希值超出了整数的大小。请注意,键最终总是小于数组大小。在霍纳方法中,我们可以在计算的每一步应用模运算符(%)。这给出了与在最后一次性应用模运算符相同的结果,但避免了溢出。

function hash(key, arraySize) {
  const base = 13;

  let hash = 0;
  for (let charIndex = 0; charIndex < key.length; charIndex += 1) {
    const charCode = key.charCodeAt(charIndex);
    hash = (hash * base + charCode) % arraySize;
  }

  return hash;
}

多项式哈希具有滚动属性:当在字符串两端添加或删除符号时,可以高效地更新指纹(前提是存储了足够长的p的幂次模M的数组)。 流行的拉宾-卡普模式匹配算法就是基于这一属性。

参考文献