5

编程小知识 之 range search

 3 years ago
source link: https://blog.csdn.net/tkokof1/article/details/115558978
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

编程小知识 之 range search

本文简述了 range search 的一些知识

所谓 range search,是指给定一些数值范围(range)和某一数值,我们来搜索该数值所属的数值范围,譬如我们给定以下的数值范围:

  • [0, 10] -> range 0
  • [11, 50] -> range 1
  • [51, 100] -> range 2

给定的数值为 32,那么我们可以搜索得到 32 所属的数值范围为: range 1.

如果给定的数值范围比较少,我们直接遍历搜索就可以了,代码大概如下:

public int RangeSearch(int key)
{
    var count = rangeConfig.Length;
    for (int i = 0; i < count; ++i)
    {
        var config = rangeConfig[i];
        if (key >= config.Min && key <= config.Max)
        {
            return config.Value;
        }
    }

    return -1;
}

但是如果给定的数值范围较多时,遍历的时间消耗就比较大了,考虑到范围本身是有序的,我们很容易想到可以用二分搜索来优化:

public int RangeSearch(int key)
{
    int count = rangeConfig.Length;
    int l = 0;
    int r = count - 1;
    while (l <= r)
    {
        int m = l + (r - l) / 2;
        var config = rangeConfig[m];

        if (key >= config.Min && key <= config.Max)
        {
            return config.Value;
        }

        if (config.Max < key)
        {
            l = m + 1;
        }
        else
        {
            r = m - 1;
        }
    }

    return -1;
}

二分搜索其实已经比较快了,但是结合范围特性,我们还可以做的更好.

  • 范围连续并且间隔相等

如果范围连续并且间隔相等,我们就可以对数值进行直接的范围映射:

假设范围的数值从 v v v 开始,并且间隔为 i i i,那么我们可以直接使用以下公式来进行映射范围:

i n d e x = f l o o r ( ( v a l u e − v ) / i ) index = floor((value - v)/i) index=floor((value−v)/i)

代码如下:

public int RangeSearch(int key)
{
    int index = (key - v) / i;
    if (index >= 0 && index < rangeConfig.Length)
    {
        return rangeConfig[index].Value;
    }

    return -1;
}
  • 数值所属范围集中

如果给定的数值范围很多,但实际数值所属的范围很集中,那么我们可以使用基于熵的方法来优化:

一种方法就是将范围按集中度进行排序,然后遍历搜索

public int RangeSearch(int key)
{
    // NOTE rangeConfig is ordered by aggregation degree
    var count = rangeConfig.Length;
    for (int i = 0; i < count; ++i)
    {
        var config = rangeConfig[i];
        if (key >= config.Min && key <= config.Max)
        {
            return config.Value;
        }
    }

    return -1;
}

另一种就是每次搜索都将搜索得到的范围前移:

public int RangeSearch(int key)
{
    var count = rangeConfig.Length;
    for (int i = 0; i < count; ++i)
    {
        var config = rangeConfig[i];
        if (key >= config.Min && key <= config.Max)
        {
            if (i > 0)
            {
                var pre = rangeConfig[i - 1];
                rangeConfig[i - 1] = rangeConfig[i];
                rangeConfig[i] = pre;
            }
            return config.Value;
        }
    }

    return -1;
}

SplayTree 很适合来做这类前置操作:

public int RangeSearch(int key)
{
    var node = splayTree.Find(key);
    if (node != null)
    {
        return node.Key.Value;
    }

    return -1;
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK