Qual é a maneira mais eficiente de testar dois intervalos inteiros para sobreposição?


251

Dado dois intervalos inteiros inclusivos [x1: x2] e [y1: y2], onde x1 ≤ x2 e y1 ≤ y2, qual é a maneira mais eficiente de testar se existe alguma sobreposição dos dois intervalos?

Uma implementação simples é a seguinte:

bool testOverlap(int x1, int x2, int y1, int y2) {
  return (x1 >= y1 && x1 <= y2) ||
         (x2 >= y1 && x2 <= y2) ||
         (y1 >= x1 && y1 <= x2) ||
         (y2 >= x1 && y2 <= x2);
}

Mas espero que haja maneiras mais eficientes de calcular isso.

Qual método seria o mais eficiente em termos de menos operações.


Pode ser interessante relacionado para alguns - stackoverflow.com/q/17138760/104380
vsync

Respostas:


454

O que significa que os intervalos se sobrepõem? Isso significa que existe algum número C que está em ambos os intervalos, ou seja,

x1 <= C <= x2

e

y1 <= C <= y2

Agora, se pudermos assumir que os intervalos estão bem formados (de modo que x1 <= x2 e y1 <= y2), então é suficiente testar

x1 <= y2 && y1 <= x2

1
Eu acredito que deveria ser x1 <= y2 && y1 >= x2, não?
David Beck

8
@DavidBeck: não, se y1> x2, os intervalos definitivamente não se sobrepõem (por exemplo, considere [1: 2] e [3: 4]: y1 = 3 e x2 = 2, então y1> x2, mas não há sobreposição) .
Simon Nickerson

8
isso seria uma resposta melhor se você explicou o raciocínio um pouco mais
shoosh

2
@ Vinine Deoraj - Por que você acha que não funciona? x1 = 1, y1 = 1, x2 = 1, y2 = 1, então x1 <= y2 && y1 <= x2 é verdadeiro; portanto, há uma sobreposição.
dcp

2
A explicação está aqui: stackoverflow.com/questions/325933/…
Alex

138

Dado dois intervalos [x1, x2], [y1, y2]

def is_overlapping(x1,x2,y1,y2):
    return max(x1,y1) <= min(x2,y2)

4
@ uyuyuy99 - só não é tão eficiente, porque quando esta verificação está sendo feito muitas vezes por segundo, chamando a função é algo que você gostaria de evitar, e fazer o máximo de matemática mesmo, mantê-lo para o básico
vsync

7
@vsync Os navegadores modernos incorporarão e otimizarão funções como Math.max, não deve haver impacto perceptível no desempenho.
Ashton Six

1
@AshtonWar - interessante. você tem um artigo explicando o que é incorporado e o que não é?
vsync

@vsync Não, mas eu tenho certeza que você pode encontrar a informação você mesmo
Ashton Six

6
Além disso, observe que min(x2,y2) - max(x1,y1)fornece a quantidade de sobreposição, caso você precise.
usar o seguinte comando

59

Isso pode deformar facilmente um cérebro humano normal, então achei uma abordagem visual mais fácil de entender:

sobreposição de loucura

le Explicação

Se dois intervalos são "muito gordos" para caber em um slot que é exatamente a soma da largura de ambos, eles se sobrepõem.

Para intervalos [a1, a2]e [b1, b2]isso seria:

/**
 * we are testing for:
 *     max point - min point < w1 + w2    
 **/
if max(a2, b2) - min(a1, b1) < (a2 - a1) + (b2 - b1) {
  // too fat -- they overlap!
}

3
Existem mais casos do que os retratados nas suas fotos. Por exemplo, e se w2 começar antes de w1 e terminar após w1?
precisa saber é o seguinte

7
@WilliamKF a lógica permanece fiel
FloatingRock

2
Concordo, mas acho que pode ajudar a fornecer uma terceira imagem.
WilliamKF

3
@WilliamKF então você precisa de um monte de outras imagens existem 16 combinações diferentes que 2 faixas podem ser colocados em ...
Peter

3
Tenha cuidado se você usar esse método, porque a soma a2 - a1 + b2 - b1pode transbordar. Para corrigi-lo, reorganize a fórmula para max(a2, b2) - a2 - b2 < min(a1, b1) - a1 - b1, o que simplifica max(a1, b1) < min(a2, b2), economizando aritmética e evitando possíveis estouros (esta é a resposta do AX-Labs abaixo). No caso especial em que você sabe b2-b1=a2-a1, outro rearranjo útil da fórmula de FloatingRock é max(a2, b2) - min(a1, b1) - (b2 - b1) < a2-a1, que se torna abs(b1-a1) < a2 - a1.
Paolo Bonzini

44

Ótima resposta de Simon , mas para mim foi mais fácil pensar em caso inverso.

Quando 2 faixas não se sobrepõem? Eles não se sobrepõem quando um deles começa depois que o outro termina:

dont_overlap = x2 < y1 || x1 > y2

Agora é fácil expressar quando eles se sobrepõem:

overlap = !dont_overlap = !(x2 < y1 || x1 > y2) = (x2 >= y1 && x1 <= y2)

1
Para mim, a expressão mais fácil de entender é: x2 <y1 || y2 <x1 // onde eu uso 'menos que' em vez de "maior que".
Park JongBum 21/10/19

25

Subtrair o mínimo dos fins dos intervalos do máximo do início parece ser suficiente. Se o resultado for menor ou igual a zero, temos uma sobreposição. Isso visualiza bem:

insira a descrição da imagem aqui


2
Isso abrange todos os casos
user3290180

10

Suponho que a pergunta era sobre o código mais rápido, não o mais curto. A versão mais rápida precisa evitar ramificações, para que possamos escrever algo como isto:

para casos simples:

static inline bool check_ov1(int x1, int x2, int y1, int y2){
    // insetead of x1 < y2 && y1 < x2
    return (bool)(((unsigned int)((y1-x2)&(x1-y2))) >> (sizeof(int)*8-1));
};

ou, para este caso:

static inline bool check_ov2(int x1, int x2, int y1, int y2){
    // insetead of x1 <= y2 && y1 <= x2
    return (bool)((((unsigned int)((x2-y1)|(y2-x1))) >> (sizeof(int)*8-1))^1);
};

7
Tenha fé em seu compilador. A expressão x1 <= y2 && y1 <= x2 também não possui ramificações , assumindo um compilador e uma arquitetura de CPU razoavelmente competentes (mesmo em 2010). De fato, no x86, o código gerado é basicamente idêntico para a expressão simples versus o código nesta resposta.
Søren Løvborg


4

Se você estava lidando com, dado dois intervalos [x1:x2]e [y1:y2], ordem natural / antinatural varia ao mesmo tempo em que:

  • ordem natural: x1 <= x2 && y1 <= y2ou
  • ordem anti-natural: x1 >= x2 && y1 >= y2

você pode usar isso para verificar:

eles estão sobrepostos <=> (y2 - x1) * (x2 - y1) >= 0

onde apenas quatro operações estão envolvidas:

  • duas subtrações
  • uma multiplicação
  • uma comparação

1

Se alguém estiver procurando por uma linha que calcule a sobreposição real:

int overlap = ( x2 > y1 || y2 < x1 ) ? 0 : (y2 >= y1 && x2 <= y1 ? y1 : y2) - ( x2 <= x1 && y2 >= x1 ? x1 : x2) + 1; //max 11 operations

Se você deseja menos operações, mas mais algumas variáveis:

bool b1 = x2 <= y1;
bool b2 = y2 >= x1;
int overlap = ( !b1 || !b2 ) ? 0 : (y2 >= y1 && b1 ? y1 : y2) - ( x2 <= x1 && b2 ? x1 : x2) + 1; // max 9 operations

1

Pense de maneira inversa : como fazer as duas faixas não se sobreporem ? Dado [x1, x2], então [y1, y2]deve estar fora [x1, x2] , ou seja, o y1 < y2 < x1 or x2 < y1 < y2que equivale ay2 < x1 or x2 < y1 .

Portanto, a condição para fazer os 2 intervalos se sobrepõe:, o not(y2 < x1 or x2 < y1)que é equivalente a y2 >= x1 and x2 >= y1(o mesmo com a resposta aceita por Simon).


Parece o mesmo que o @damluar respondeu (2 / mar / 16 às 17:36)
Nakilon

0

Você já tem a representação mais eficiente - é o mínimo necessário que deve ser verificado, a menos que você tenha certeza de que x1 <x2 etc, e use as soluções fornecidas por outras pessoas.

Você provavelmente deve observar que alguns compiladores realmente otimizarão isso para você - retornando assim que qualquer uma dessas 4 expressões retornar verdadeira. Se um retornar verdadeiro, o resultado final será o mesmo - para que as outras verificações possam ser ignoradas.


2
Todos os compiladores irão. Todas as linguagens atualmente utilizadas (com o meu conhecimento) com sintaxe no estilo C (C, C ++, C #, Java etc.) empregam operadores booleanos em curto-circuito e fazem parte dos vários padrões que governam essas linguagens. Se o resultado do valor à esquerda for suficiente para determinar o resultado da operação, o valor à direita não será avaliado.
Jonathan Grynspan

1
Marca H - o compilador irá pular a segunda cláusula se puder: portanto, se você tiver uma função que diz: foo (int c) {int i = 0; if (c <3 || ++ i == argc) printf ("Interior \ n"); printf ("i é% d \ n", i); O Foo (2) será impresso: Dentro de i é 0 e o Foo (4) será impresso: i é 1 (testado no gcc 4.4.3, mas eu confiei nesse comportamento para algum código feio no icc também)
J Teller

0

Meu caso é diferente. Eu quero verificar dois intervalos de tempo se sobrepõem. não deve haver sobreposição de tempo unitário. aqui está a implementação Go.

    func CheckRange(as, ae, bs, be int) bool {
    return (as >= be) != (ae > bs)
    }

Casos de teste

if CheckRange(2, 8, 2, 4) != true {
        t.Error("Expected 2,8,2,4 to equal TRUE")
    }

    if CheckRange(2, 8, 2, 4) != true {
        t.Error("Expected 2,8,2,4 to equal TRUE")
    }

    if CheckRange(2, 8, 6, 9) != true {
        t.Error("Expected 2,8,6,9 to equal TRUE")
    }

    if CheckRange(2, 8, 8, 9) != false {
        t.Error("Expected 2,8,8,9 to equal FALSE")
    }

    if CheckRange(2, 8, 4, 6) != true {
        t.Error("Expected 2,8,4,6 to equal TRUE")
    }

    if CheckRange(2, 8, 1, 9) != true {
        t.Error("Expected 2,8,1,9 to equal TRUE")
    }

    if CheckRange(4, 8, 1, 3) != false {
        t.Error("Expected 4,8,1,3 to equal FALSE")
    }

    if CheckRange(4, 8, 1, 4) != false {
        t.Error("Expected 4,8,1,4 to equal FALSE")
    }

    if CheckRange(2, 5, 6, 9) != false {
        t.Error("Expected 2,5,6,9 to equal FALSE")
    }

    if CheckRange(2, 5, 5, 9) != false {
        t.Error("Expected 2,5,5,9 to equal FALSE")
    }

você pode ver que existe um padrão XOR na comparação de limites


-10

Aqui está a minha versão:

int xmin = min(x1,x2)
  , xmax = max(x1,x2)
  , ymin = min(y1,y2)
  , ymax = max(y1,y2);

for (int i = xmin; i < xmax; ++i)
    if (ymin <= i && i <= ymax)
        return true;

return false;

A menos que você esteja executando um verificador de intervalo de alto desempenho em bilhões de números inteiros com espaçamento amplo, nossas versões deverão ter o mesmo desempenho. O que quero dizer é que isso é micro-otimização.


Acho que você examinou a especificação aqui. Supõe-se que x1 a x2 esteja subindo / descendo (de qualquer forma, está classificado) - não há necessidade de um loop, você só precisa verificar os elementos de cabeça e cauda. No entanto, prefiro a solução min / max - simplesmente porque é mais fácil ler quando você volta ao código mais tarde.
Mark H

12
-1: isso não é micro-otimização; isso é escolher um algoritmo apropriado. Seu algoritmo é O (n) quando existe uma opção O (1) simples.
Simon Nickerson

É o que acontece quando "a otimização prematura é a raiz de todo mal" se torna um princípio religioso inviolável para os ineptos, em vez de uma observação meio séria sobre algum padrão ocasional de comportamento.
Rghome 9/04/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.