【问题标题】:algorithm to find subset of large int array which matches boolean query查找与布尔查询匹配的大型 int 数组的子集的算法
【发布时间】:2011-11-03 13:10:45
【问题描述】:

假设我有一个由 M 32 位整数组成的大型数组,其中每个值的设置不超过 N 位。现在我想返回与查询 Target AND Value == Target 匹配的子集,即目标位出现在数组值中的值。

蛮力很容易,只需迭代数组并提取 target&value == target 的位置。如果 M 变得非常大,这将变得太慢。有人知道如何将数组转换为更适合搜索的数据结构吗?

一种方法是为每个位存储数组或值(因此对于 32 位数组,您需要其中的 32 个),然后仅搜索与目标值中的每个位匹配的值。除非 N 接近 32 或目标设置了接近 N 位,否则这会有所帮助。由于我要查找的内容本质上是部分匹配,因此散列或排序似乎没有帮助。

要求结果准确无误。这必须在不访问并行硬件(如 GPU 或使用 SIMD)的情况下工作。

我将使用 C++,但只是一些指向算法或想法的指针就可以了。最可能的情况是 M=100000 和 N=8 并且被频繁调用。

重申一下:我需要部分匹配(如 item = 011000 匹配目标 = 001000)而不是完全匹配。虽然 M 项是提前知道的,但目标的可能值可以是任何值。

我最终决定坚持使用蛮力。对于 80,000 个项目,不值得做任何其他事情。我想如果数据集的大小更像是 800,000,000,那可能是值得的。

【问题讨论】:

  • 如果 target&value=target, value 包含目标的位,反之亦然?你能举个目标和价值的例子吗
  • 项目在列表中是 00011100,目标是 00010100,因此目标匹配项目但不是所有位
  • 您需要多快?我测试了蛮力方法,在 M=100,000 和 N=8 的情况下,我始终得到平均 20ms。那是更高阶的语言。我想 C++ 应该快得多。我在下面尝试的答案慢了约 100 倍。
  • 可能在从桌面到移动设备的任何设备上运行。到目前为止,蛮力似乎是我没想到的最快的。

标签: arrays algorithm optimization boolean-operations


【解决方案1】:

你可以建立一个bitwise trie。

在遍历 trie 时,对于目标中的每个 0,您都需要遍历两个分支。

编辑在测试了快速实现之后,我不会推荐这种方法。蛮力方法比这种方法快约 100 倍。

【讨论】:

  • 有趣,我记得读过关于 nedtrie 的文章。但是,我看不出它如何与部分匹配一起工作。找到类似的前缀似乎会更好。
  • 目标中遇到零时,需要遍历两个节点。如果目标中有很多零,它最终可能会经过相当长的遍历。
  • 我认为这个想法值得进一步考虑。您可以不按位级别拆分树,而是按每个级别 2 位甚至 4 位来拆分树。这可能会提高一点速度。因此,假设您拥有Target = 10 00,那么您将跳过 00 和 01 的子节点,但会下降到子节点 10 和 11。
【解决方案2】:

从另一个角度来看这个问题怎么样?.. 将您的整数集视为一维图片的集合。其中一种组织方式是将每张图片分成A和B两部分,并按类别对所有图片进行排序:

  1. A 仅包含零,B 包含一些已设置的位(至少一个)
  2. A 包含一些位设置,B 仅包含零
  3. A 和 B 包含一些位集(1 和 2 的超集)
  4. A 和 B 仅包含零

现在您将目标/蒙版拆分为相同的部分并以相同的方式进行分类。之后您可以推断下一个(按目标/掩码类别):

  1. 您可以跳过第 2 类和第 4 类的图片
  2. 您可以跳过第 1 类和第 4 类的图片
  3. 您可以跳过第 4 类的图片
  4. 所有图片都匹配此目标/蒙版

在下一级部分A 和B 再次拆分(所以你有 4 个部分)等等。

当然,我不希望它能够加快速度。但是对于一些没有设置太多位的数据集(与基于位树的变体相反),它可能会更好。

更新:我在 Haskell 变体中获得了 34% 的加速:

    benchmarking burte-force list search
    mean: 14.67350 ms, lb 14.65103 ms, ub 14.71614 ms, ci 0.950
    std dev: 153.6920 us, lb 95.70642 us, ub 246.6497 us, ci 0.950

    benchmarking tree-lookup search
    mean: 9.592271 ms, lb 9.564509 ms, ub 9.667668 ms, ci 0.950
    std dev: 216.6084 us, lb 100.3315 us, ub 455.2730 us, ci 0.950

源代码:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}

import Control.Arrow (first)
import Control.DeepSeq
import Data.Word
import Data.Bits
import Data.List

import Criterion.Main
import Criterion.Config
import System.Random

class BitmapsCollection a where
    type BitmapOf a
    bitmapsCollection :: [BitmapOf a] -> a
    findMaskedPattern :: a -> BitmapOf a -> [BitmapOf a]

-- Plain lookup through array
newtype BitmapsList p = BitmapsList [p]
    deriving (Show, NFData)

instance Bits p => BitmapsCollection (BitmapsList p) where
    type BitmapOf (BitmapsList p) = p
    bitmapsCollection = BitmapsList
    findMaskedPattern (BitmapsList xs) m = filter (\e -> e .&. m == m) xs

-- Tree of bitmap partitions
data Bits p => BitmapsCoverTree p = EmptyBitmapsCoverNode
                                  | BitmapsCoverNode (p,p) (BitmapsCoverTree p) (BitmapsCoverTree p) [p] [p]
                                  | LeafBitmapsCoverNode [p]
    deriving Show

instance (Bits p, NFData p) => NFData (BitmapsCoverTree p) where
    rnf EmptyBitmapsCoverNode = ()
    rnf (LeafBitmapsCoverNode xs) = rnf xs
    rnf (BitmapsCoverNode mask node1 node2 category3 category4) = mask `deepseq` node1 `deepseq` node2 `deepseq` category3 `deepseq` rnf category4

data BitmapCoverCategory = CoverA | CoverB | CoverAB | CoverZero

coverCategory :: Bits a => (a, a) -> a -> BitmapCoverCategory
coverCategory (maskA, maskB) x = case (x .&. maskA, x .&. maskB) of
                                     (0, 0) -> CoverZero
                                     (0, _) -> CoverB
                                     (_, 0) -> CoverA
                                     _ -> CoverAB

coverCategorize :: Bits a => (a, a) -> [a] -> ([a], [a], [a], [a])
coverCategorize mask = walk (id, id, id, id) where
    category = coverCategory mask
    walk (a, b, ab, z) [] = (a [], b [], ab [], z [])
    walk (a, b, ab, z) (x:xs) = case (category x) of
                                    CoverA -> walk (a . (x:), b, ab, z) xs
                                    CoverB -> walk (a, b . (x:), ab, z) xs
                                    CoverAB -> walk (a, b, ab . (x:), z) xs
                                    CoverZero -> walk (a, b, ab, z . (x:)) xs

suffixMask, prefixMask :: Bits a => Int -> a
suffixMask n = complement 0 `shiftL` n
prefixMask n = complement (suffixMask n)

rangeMask :: Bits a => (Int, Int) -> a
rangeMask (n, m) = suffixMask n .&. prefixMask m

instance Bits p => BitmapsCollection (BitmapsCoverTree p) where
    type BitmapOf (BitmapsCoverTree p) = p
    bitmapsCollection bms = buildCoverNode (0, bitSize (head bms)) bms where
        splitBoundary = 4
        buildCoverNode :: Bits a => (Int, Int) -> [a] -> BitmapsCoverTree a
        buildCoverNode _ [] = EmptyBitmapsCoverNode
        buildCoverNode (n, m) xs | (m - n) < splitBoundary = LeafBitmapsCoverNode xs -- too small
        buildCoverNode (n, m) xs = BitmapsCoverNode mask node1 node2 category3 category4 where
            mm = (n+m) `div` 2

            mask = (rangeMask (n, mm), rangeMask (mm, m))

            (category1, category2, category3, category4) = coverCategorize mask xs

            node1 = buildCoverNode (n, mm) category1
            node2 = buildCoverNode (mm, m) category2

    findMaskedPattern EmptyBitmapsCoverNode _ = []
    findMaskedPattern (LeafBitmapsCoverNode ps) m = filter (\e -> e .&. m == m) ps

    findMaskedPattern (BitmapsCoverNode _ node1 node2 category3 category4) 0 = flatten where
        flatten = findMaskedPattern node1 0 ++ findMaskedPattern node2 0 ++ category3 ++ category4

    findMaskedPattern (BitmapsCoverNode mask node1 node2 category3 category4) m = result where
        targetCategory = coverCategory mask m
        filterTarget = filter (\p -> p .&. m == m)
        result = case targetCategory of
                     CoverA -> findMaskedPattern node1 m ++ filterTarget category3
                     CoverB -> findMaskedPattern node2 m ++ filterTarget category3
                     CoverAB -> filterTarget category3
                     CoverZero -> category1 ++ category2 ++ category3 ++ category4

        category1 = findMaskedPattern node1 0
        category2 = findMaskedPattern node2 0


main = do
    gen <- getStdGen
    let size = 1000000
        bitmaps :: [Word32]
        (bitmap, genm) = first fromIntegral (random gen :: (Int, StdGen))
        bitmaps = map fromIntegral (take size (randoms genm) :: [Int])
        bitmapsList = bitmapsCollection bitmaps :: BitmapsList Word32
        bitmapsTree = bitmapsCollection bitmaps :: BitmapsCoverTree Word32

    bitmapsList `deepseq` bitmapsTree `deepseq` return ()

    defaultMainWith defaultConfig (return ()) [
        bench "burte-force list search" $ nf (findMaskedPattern bitmapsList) bitmap,
        bench "tree-lookup search" $ nf (findMaskedPattern bitmapsTree) bitmap
        ]

更新:一种 C++11 代码。它为蛮力提供 10.9444 毫秒,为该算法提供 8.69286 毫秒。我通过使打开位的分布输出更稀疏来作弊。

#include <iostream>
#include <vector>
#include <list>
#include <random>
#include <functional>
#include <cassert>
#include <memory>


#include <sys/time.h>
#include <sys/resource.h>

// benchmark boiler plate code
double cputime()
{
    struct rusage usage;
    int check = getrusage( RUSAGE_SELF, &usage );
    assert(check == 0);
    return (usage.ru_utime.tv_sec + usage.ru_utime.tv_usec*1.0e-6);
    //return (((double)clock())/((double)CLOCKS_PER_SEC));
}

double measure(std::function<void()> func, size_t iterations)
{
    double t1, t2;
    size_t i;
    t1 = cputime();
    for(i = 0; i < iterations; ++i) func();
    t2 = cputime();
    return (t2 - t1);
}

std::pair<std::string, double> human(double value)
{
    static const std::vector<std::pair<std::string, double>> prefixes = {
        { "pico",  1e-12 },
        { "nano",  1e-9  },
        { "micro", 1e-6  },
        { "milli", 1e-3  },
        { "",      1     },
        { "kilo",  1e3   },
        { "mega",  1e6   },
        { "giga",  1e9   },
        { "tera",  1e12  }
    };

    for(auto it = prefixes.begin(); it != prefixes.end(); ++it)
    {
        if (it->second > value) 
        {
            auto prev = *(--it);
            return std::pair<std::string, double>(prev.first, value/prev.second);
        }
    }
    auto last = *prefixes.rbegin();
    return std::pair<std::string, double>(last.first, value/last.second);
}

void bench(std::string name, std::function<void()> func, double bench_seconds = 10)
{
    const double accurate_seconds = 0.1;

    std::cout << "benchmarking " << name << std::endl
              << "estimating iterations" << std::endl;
    size_t base_iterations = 1;
    double base_seconds = measure(func, base_iterations);
    while(base_seconds < accurate_seconds)
    {
        base_iterations *= 2;
        base_seconds = measure(func, base_iterations);
    }

    const size_t iterations = bench_seconds * base_iterations / base_seconds;
    const double estimated_seconds = iterations * base_seconds / base_iterations;
    std::cout << "estimated time " << estimated_seconds << " seconds (" << iterations << " iterations)" << std::endl;

    const double seconds = measure(func, iterations);
    const auto ips = human(iterations / seconds);
    const auto spi = human(seconds / iterations);
    std::cout << "benchmark took " << seconds << " seconds" << std::endl
              << "average speed " << ips.second  << ' ' << ips.first << " iterations per second" << std::endl
              << "average time " << spi.second << ' ' << spi.first << " seconds per iteration" << std::endl;
}

// plain brute-force lookup
template<class iterator>
std::list<typename iterator::value_type> brute_lookup(const typename iterator::value_type pattern, iterator begin, const iterator &end)
{
    typedef typename iterator::value_type value_type;
    std::list<value_type> result;

    for(;begin != end; ++begin)
    {
        if ((*begin & pattern) == pattern) result.push_back(*begin);
    }

    return result;
}

// tree-traversing lookup

template<class _value_type>
struct cover_node
{
    typedef _value_type value_type;

    value_type mask_a, mask_b;
    std::auto_ptr<cover_node<value_type>> node_a, node_b;
    std::vector<value_type> category_ab, category_zero;
};


template<class _value_type>
std::ostream &pprint(std::ostream &s, const std::auto_ptr<cover_node<_value_type>> &node, const std::string indent = "")
{
    if (!node.get())
    {
        s << indent << "cover_node: (null)" << std::endl;
        return s;
    }

    s << indent
      << "cover_node: mask = " << std::hex << node->mask_a << "/" << node->mask_b
      << ", leafs = " << std::dec << node->category_ab.size() << "/" << node->category_zero.size() << std::endl;

    const std::string sub = indent + "  ";
    pprint(s, node->node_a, sub);
    return pprint(s, node->node_b, sub);
}

enum class cover_category { a, b, ab, zero };

template<class vt>
cover_category
identify_cover(const vt mask_a, const vt mask_b, const vt x)
{
    const auto a = (x & mask_a) != 0;
    const auto b = (x & mask_b) != 0;

    if (!a)
    {
        if (!b) return cover_category::zero;
        else return cover_category::b;
    }
    else
    {
        if (!b) return cover_category::a;
        else return cover_category::ab;
    }
}

template<class vt>
vt bitmask(const size_t n, const size_t m)
{
    return (~0 << n) & ~(~0 << m);
}

template<class iterator>
std::auto_ptr<cover_node<typename iterator::value_type>>
build_cover_node(size_t n, size_t m, iterator begin, const iterator &end)
{
    const size_t split_boundary = 4;

    typedef typename iterator::value_type value_type;
    std::auto_ptr<cover_node<value_type>> node(new cover_node<value_type>);

    if ((m - n) < split_boundary) // too small group
    {
        // overlapped mask for simplification of sub-tree into list
        node->mask_a = ~0;
        node->mask_b = ~0;
        node->category_ab.insert(node->category_ab.end(), begin, end);
        return node;
    }

    std::list<value_type> category_a, category_b;

    const size_t h = (n + m) / 2;

    node->mask_a = bitmask<value_type>(n, h);
    node->mask_b = bitmask<value_type>(h, m);
    auto &category_ab = node->category_ab;
    auto &category_zero = node->category_zero;

    // categorize
    for(;begin != end; ++begin)
    {
        switch(identify_cover(node->mask_a, node->mask_b, *begin))
        {
        case cover_category::a:
            category_a.push_back(*begin);
            break;
        case cover_category::b:
            category_b.push_back(*begin);
            break;
        case cover_category::ab:
            category_ab.push_back(*begin);
            break;
        case cover_category::zero:
            category_zero.push_back(*begin);
            break;
        }
    }

    // build sub-nodes
    if (!category_a.empty()) node->node_a = build_cover_node(n, h, category_a.begin(), category_a.end());
    if (!category_b.empty()) node->node_b = build_cover_node(h, m, category_b.begin(), category_b.end());

    return node;
}

template<class _value_type>
struct cover_walker
{
    typedef _value_type value_type;
    typedef cover_node<value_type> node_type;

    cover_walker(value_type target_pattern, const node_type &root_node) :
        target(target_pattern)
    { 
        walk(root_node);
    }

    const std::list<value_type> &get_result() const
    {
        return result;
    }

private:
    value_type target;

    std::list<value_type> result;

    template<class Container>
    void filtered_add(const Container &xs)
    {
        for(auto it = xs.begin(); it != xs.end(); ++it)
        {
            const auto &x = *it;
            if ((x & target) == target) result.push_back(x);
        }
    }

    template<class Container>
    void add(const Container &xs)
    {
        result.insert(result.end(), xs.begin(), xs.end());
    }

    void flatout(const node_type &node)
    {
        if (node.node_a.get()) flatout(*node.node_a);
        if (node.node_b.get()) flatout(*node.node_b);
        add(node.category_ab);
        add(node.category_zero);
    }

    void walk(const node_type &node)
    {
        const auto &mask_a = node.mask_a;
        const auto &mask_b = node.mask_b;

        if (mask_a == mask_b)
        {
            filtered_add(node.category_ab);
            return;
        }

        switch(identify_cover(mask_a, mask_b, target))
        {
        case cover_category::a:
            if (node.node_a.get()) walk(*node.node_a);
            filtered_add(node.category_ab);
            break;

        case cover_category::b:
            if (node.node_b.get()) walk(*node.node_b);
            filtered_add(node.category_ab);
            break;

        case cover_category::ab:
            filtered_add(node.category_ab);
            break;

        case cover_category::zero:
            flatout(node);
            break;
        }
    }

};


int main()
{
    std::mt19937 rng;
    std::uniform_int_distribution<uint32_t> uint_dist;

    const auto bitmap = uint_dist(rng);
    //const uint32_t bitmap = 0;

    std::vector<uint32_t> bitmaps;
    bitmaps.resize(10000000);

    //for(auto it = bitmaps.begin(); it < bitmaps.end(); ++it) *it = uint_dist(rng);
    for(auto it = bitmaps.begin(); it < bitmaps.end(); ++it) *it = uint_dist(rng) & uint_dist(rng) & uint_dist(rng); // sparse

    const auto brute = [&bitmaps, bitmap](){
        brute_lookup(bitmap, bitmaps.begin(), bitmaps.end());
    };

    std::auto_ptr<cover_node<uint32_t>> cover_tree = build_cover_node<std::vector<uint32_t>::const_iterator>(0, 32, bitmaps.begin(), bitmaps.end());
    pprint(std::cout, cover_tree);

    const auto traversal = [&cover_tree, bitmap]() {
        cover_walker<uint32_t>(bitmap, *cover_tree).get_result();
    };

    bench("brute-force array search", brute);
    bench("tree-traversal search", traversal);


    return 0;
}

【讨论】:

  • 有趣,但我不知道 Haskell。
  • 我对这个问题的感觉是,您可以通过根据数据样本分布调整算法来获得更多性能。 IE。您可以按出现的顺序对位进行排序。使树更平衡和/或使模式/掩码在第一次迭代时排除更多数据。等
【解决方案3】:

此解决方案将占用与 M 中“1”位数成比例的内存, 但应该运行得相当快。我假设 集合 M 是静态的,有很多 Target 匹配请求。

预处理:

给定集合 M,将其按升序排序。接下来构造一个包含一个的数组 每比特插槽。您使用的是 32 位数字,因此您需要一个 32 个插槽的数组。调用这个数组:MBit[0..31]。 每个插槽包含 指向链表的指针(称之为:MPtr)。链表包含来自 M 的数字,其中 相应的位被设置。为了 例如 M 中设置了第 3 位的所有数字都将在链表中找到:MBit[3].MPtr.

基本算法是处理每个 MBit 列表 其中对应的目标编号设置了“1”位。只有所有已处理列表共有的数字 被选中。由于每个 MPtr 列表都包含排序后的数字,我们可以向前扫描直到我们正在寻找的数字 找到(匹配),找到更大的数字(不匹配)或列表已用尽(不再匹配)。

这种方法的主要缺点是来自 M 的相同数字将出现在尽可能多的 链表,因为它有 '1' 位。 这个有点 记忆受到打击,但你必须在某处给予一些东西!

大纲:

如上所述构建 MBit 数组。

为目标编号构建另一个数组数据结构。该数组包含 1 目标中每个位的插槽(称为:TBit[0..31])。每个插槽 包含一个链表指针(称之为:MPtr)和一个布尔值(称之为:BitSet)。 BitSet 表示是否对应 设置了一些目标。

给定一个新目标:

/* Initialize each slot of TBit to the head of the corresponding MBit Linked list */
if Target == 0 then goto Done      /* Target contains only zero bits - no matches */
for (i = 0; i < 32; i++) {         /* Bit 0 is LSB, Bit 31 is MSB */
   TBit[i].MPtr = MBit[i].MPtr     /* List of numbers with bit i set */
   TBit[i].BitSet = (Target && 1)  /* Target bit i set? */
   Target = Target >> 1            /* Shift 1 bit right */
}

/* Iterate until one of the linked lists in TBit is exhausted */
for(;;) {
   First1Bit = False          /* Found first '1' bit in Target for this iteration */
   AcceptCandidate = True     /* Assume Candidate number matches all '1' bits in Target */
   for (i = 0; i < 32 & AcceptCandidate; i++) { /* For each bit in TBit Array... */
      if !TBit[i].BitSet then iterate          /* Target bit is zero, nothing to add */
      if !First1Bit then {                     /* First Target '1' bit, initialize for iteration */
         if TBit[i].MPtr == Nil then goto Done /* List exhausted, no more matches possible */
         Candidate = value(TBit[i].MPtr)       /* Candidate Number from linked list */
         TBit[i].MPtr = next(TBit[i].MPtr)     /* setup for next cycle */
         First1Bit = True                      /* First 1 bit for this cycle completed */
      } else {
         /* Scan list until Candidate or larger number found... */
         while (TBit[i].MPtr != Nil & value(TBit[i].MPtr) < Candidate) {
            TBit[i].MPtr = next(TBit[i].MPtr)
         }
         if TBit[i].MPtr = Nil then goto Done  /* List exhausted, no more matches possible */
         AcceptCandidate = (value(TBit[i].MPtr) == Candidate)
      }
   }
   if AcceptCandidate then {
      /* Candidate contains a '1' bit in the same positions Target contains a '1' bit */
      /* Do what you need to do with Candidate */
   }
}
Done: /* No further matches on Target are possible */ 

我可以看到对上述大纲的一些优化,但认为这是一个好的开始。

【讨论】:

  • 平均行为可能比原始搜索好一点,但在糟糕的情况下,您仍然可以搜索几乎整个项目列表。想象一下 1111111111111110111111111111111 作为目标。
  • 这个解决方案可能比蛮力更糟糕。考虑如果M 数字是均匀分布的,则每个数字都有0.5 的概率出现在其中一个插槽中。如果目标模式仅包含 8 位设置,则要检查的预期数字量是 4*M,而不是使用蛮力方法的 M。
  • @Simon Stelling。好点。经过反思,我同意,我上面的“解决方案”实际上比问题中给出的所有情况下的“稻草人”burte force 方法更糟糕,除非 Target 只设置了 1 位,然后答案已经预先计算。
  • @codist 给定1111111111111110111111111111111 的目标,只有 2 个可能的 32 位整数可以选择 - 大量过滤才能到达那里。正如 Simon 指出的那样,我的每个 MBit 列表将包含集合 M 中大约一半的数字。因此,如果 M 包含 100000 个数字,我们需要平均运行 50000 个数字乘以 Target 中的“1”位的数量。对于 31 个“1”位,可以计算出 1550000 个数字 - 超过集合 M 的 15 倍!从头开始这个解决方案。
【解决方案4】:

这似乎是 SQL 数据库擅长的。如果您在 (MSB, BitsSet, Value) 上放置一个复合索引,您的结果应该会非常快。

IntegerList:
    Value INT
    BitsSet INT
    MSB INT

INSERT INTO IntegerList(Value, BitsSet, MSB) VALUES(@Value, GetBitsSet(@Value), GetMSB(@Value)

SELECT Value FROM IntegerList WHERE MSB = GetMSB(@Target) AND BitsSet >= GetBitsSet(@Target) AND (Value & @Target) = @Target

---GetMSB
DECLARE @b BIGINT
DECLARE @c INT
SELECT @b = 0x80000000
SELECT @c = 32
WHILE (@b <> 0)
BEGIN
    IF (@b & @value) = @b
    BEGIN
        RETURN @c
    END
    SELECT @b = @b / 2
    SELECT @c = @c - 1
END

---GetBitsSet
DECLARE @b BIGINT
DECLARE @c INT
SELECT @b = 0x80000000
SELECT @c = 0
WHILE (@b <> 0)
BEGIN
    IF (@b & @value) = @b
    BEGIN
        SELECT @c = @c + 1
    END
    SELECT @b = @b / 2
END
RETURN @c

如果您必须直接使用 C++,我建议您尝试模拟 SQL 方法。

使用 int Value、BitsSet、MSB 创建结构或类

创建 2 个节点数组,一个为 MSB 排序,另一个为 BitsSet。

对 MSB(匹配 Target 的 MSB)数组和 BitsSet(匹配所有 BitsSet >= Target)数组使用二分查找。

获取这两个结果的并集,然后执行 Target & Value == Target 检查。

【讨论】:

    【解决方案5】:

    一般方法。

    逐位构建树。一级节点是第一位,而二级节点是第二位,...

    当你得到面具时,你只是否定它,你知道你必须排除树的哪些部分。
    比只能快速遍历相关的槽节点。

    N_bits 空间解*
    只需对这些整数进行排序,然后使用二分搜索来遍历这棵树。

    复杂度 O(N_results*N_bits))

    与蛮力 O(N) 相比,它的运行速度似乎快了 3 倍。但这是我在 C++ 中的第一个代码,所以我可能会遗漏一些东西。任何关于代码的评论也很酷。

    代码是如何工作的?
    它使用的唯一数据结构是输入的排序数组。
    在每个步骤中,它使用二分搜索 std::lower_bound();
    基于 bound 将数组拆分为两部分 如果 mask[depth] 为 1,则不需要在这棵树的左侧 无论如何,它必须正确。

    如果你设置像 0xFFFFFFFF 这样的掩码,它总是只会正确运行,并且会在 log(n) 时间内执行 如果您放置掩码 0x00000000 它将返回所有解决方案,因此它将在左右每个步骤中进行,并且会比简单循环执行得更差。一旦数组的大小小于 10(可以更改),它就会使用简单的方法返回输出向量中的所有解决方案。

    在长度为 100k 且 mask 0x11111111(8 位)的随机输入向量上,它的运行速度比简单循环快两倍。

    #include <iostream>
    #include <algorithm>
    #include <vector>
    using namespace std;
    
    void find_masks(const int mask,const int bound,const int depth,const vector<int>::iterator begin,const vector<int>::iterator end, vector<int> &output )
    {
        vector<int>::iterator i,split;
        if( ( distance(begin,end)<10 ) | (depth==0) ) //if less than 10 we just bruteforce it is also stopping condition
        {
            for(i=begin; i!=end; i++)
            {
                if(mask == (int)(mask & (*i)))
                {
                    output.push_back(*i);
                }
            }
            return;
        }
    
        int bitmask =  (1<<depth) ;
        split=lower_bound(begin,end,bound | bitmask );
    
        if( !(mask & bitmask) ) //go left if mask == 0 at this point
        {
            find_masks(mask,bound,depth-1,begin,split, output );
        }
        find_masks(mask,bound | bitmask ,depth-1,split, end, output );
    }
    
    
    int main ()
    {
        vector<int> result,v,bruteforce;
        vector<int>::iterator i;
    
        //100k random vector
        for(int i=0; i<100000; i++)
        {
            int r=0;
            for(int j=0; j<4; j++)
            {
                r=r<<8;
                r=r^rand();
            }
            v.push_back(r);
        }
    
        sort(v.begin(),v.end());
    
        int mask=0xF0F;
        //use sorted vector and binary search for traversing tree
        find_masks(mask,0,31,v.begin(),v.end(), result );
    
        //use naive loop
        bruteforce.erase(bruteforce.begin(),bruteforce.end());
        for(i=v.begin(); i!=v.end(); i++)
        {
            if(mask == (int)(mask & (*i)))
            {
                bruteforce.push_back(*i);
            }
        }
    
        cout<<"n solutions binary search " << distance(result.begin(),result.end())<<endl;
        cout<<"n solutions loop "  << distance(bruteforce.begin(),bruteforce.end())<<endl;
        cout<<"are solutions same => " << equal(result.begin(),result.end(),bruteforce.begin());
    
        return 0;
    }
    

    【讨论】:

    • 有趣的解决方案,但它不需要树中的 2^32 个节点吗? [每个位的分支因子为 2,因此总共 2^32 个节点]。
    • 这也是我的第一个想法……直到我计算了树的大小。
    • 重点是构建树,你只需使用排序的整数数组并使用二进制搜索遍历这个“树”
    • 不完全匹配只是代码以这种方式运行,将掩码替换为 1 之类的东西。如果掩码设置了很多位并且解决方案的数量很少,它具有很好的性能。但是其他方面很糟糕
    • 是的,这将通过为 lower_bound 采取额外的滴答声来节省您的空间。我还要补充一点,对于设置为 1 的最重要的位,它会更好地工作。即较高位的一位将排除搜索树的一半,但较低的一位将导致通往该位的许多路径,并可能将它们全部一分为二。
    猜你喜欢
    • 2010-10-01
    • 2020-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-17
    • 2018-08-20
    相关资源
    最近更新 更多