슬라이딩 윈도우 중앙값 계산을위한 사소한 알고리즘


25

실행중인 중앙값을 계산해야합니다.

  • 입력 : , k , 벡터 ( x 1 , x 2 , , x n ) .nk(x1,x2,,xn)

  • 출력 : 벡터 , 여기서 y i( x i , x i + 1 , , x i + k - 1 ) 의 중앙값입니다 .(y1,y2,,ynk+1)yi(xi,xi+1,,xi+k1)

(근사치로 부정 행위 없음; 정확한 솔루션을 원합니다. 요소 는 큰 정수입니다.)xi

크기의 검색 트리를 유지하는 사소한 알고리즘이 있습니다 . 총 실행 시간은 O ( n log k ) 입니다. 여기서 "검색 트리"는 로그 시간에 삽입, 삭제 및 중앙 쿼리를 지원하는 효율적인 데이터 구조를 나타냅니다.kO(nlogk)

그러나 이것은 나에게 약간 바보처럼 보입니다. 우리는 중앙값뿐만 아니라 크기가 k 인 모든 창 내에서 모든 주문 통계를 효과적으로 학습 합니다 . 또한, 특히 k 가 큰 경우 (실제로 큰 검색 트리가 느리거나 메모리 소비 오버 헤드가 크지 않으며, 캐시 효율성이 좋지 않은 경우 등) 이는 실제로 매력적이지 않습니다 .kk

실질적으로 더 나은 것을 할 수 있습니까?

하한이 있습니까 (예 : 사소한 알고리즘이 비교 모델에 대해 무조건 최적입니까?)?


편집 : David Eppstein은 비교 모델에 대한 좋은 하한을 제공했습니다! 그럼에도 불구하고 사소한 알고리즘보다 약간 더 영리한 것을 할 수 있는지 궁금합니다.

예를 들어, 다음 선을 따라 무언가를 수행 할 수 있습니다. 입력 벡터를 크기가 부분으로 나눕니다 . 각 부분을 정렬합니다 (각 요소의 원래 위치를 추적 함). 그런 다음 부분적으로 정렬 된 벡터를 사용하여 보조 데이터 구조없이 효율적으로 실행중인 중앙값을 찾으십시오. 물론 이것은 여전히 O ( n log k ) 이지만 실제로 정렬 정렬은 검색 트리를 유지 관리하는 것보다 훨씬 빠른 경향이 있습니다.kO(nlogk)


편집 2 : Saeed는 정렬이 검색 트리 작업보다 빠른 이유를 알고 싶었습니다. 다음은 매우 빠른 벤치 마크입니다. , n = 10 8 :k=107n=108

  • ≈ 8s : 각각 k 개의 요소로 벡터 정렬n/kk
  • s 10s : 요소 로 벡터 정렬n
  • s 80s : k 크기의 해시 테이블에서 삽입 및 삭제nk
  • 390 390s : k 크기의 균형 검색 트리에서 삽입 및 삭제nk

해시 테이블은 비교를 위해 존재합니다. 이 응용 프로그램에서 직접 사용되지 않습니다.

요약하면 정렬 성능과 균형 검색 트리 작업의 성능에 거의 50 배의 차이가 있습니다. 를 늘리면 상황이 훨씬 나빠집니다 .k

(기술 세부 사항 : 데이터 = 임의의 32 비트 정수. 컴퓨터 = 일반적인 최신 랩톱) 테스트 코드는 표준 라이브러리 루틴 (std :: sort) 및 데이터 구조 (std :: multiset, std ::를 사용하여 C ++로 작성되었습니다. unsorted_multiset) 두 개의 다른 C ++ 컴파일러 (GCC와 Clang)와 두 개의 다른 표준 라이브러리 구현 (libstdc ++ 및 libc ++)을 사용했습니다.


1
나는 당신이 를 향상시킬 수 있다고 생각하지 않습니다 . 당신이 윈도우를 보면 그 이유는,이다 의 X t , . . . , x t + k 1 , 숫자 x t + k를 배제 할 수 없습니다nlogkxt,...,xt+k1은 미래 윈도우의 중앙값입니다. 이것은 언제든지 최소한k를 유지해야한다는 것을 의미합니다xt+k2,...,xt+k1데이터 구조에 2 개의 정수가 있으며 로그 시간 미만으로 업데이트되지 않는 것 같습니다. k2
RB

나에게 당신의 사소한 알고리즘이 될 것으로 보인다 하지 O ( N 로그 케이 ) , 내가 오해 뭔가입니까? 그리고 나는 이것 때문에 big k에 문제가 있다고 생각합니다 . 그렇지 않으면 로그 팩터는 실제 응용 프로그램에는 아무것도 없으며이 알고리즘에는 숨겨진 상수가 없습니다. O((nk)klogk)O(nlogk)k
Saeed

@Saeed : 사소한 알고리즘에서는 요소를 하나씩 처리합니다. 단계에서 x i 를 검색 트리에 추가 하고 ( i > k 인 경우 ) 검색 트리에서 x i - k 도 제거 합니다. 이것은 n 단계이며, 각각 O ( log k ) 시간 이 걸립니다 . ixii>kxiknO(logk)
Jukka Suomela

평범한 검색 트리가 아닌 균형 잡힌 검색 트리가 있다는 의미입니까?
Saeed

1
@Saeed : 내 벤치 마크에서 중간 값을 찾으려고하지조차 않았습니다. k 크기의 검색 트리에서 n 삽입과 n 개의 삭제를 방금 수행 했으며 이러한 작업은 O ( log k ) 시간이 소요됩니다. 검색 트리 작업이 정렬과 비교할 때 실제로 매우 느리다는 것을 받아 들여야합니다. 균형 잡힌 검색 트리에 요소를 추가하여 작동하는 정렬 알고리즘을 작성하려고하면이를 쉽게 알 수 있습니다. 확실히 O ( n log n ) 시간에 작동하지만 실제로 엄청나게 느려질뿐만 아니라 많은 낭비를합니다 기억의. nnkO(logk)O(nlogn)
Jukka Suomela

답변:


32

정렬에서 하한이 있습니다. 입력 설정을 감안할 때 길이의 N 정렬하려면이 중간 문제로 구성된 실행에 입력 만들 N - 1 개 의 최소보다 작은 수의 사본 S를 한 후, S 자체, 다음 N - 1 개 보다 작은 숫자의 사본이 큰 S 의 최대 값으로 설정하고 k = 2 n - 1로 설정하십시오 . 이 입력의 실행 중앙값은 정렬 된 S 순서와 같습니다 .Snn1SSn1Sk=2n1S

따라서 계산 비교 모델에서는 시간이 필요합니다. 입력이 정수이고 정수 정렬 알고리즘을 사용하면 더 잘 할 수 있습니다.Ω(nlogn)


6
이 대답은 대화가 잘 유지되는지 궁금합니다. 효율적인 정렬 알고리즘이 주어지면 효율적인 실행 중위 알고리즘을 얻습니까? (예를 들어, 효율적인 정수 정렬 알고리즘에서 정수에 대한 효율적인 실행 중위 알고리즘을 의미합니까? 또는 IO 효율적인 정렬 알고리즘이 IO 효율적인 실행 중위 알고리즘을 제공합니까?)
Jukka Suomela

1
다시 한 번, 귀하의 답변에 감사드립니다. 정말 나에게 올바른 길을 가고 정렬 기반의 중간 필터 알고리즘에 대한 영감을주었습니다! 결국 나는 1991 년부터 여기에 제공 한 것과 기본적으로 같은 주장을 제시 한 논문을 찾을 수 있었고 Pat Morin은 2005 년부터 다른 관련 논문에 대한 포인터를 제공했습니다. 심판을 참조하십시오. [6]와 [9]는 여기에 있습니다 .
Jukka Suomela

9

편집 : 이 알고리즘은 이제 여기에 제시됩니다 : http://arxiv.org/abs/1406.1717


예,이 문제를 해결하려면 다음 작업을 수행하면 충분합니다.

  • 각각 k 개의 요소를 갖는 벡터를 정렬 합니다.n/kk
  • 선형 시간 후 처리를 수행하십시오.

대략적으로 아이디어는 다음과 같습니다.

  • k 요소를 가진 두 개의 인접한 입력 블록 b를 고려하십시오 . 요소가 될 수 있도록 1 , 2 , . . . , KB 1 , B 2 , . . . , B 형 K 입력 벡터의 출현 순서 X .abka1,a2,...,akb1,b2,...,bkx
  • 이 블록을 정렬하고 블록 내 각 요소의 순위를 학습하십시오.
  • 포인터 체인을 따라 증가하는 순서로 요소를 순회 할 수 있도록 선행 작업 / 후행 포인터로 벡터 b 를 보강합니다 . 이 방법으로 이중 연결리스트 a 'b '를 구성했습니다 .abab
  • 하나씩, 모양 b k , b k - 1 , 의 역순으로 링크 된 목록 에서 모든 요소를 ​​삭제하십시오 . . . , b 1 . 요소를 삭제할 때마다 삭제 당시의 후속 요소 와 선행 요소를 기억하십시오 .bbk,bk1,...,b1
  • 이제 유지 "중간 포인터" Q 그 목록을 가리킨 'B ' 는 각각. 초기화 페이지 의 중간에 ' 및 초기화 Q 빈리스트의 꼬리 B ' .pqabpaqb
  • 대해 :i

    • 삭제 내가 목록에서 ' (이는 O ( 1 ) 시간, 그냥 링크 목록에서 삭제). 비교 난을 요소에 의해 지적으로 P 우리가 이전 또는 이후에 삭제 있는지 확인하기 위해 페이지 .aiaO(1)aipp
    • 를 원래의 위치에있는 목록 b ' 에 다시 넣 습니다 (이것은 O ( 1 )입니다 , 우리는 b i 의 전임자와 후임자를 암기했습니다 ). 비교 b를 내가 가리키는 요소와 Q 우리가 이전 또는 이후에 요소를 추가 한 경우보고 Q .bibO(1)bibiqq
    • 업데이트 포인터 q는 수 있도록 결합 된 목록의 중간 'B는 ' 에 중 하나입니다 페이지 또는에서 q를 . (이것은 O ( 1 ) 시간입니다. 링크 된 목록을 따라 하나 또는 두 단계로 모든 것을 고치십시오. 우리는 각 목록에서 pq 앞뒤에 몇 개의 항목이 있는지 추적 하고 p는Q의 가능한 한 중간에 근접하다 요소 점).pqabpqO(1)pqpq

링크 된 목록은 요소로 구성된 인덱스 배열이므로 가볍습니다 (메모리 액세스의 위치가 좋지 않은 경우 제외).k


다음은 샘플 구현 및 벤치 마크입니다.

n2106

  • O(nlogk)
  • O(nlogk)
  • O(nlogk)
  • O(nk)
  • k/2
  • Y 축 = 실행 시간 (초)
  • 다양한 분포의 데이터 = 32 비트 정수 및 임의 64 비트 정수

상영 시간


3

mO(nlogm+mlogn)

O(logm)O(logn)O(logn) 중앙값 당 한 번만 청구됩니다.

O(nlogm+mlogk)


요소를 삭제하지 않으면 개수가 새 창에 반영되지 않기 때문에 작성된대로 작동하지 않습니다. 그것이 고칠 수 있는지 확실하지 않지만 방법이있을 경우 답변을 남겨 두겠습니다.
Geoffrey Irving

O(nlogm)

참고 : 질문이 명확하지 않고, 기초 데이터 구조가 정의되지 않았으며, 매우 모호한 것을 알고 있습니다. 그것이 무엇인지 모르는 것을 어떻게 개선하고 싶습니까? 접근 방식을 어떻게 비교하고 싶습니까?
Saeed

1
불완전한 작업에 대해 사과드립니다. 이 답변을 수정하는 데 필요한 구체적인 질문을 여기에 요청했습니다 : cstheory.stackexchange.com/questions/21778/… . 적절하다고 생각되면 보조 질문이 해결 될 때 까지이 답변을 제거 할 수 있습니다.
Geoffrey Irving 1
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.