수직 가시성 문제에 대한 효율적인 알고리즘


18

한 가지 문제에 대해 생각하는 동안 다음 작업을 해결하는 효율적인 알고리즘을 만들어야한다는 것을 깨달았습니다.

문제 : 우리는 측면이 축과 평행 한 측면 의 2 차원 사각형 상자가 제공 됩니다. 상단을 통해 살펴볼 수 있습니다. 그러나 수평 세그먼트 도 있습니다. 각 세그먼트에는 정수 좌표 ( )와 좌표 ( )가 있으며 점 와 를 연결합니다 ( 아래 그림).m y 0 y n x 0 x 1 < x 2n ( x 1 , y ) ( x 2 , y )nmy0ynx0x1<x2n(x1,y)(x2,y)

상자 상단의 각 단위 세그먼트에 대해이 세그먼트를 살펴보면 상자 내부를 세로로 볼 수있는 깊이를 알고 싶습니다.

공식적으로 경우 입니다.x{0,,n1}maxi: [x,x+1][x1,i,x2,i]yi

예 : 아래 그림과 같이 n=9m=7 세그먼트가 주어지면 결과는 (5,5,5,3,8,3,7,8,7) 입니다. 상자에 빛이 얼마나 깊이 들어갈 수 있는지보십시오.

일곱 세그먼트;  음영 부분은 빛에 도달 할 수있는 영역을 나타냅니다

다행스럽게도 nm매우 작으며 계산을 오프라인으로 수행 할 수 있습니다.

이 문제를 해결하는 가장 쉬운 알고리즘은 무차별입니다. 각 세그먼트마다 전체 배열을 통과하고 필요한 경우 업데이트합니다. 그러나 그것은 우리에게 매우 인상적인 O (mn)을 제공하지 않습니다 O(mn).

쿼리 중에 세그먼트의 값을 최대화하고 최종 값을 읽을 수있는 세그먼트 트리를 사용하면 크게 개선됩니다. 더 자세히 설명하지는 않지만 시간 복잡도는 입니다.O((m+n)logn)

그러나 더 빠른 알고리즘을 생각해 냈습니다.

개요:

  1. 좌표 (카운팅 정렬의 변형을 사용하여 선형 시간) 의 내림차순으로 세그먼트를 정렬하십시오. unit 세그먼트가 이전에 어떤 세그먼트로 덮여 있었다면 더 이상 이 unit 세그먼트를 통과하는 광선을 추적 할 수있는 세그먼트는 없습니다. 그런 다음 상자의 상단에서 하단으로 라인 스윕을 수행합니다.x xyxx

  2. 지금하자 약간의 정의를 도입 : -unit 세그먼트 스윕에 가상의 수평 세그먼트 -coordinates 정수이며, 길이가 1 인 주사 과정 동안 각 세그먼트 이어도 표시 해제 이다 (즉, 광 빔으로부터가는 상자 상단이이 세그먼트에 도달하거나 표시 (반대 ) 할 수 있습니다 . 고려 와 -unit 세그먼트 , 항상 표시 해제한다. 집합도 소개하겠습니다 . 각 세트에는 다음과 같이 표시되지 않은 연속 된 마크 된 단위 세그먼트 (있는 경우) 의 전체 시퀀스가 ​​포함됩니다.x x x 1 = n x 2 = n + 1 S 0 = { 0 } , S 1 = { 1 } , , S n = { n } xxxxx1=nx2=n+1S0={0},S1={1},,Sn={n} x 분절.

  3. 이러한 세그먼트에서 작동하고 효과적으로 설정할 수있는 데이터 구조가 필요합니다. 최대 단위 세그먼트 인덱스 ( 표시되지 않은 세그먼트의 인덱스)를 보유하는 필드로 확장 된 찾기 연합 구조 를 사용 합니다 .x

  4. 이제 세그먼트를 효율적으로 처리 할 수 ​​있습니다. 에서 시작 하여 끝나는 번째 세그먼트를 순서대로 고려하고 있다고 가정 해 봅시다 ( "query"라고 함) . 번째 세그먼트 안에 포함 된 모든 표시되지 않은 단위 세그먼트 를 찾아야합니다 (이것은 광선이 끝나는 세그먼트입니다). 먼저 다음을 수행 할 것입니다. 첫째, 쿼리 에서 첫 번째 표시되지 않은 세그먼트를 찾습니다 ( 이 포함 된 세트의 대표자를 찾고 정의에 의해 표시되지 않은 세그먼트 인이 세트의 최대 색인을 가져옵니다 ). 그런 다음이 색인x 1 x 2 x i x 1 x y x x + 1 x x 2ix1x2 xix1x는 쿼리 안에 있고 결과에 추가하고 (이 세그먼트의 결과는 )이 인덱스를 표시합니다 ( 및 포함하는 Union 세트 ). 그런 다음 표시되지 않은 모든 세그먼트를 찾을 때까지이 절차를 반복하십시오 . 즉, 다음 찾기 쿼리는 인덱스 합니다.yxx+1xx2

각 찾기 연합 작업은 두 가지 경우에만 수행됩니다. 세그먼트를 고려하기 시작하거나 ( 번 발생할 수 있음 ) 단위 세그먼트를 표시했습니다 ( 번 발생할 수 있음 ). 따라서 전체 복잡도는 ( 는 역행렬 (Ackermann) 함수입니다 ). 무언가가 명확하지 않으면 더 자세히 설명 할 수 있습니다. 시간이 있으면 사진을 추가 할 수있을 것입니다.mxnO((n+m)α(n))α

이제 나는 "벽"에 도달했습니다. 선형 알고리즘을 생각해 낼 수는 없지만 하나는 있어야합니다. 그래서 두 가지 질문이 있습니다.

  • 수평 세그먼트 가시성 문제를 해결 하는 선형 시간 알고리즘 (즉, )이 있습니까?O(n+m)
  • 그렇지 않은 경우 가시성 문제가 이라는 증거는 무엇 입니까?ω(n+m)

m 세그먼트 를 얼마나 빨리 정렬 합니까?
babou

@babou, 질문은 질문에서 알 수 있듯이 선형 시간 ( "계산 정렬의 변형을 사용하는 선형 시간")으로 실행되는 계산 정렬을 지정합니다.
DW

왼쪽에서 오른쪽으로 쓸어 내려고 했습니까? 당신이에 정렬되어 필요로 및 모두 와 오른쪽으로 걸어 단계를 반복합니다. 총 입니다. x1x2O(m)O(m)O(m)
invalid_id

@invalid_id 예, 시도했습니다. 이 세그먼트의 시작을 충족 그러나이 경우, 스윕 라인 (즉 세그먼트의 개수와 동일한 개수의 추가, 적절한 반응해야 세그먼트의 끝을 충족 MULTISET에 -coordinate ()의 선두로부터 제거 -coordinate)하고 가장 높은 활성 세그먼트를 출력합니다 (멀티 세트에서 출력 최대 값). 나는 우리가 일정한 시간에 이것을 할 수있게하는 데이터 구조에 대해 들어 본 적이 없다. yy
mnbvmar

@mnbvmar 어쩌면 바보 같은 제안하지만, 크기의 배열에 대해 어떻게 은 당신이 청소하고 모든 세포의 중지 . evry cell의 경우 max 를 알고 있으며 행렬에 입력 할 수 있으며, 변수를 사용하여 전체 최대 값을 추적 할 수도 있습니다. nO(n)y
invalid_id

답변:


1
  1. 먼저 두 개의 개별 배열 및 에서 선의 및 좌표를 모두 정렬합니다 . x 2 A B O ( m )x1x2ABO(m)
  2. 또한 활성 세그먼트를 추적하기 위해 보조 비트 배열 크기 을 유지합니다.n
  3. 왼쪽에서 오른쪽으로 스윕을 시작하십시오.
  4. 위한(i=0,i<n,i++)
  5. {
  6. .. 하는 경우 값이y c O ( 1 )x1=iyc O(1)
  7. .. {
  8. .... 찾기 ( )max
  9. .... store ( )O ( 1 )maxO(1)
  10. ..}
  11. .. 하는 경우 값이y c O ( 1 )x2=iyc O(1)
  12. .. {
  13. .... 찾기 ( )max
  14. .... store ( )O ( 1 )maxO(1)
  15. ..}
  16. }

find ( )는 비트 의 비트 배열을 사용하여 구현할 수 있습니다 . 이제 요소를 제거하거나 추가 할 때마다 비트를 각각 true 또는 false로 설정하여이 정수를 업데이트 할 수 있습니다. 이제 사용 된 프로그래밍 언어에 따라 두 가지 옵션이 있으며 이 상대적으로 작다고 가정합니다. 즉 , 최소 64 비트 또는 고정 된 양의 정수인 보다 작습니다 .n L n l o n g l o n g i n tmaxnLnlonglongint

  • 일정한 시간에 최하위 비트를 얻는 것은 일부 하드웨어와 gcc에 의해 지원됩니다.
  • 을 정수 로 변환 하면 최대 값을 얻을 수 있습니다 (직접적인 것이 아니라 파생 할 수 있음).( 1 )LO(1)

나는 이것이 대한 최대 값을 가정하기 때문에 꽤 해킹이라는 것을 알고 있으므로 은 상수로 볼 수 있습니다 ...nnn


보시다시피 64 비트 x86 프로세서가 있다고 가정하면 만 처리 할 수 ​​있습니다 . 이 수백만의 순서 이면 어떻게 됩니까? Nn64n
mnbvmar

그런 다음 더 많은 정수가 필요합니다. 두 개의 정수를 사용하면 최대 128까지 처리 할 수 ​​있습니다 . 따라서 최대 찾기 단계는 필요한 정수 수에 숨겨져 있으며 이 작을 경우 여전히 최적화 할 수 있습니다 . 귀하의 질문에 이 상대적으로 작기 때문에 수백만의 순서가 아닌 것으로 추측했습니다. 그런데 long long int는 32 비트 프로세서에서도 정의상 항상 64 비트 이상입니다. O ( m ) m의 NnO(m)mn
invalid_id

물론 C ++ 표준은 long long int최소한 64 비트 정수 유형으로 정의 됩니다. 그러나 이 거대하고 단어 크기를 (보통 )로 표시하면 각각 시간 이 걸리지 않습니까? 그런 다음 총 끝납니다 . w w = 64 O ( Nnww=64findO(nw)O(mnw)
mnbvmar

그렇습니다. 불행히도 큰 값의 해당합니다. 그래서 나는 당신의 경우에 얼마나 큰 n 이 될 것인지 그리고 그것이 제한되는지 궁금합니다 . 실제로 수백만의 순서 로이 해킹은 더 이상 작동하지 않지만 낮은 c 값에 대해 c w n이면 빠르고 실질적으로 O ( n + m ) 입니다. 따라서 최상의 알고리즘 선택은 평소와 같이 입력에 따라 다릅니다. 예를 들어 , N 100 삽입 정렬 보통 빨리 다음 짝수의 주행 시간 정렬 병합되는 O ( N 2 ) 에 비해nncwncO(n+m)n100O(n2) . O(nlogn)
invalid_id

3
나는 당신이 선택한 형식에 혼란스러워합니다. 여기서 코드를 조판 할 수 있다는 것을 알고 있습니까?
Raphael

0

선형 알고리즘이 없지만 O (m log m) 인 것 같습니다.

첫 번째 좌표와 높이를 기준으로 세그먼트를 정렬하십시오. 이는 x1 <x2 일 때마다 (x1, l1)이 항상 (x2, l2) 앞에옵니다. 또한 y1> y2 일 때마다 높이 y1의 (x1, l1)이 높이 y2의 (x1, l2)보다 먼저 나타납니다.

첫 번째 좌표가 동일한 모든 하위 집합에 대해 다음을 수행합니다. 첫 번째 세그먼트를 (x1, L)로 둡니다. 서브 세트의 다른 모든 세그먼트의 경우 : 세그먼트가 첫 번째보다 긴 경우 세그먼트를 (x1, xt)에서 (L, xt)로 변경하고 L- 서브셋에 올바른 순서로 추가하십시오. 그렇지 않으면 떨어 뜨립니다. 마지막으로 다음 부분 집합에 L보다 작은 첫 번째 좌표가 있으면 (x1, L)을 (x1, x2) 및 (x2, L)로 분할하십시오. (x2, L)을 다음 순서에 올바른 순서로 추가하십시오. 서브 세트의 첫 번째 세그먼트가 더 높고 (x1, L)의 범위를 포함하기 때문에이를 수행 할 수 있습니다. 이 새로운 세그먼트는 (L, x2)를 다루는 세그먼트 일 수 있지만, 첫 좌표 L을 갖는 부분 집합을 볼 때까지는 알 수 없습니다.

모든 하위 집합을 살펴본 후에 겹치지 않는 세그먼트 집합이 생깁니다. 주어진 X에 대한 Y 값이 무엇인지 확인하기 위해 나머지 세그먼트 만 통과하면됩니다.

여기서 복잡성은 무엇입니까? 정렬은 O (m log m)입니다. 서브 세트를 통한 루핑은 O (m)입니다. 조회도 O (m)입니다.

따라서이 알고리즘은 n과 무관 한 것 같습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.