binary indexed tree

背景

二进制索引树(binary indexed tree [bit])是一种高效计算累积频率的“假想数据结构“,不是通所说的二叉搜索树(binary search tree)。
对一维数组A求累积频率, 常规做法如下。
更新(x, v) := A[x] += v; //O(1)
查询(x) := A[0] + A[1] + … + A[x]; //O(N)
而bit的查询,更新都为O(log N)。

原理

频率F(x) := x出现的次数
累积频率C(x) := 频率F(1) + F(2) + … + F(x)
F(i) + F(i+1) + … + F(j)简记为F[i..j]

11个1的和:直接相加 <==> 部分和相加
1 + 1 + … + 1 = 8 + 2 + 1

累积频率C(11):直接相加 <==> 部分和相加
F[1..11] = F[1..8] + F[9..10] + F[11]

预先计算好部分和,存入一维数组A
F[1..8] –> A[8]; F[9..10] –> A[10]; F[11] –> A[11];

十进制展开为二进制:去掉最低比特位1
11 = #b1011
10 = #b1010
8 = #b1000

再来几个例子
#b110 = 6
#b100 = 4
F[1..6] = F[1..4] + F[5..6]
F[1..4] –> A[4]; F[5..6] –> A[6];
#b11 = 3
#b10 = 2
F[1..3] = F[1..2] + F[3] = A[2] + A[3];
#b1 = 1
#b0 = 0
F[0..1] = F[0] + F[1] = A[0] + A[1]

组织成左倾树
0-1
0-2-3
0-4-5
0-4-6-7
0-8-9
0-8-10-11
0-8-12-13
0-8-12-14-15

如何得最低比特位1:与其补码+1按位与
11 & -11 = #b1011 & #b0101 = #b0001
10 & -10 = #b1010 & #b0110 = #b0010
去掉就是减去
11 - 1 = 10; 10 - 2 = 8;

  1. 求累加频率:
    C(x) = 不断地 累加A[x]; x -= x&-x; 直到x为0。

如何计算部分和数组A
初始值当然为0;
对F[x]加上一个值v, 影响C(x), C(x+1), ….

  1. 要对A所有下标如下的加上v:
    x, x += x&-x, …, 直到x最大可能值。
    例子:
    x= #b101, #b110, #b1000
    x= #b001, #b010, #b100
    x= #b1001, #b1010, #b1100, #b10000

  2. 从部分和数组A反推频率F(x):
    F(x) = C(x+1) - C(x)
    优化:在两条路径交汇点及之后不必再计算,例如
    x = 11, C(x) = A[11] + A[10] + A[8]
    y = 10, C(y) = A[10] + A[8]
    x先走,Sx += A[11]; 下一步 x =10,如果遇到y,终止;
    轮到y走,Sy累加,…, 发现遇到x,终止;
    结果为Sx - Sy.

  3. 从累加频率反推下标x:
    在左倾树二分查找

应用

插入排序中的移位次数[hr] (common lisp/sbcl 实现)

(defun get-culmulative (x A)
  (let ((s 0))
    (loop until (= x 0) do
         (incf s (aref A x))
         (decf x (logand x (- x)))) s))

(defun add-culmulative (x v maxv A)
  (loop until (> x maxv) do
       (incf (aref A x) v)
       (incf x (logand x (- x)))))

#|
对于新的元素e, 在已经部分排好序数组A中,比e大的有几个
等价于数组大小减去小于等于e的个数。
等价于以e作为下标,求e的累加频率。

|#

(defun shifts-of-insert-sort (arr n)
  (let* ((shifts 0) (maxv 10000000) (A (make-array (1+ maxv) :initial-element 0)))
    (add-culmulative (aref arr 0) 1 maxv A)
    (loop for i from 1 to (1- n) by 1 do
         (let ((e (aref arr i)))
           (incf shifts (- i (get-culmulative e A)))
           (add-culmulative e 1 maxv A)))shifts))


(defvar TEST (read))

(loop repeat TEST do
     (let* ((n (read)) (arr (make-array n)))
       (loop for i from 0 to (1- n) by 1 do
            (setf (aref arr i) (read)))
       (format t "~a~%" (shifts-of-insert-sort arr n))))

参考

[bst] peter m. fenwick, 1994, A New Data Structure for Cumulative Frequency Tables.
[hr] https://www.hackerrank.com/challenges/insertion-sort?h_r=next-challenge&h_v=zen

你可能感兴趣的:(算法,lisp)