《編程之美》學習筆記——2.19區間重合判斷

一、問題

  給定一個源區間[x,y](y>=x)和N個無序的目標區間[x1,y1],[x2,y2],...,[xn,yn],判斷源區間[x,y]是不是在目標區間內。

例:

  給定源區間[1 6]和一組無須的目標區間[2 3][1 2][3 9],即可認爲區間[1 6]在區間[2 3][1 2][3 9]內(因爲目標區間實際上時[1,9])。

問題分析:

  輸入:源區間[x,y],可以用一個長度爲2的數組表示;N個無序的目標區間[x1,y1],[x2,y2],...,[xn,yn],可用爲長度爲N,子數組長度爲2的二維數組表示。

  輸出:1值表示源區間[x,y]在目標區間內,0值表示源區間[x,y]不在目標區間內。

  約束:爲簡化分析,這裏表示區間的均是整數值,實際上可以考慮浮點表示;輸入的所有區間[xi,yi]有xi <= yi。

二、解法

  解法一 遍歷合併(自我思考實現)

思考:雖然問題中含有表示區間的含義[xi,yi],但這裏輸入的所有的數據量爲(N + 1) * 2。判斷源區間是否在目標區間內,我們可以先對目標區間進行處理,將N個目標區間處理爲n(n <= N)個區間(合併),處理後的區間有如下特點:兩兩交集均爲零。接下來通過逐一判斷源區間[x,y]是否在這些處理後的n個區間內即可知道源區間是否在目標區間內。

我們現在考慮如何將N個目標區間處理爲n(n <= N)個區間(兩兩交集均爲空集)。考慮到區間可能時大於兩個的目標區間的並集,我們採用分治或遞歸的想法,不斷進行兩兩有重合的區間求並集,直到任意兩個區間間交集均爲0。任意兩個區間之間是否重疊,如[x1,y1]和[x2,y2],通過判斷x1,x2,y1,y2之間的大小關係即可。

時間複雜度:嵌套遍歷處理目標區間時間複雜度爲O(n^2),判斷源區間是否在這些區間內時間複雜度爲O(n),算法總時間複雜度爲O(n^2)。

算法C實現:

設置標誌位0和1分別表示處理後留下來的區間和丟棄的區間(當兩個區間有重疊時修改前一個區間使之爲這兩個區間的並集,同時丟棄後一個區間)。

採用嵌套遍歷,使得遍歷後留下來的區間爲對N個目標區間進行處理後的n個區間,並滿足兩兩交集爲空集。

/**
 * @file range_overlap_judgement.c
 * @brief judge if the given source range is in the given target ranges.
 * @author [email protected]
 * @version 1.0
 * @date 2015-02-03
 */

#include <stdlib.h>
#include <stdio.h>
// #define NDEBUG
#include <assert.h>

#include "memory.h"
// #define NDBG_PRINT
#include "debug_print.h"

typedef int TYPE;

#define MAX_COUNT      10
TYPE array[MAX_COUNT][2] = {{0}};

/**
 * @brief judge if the given source range is in the given target ranges.
 *
 * @param[in]     source   source range array length 2
 * @param[in]     target   target range, each range has two values
 * @param[in]     count    target range count
 *
 * @return return 1 if the given source range is in the given target ranges,
 * others return 0.
 */
TYPE range_overlap_judgement(TYPE* source, TYPE* target, TYPE count)
{
    // x <= y for range [x, y]
    assert(source[0] <= source[1]);
    TYPE i, j;
    for (i = 0; i < count; i++) {
        assert(target[i << 1] <= target[(i << 1) + 1]);
    }
    // target range combined
    TYPE *x1, *y1, *x2, *y2;
    TYPE* flag_targe_done = SCALLOC(count, TYPE);
    /// get first range
    for (i = 0; i < count; i++) {
        if (flag_targe_done[i])
            continue;
        x1 = target + (i << 1);
        y1 = target + (i << 1) + 1;
        /// do with the second range
        for (j = i + 1; j < count; j++) {
            if (flag_targe_done[j])
                continue;
            x2 = target + (j << 1);
            y2 = target + (j << 1) + 1;
            /// process two relative range
            if (*x2 >= *x1 && *x2 <= *y1) {
                if (y1 <= y2) {
                    /// refresh range
                    *y1 = *y2;
                }
                flag_targe_done[j] = 1;
            } else if (*y2 >= *x1 && *y2 <= *y1) {
                if (*x2 <= *x1) {
                    /// refresh range
                    *x1 = *x2;
                }
                flag_targe_done[j] = 1;
            } else if(*x2 <= *x1 && *y1 <= *y2) {
                /// refresh range
                *x1 = *x2;
                *y1 = *y2;
                flag_targe_done[j] = 1;
            }
        }
    }
    // display the processed range
    DEBUG_PRINT_STRING("test flag and range.\n");
    for (i = 0; i < count; i++) {
        /// ignore useless range
        if (!flag_targe_done[i]) {
            DEBUG_PRINT_VALUE("%d", target[i << 1]);
            DEBUG_PRINT_VALUE("%d", target[(i << 1) + 1]);
        }
    }
    // source range detect
    DEBUG_PRINT_STRING("test source range.\n");
    for (i = 0; i < count; i++) {
        /// ignore useless range and test if source range in the target range
        if (!flag_targe_done[i] && target[i << 1] <= source[0] &&
                target[(i << 1) + 1] >= source[1]) {
            SFREE(&flag_targe_done);
            return 1;
        }
    }
    SFREE(&flag_targe_done);
    return 0;
}

int main(void) {
    /// read range to array, each range write as [32,24]
    TYPE count = 0;
    while(count < MAX_COUNT && scanf("[%d,%d]\n",
                *(array + count), *(array + count) + 1) == 2) {
        ++count;
    }
    /// make last range as source range
    TYPE source[2] = {0};
    count--;
    source[0] = array[count][0];
    source[1] = array[count][1];
    printf("the source range(ex:3 5) is [%d,%d]\n",source[0], source[1]);
    printf("the target range count = %d\n", count);
    /// output result
    if(range_overlap_judgement(source, (TYPE*)array, count)) {
        printf("The source range is in the target range.\n");
    } else {
        printf("The source range is not in the target range.\n");
    }
    return EXIT_SUCCESS;
}

  解法二 (參考 《編程之美》)

  解法一可以改善的地方是合併目標區間的算法,解法一採用的是嵌套遍歷,時間複雜度是O(n^2)。解法二採用另一種和並目標區間的算法,思路如下:首先利用區間的左值邊界先對區間進行快速排序O(n*lgn),再進行合併O(n)。這樣接下來可以使用二分查找進行查找(單次查找O(lgn))並判斷源區間是否在目標區間內,(思路一如下:分別查找源區間的兩個邊界處於排序合併後的哪個區間,再判斷這兩個區間是否爲同一個區間,若爲同一個,則說明源區間在目標區間內;思路二如下:查找源區間的一個邊界處於排序後並後哪個區間,再判斷另一個邊界是否位於此區間內)。這種算法總的時間複雜度爲O(n*lgn + n + k*lgn)(k爲查找次數)。不僅總的時間複雜度減低了,還有一個優點就是:若給定多個源區間(k >> n),則判定每個源區間是否在目標區間內僅需O(lgn)時間,大大加快來查找的速度(解法一需O(n)時間)。

  分析:儘管N個目標區間均採用兩個數而不是一個數來表示,但是仍然可以利用快速排序和二分查找算法對這些數據進行處理,稍微變換下排序思路和查找思路即可,可見排序查找的應用是相當廣泛的,並不侷限一個單個數據,可以拓展到其他類型。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章