배열 인터리빙을위한 적절한 알고리즘


62

2 요소 의 배열이 제공됩니다

에이1,에이2,,에이,1,2,

작업은 결과 배열이 다음과 같은 내부 알고리즘을 사용하여 배열을 인터리브하는 것입니다.

1,에이1,2,에이2,,,에이

내부 요구 사항이 없으면 새 배열을 쉽게 만들고 시간 알고리즘을 제공하는 요소를 복사 할 수 있습니다 .영형()

위치 내 요구하는 분할 수와 알고리즘을 알고리즘 범프를 정복 .θ(로그)

따라서 질문은 다음과 같습니다.

거기 에 현재 위치도 시간 알고리즘은?영형()

(참고 : 균일 한 비용의 WORD RAM 모델을 가정 할 수 있으므로 그 자리에서는 공간 제한으로 변환됩니다).영형(1)


1
이것은 스택 오버 플로우에 있지만 품질 솔루션을 제공하지 않습니다. 최고 답변은 "이 문제는 사람들이 생각하는 것만 큼 사소한 것이 아닙니다. 숙제? LOL. arXiv 에는 해결책 이 있습니다. "그러나 arxiv 솔루션에는 다른 논문에서 몇 가지 이론 + 참조 증명이 필요합니다. 여기에 간결한 해결책이 있다면 좋을 것입니다.
Joe


스택 오버플로의 또 다른 스레드 : stackoverflow.com/questions/15996288/…
Nayuki

답변:


43

Joe가 링크 한 논문에서 알고리즘을 자세히 설명하는 답변은 다음과 같습니다. http://arxiv.org/abs/0805.1598

먼저 나누기와 정복을 사용 하는 Θ(로그) 알고리즘을 살펴 보겠습니다 .

1) 나누고 정복

우리는 주어진다

에이1,에이2,,1,2,

이제 나누기와 정복을 사용하기 위해 =Θ() 에 대해 배열을 얻으려고합니다.

[에이1,에이2,,에이,1,2,,],[에이+1,,에이,+1,]

그리고 재귀.

일부의 알

1,2,,에이+1,에이
의 순환 시프트이다

에이+1,에이,1,

에 의해 장소.

이것은 고전적이며 세 번의 반전과 영형() 시간 내에 제자리에서 수행 할 수 있습니다 .

따라서 나누기와 정복은 T ( n ) = 2 T ( n / 2 ) + Θ ( n ) 과 유사한 재귀 로 Θ(로그) 알고리즘을 제공합니다 .()=2(/2)+Θ()

2) 순열주기

이제 문제에 대한 또 다른 접근 방식은 순열을 일련의 분리 된 주기로 간주하는 것입니다.

순열은 다음과 같이 주어집니다 ( 1 에서 시작한다고 가정 ).

제이2제이모드2+1

일정한 여분의 공간을 사용하여 사이클이 무엇인지 정확히 알고 있다면 요소 에이 를 선택하여 순열을 실현하고 해당 요소가 어디로 갔는지 (위의 공식을 사용하여) 결정하고 대상 위치의 요소를 임시 공간에 넣고 요소 에이 를 해당 대상 위치에 놓고 사이클을 따라 계속합니다. 한 사이클을 마치면 다음 사이클의 요소로 이동하여 해당 사이클을 따릅니다.

이것은 우리에게 영형() 시간 알고리즘을 제공 할 것이지만, 우리가 어떻게 "정확한 사이클이 무엇인지 알았다"고 가정하고 영형(1) 공간 제한 내에서 이러한 부기를 유지하려고 시도하는 것이이 문제를 어렵게 만듭니다.

종이가 숫자 이론을 사용하는 곳입니다.

이 경우에는, 그 표시 할 수있는 경우에 2+1=케이 , 위치의 요소를 1 , ,2,,케이1 다른 사이클에있는 모든 사이클은 위치 요소를 포함 ,0 .

이 사실 사용하는 2 의 발생기이다 (/케이) .

따라서 2+1=케이 일 때, 사이클 접근 방식에 따라 각 사이클에 대해 영형() 시간 알고리즘이 제공됩니다. 우리는 정확히 시작 위치를 알고 있습니다 : 거듭 제곱 ( 1 포함 ) 영형(1) 공간).

3) 최종 알고리즘

이제 우리는 나누기와 정복 + 순열주기 두 가지를 결합합니다.

우리는 나눗셈을 정복하고 있지만, 선택 되도록 2+1 의 힘 =Θ() .

따라서 두 "반쪽"에서 되풀이되는 경우 하나만 반복하고 Θ() 추가 작업을 수행합니다.

이것은 우리에게 재발 제공 ()=()+Θ() (일부 0<<1 ), 따라서 우리에게 제공 영형() 시간, 영형(1) 공간 알고리즘!


4
저거 정말 예쁘다.
Raphael

1
아주 좋아요 순열 예제를 통해 이제는 대부분 이해합니다. 두 가지 질문 : 1. 실제로 값 m을 어떻게 찾습니까? 종이에 O (log n)가 필요하다고 주장하는 이유는 무엇입니까? 2. 비슷한 방법으로 어레이를 DE- 인터리빙 할 수 있습니까?
num3ric

2
@ num3ric : 1) 의 가장 높은 거듭 제곱 은 < n 입니다. 따라서 O ( log n )가 됩니다. 2). 예, 가능합니다. 어딘가에 스택 오버 플로우에 대한 답변을 추가했다고 생각합니다. 이 경우 사이클 리더는 2 a 3 b ( 2 m + 1 = 3의 거듭 제곱)에 대한 것으로 나타났습니다 . 3<nO(logn)2a3b2m+1
Aryabhata

@Aryabhata 왜 우리는 두 개의 "반쪽"대신 하나의 "반쪽"만 재귀합니까?
sinoTrinity 1

1
@Aryabhata이 알고리즘을 확장하여 둘 이상의 어레이를 인터리브 할 수 있습니까? 예를 들어 회전 1 , 2 , ... , N , B 1 , B 2 , ... , B에 N , C 1 , C 2 , ... , C , NC (1) , (B) 1 , 1 , C 2 , B 2 , 2 , ... ,a1,a2,,an,b1,b2,,bn,c1,c2,,cn 또는 이와 유사한 것. c1,b1,a1,c2,b2,a2,,cn,bn,an
Doub

18

숫자 이론이나 순환 이론에 의존하지 않는 알고리즘을 찾았습니다. 해결해야 할 세부 정보가 몇 개 있지만 (내일 가능성이 있음), 문제가 해결 될 것이라고 확신합니다. 나는 문제를 숨기려고하지 않기 때문에 잠을 자야한다고 생각하는 핸드 웨이브 :)

하자 A첫 번째 배열 될 B두 번째, |A| = |B| = N그리고 가정 N=2^k일부 k단순화를 위해. 까지를 포함한 인덱스 A[i..j]의 하위 배열이되도록 합시다 . 배열은 0부터 시작합니다. 하자 의 '1'이고 가장 오른쪽의 비트 (0 계)의 위치 복귀 오른쪽을 계산. 알고리즘은 다음과 같이 작동합니다.AijRightmostBitPos(i)i

GetIndex(i) {
    int rightPos = RightmostBitPos(i) + 1;
    return i >> rightPos;
}

Interleave(A, B, N) {
    if (n == 1) {
        swap(a[0], b[0]);
    }
    else {
        for (i = 0; i < N; i++)
            swap(A[i], B[GetIndex(i+1)]);

        for (i = 1; i <= N/2; i*=2)
            Interleave(B[0..i/2-1], B[i/2..i-1], i/2);

        Interleave(B[0..N/2], B[N/2+1..N], n/2);
    }
}

16 개의 숫자 배열을 가져 와서 스왑을 사용하여 인터리빙을 시작하고 어떻게되는지 살펴 봅시다

1 2 3 4 5 6 7 8    | 9 10 11 12 13 14 15 16
9 2 3 4 5 6 7 8    | 1 10 11 12 13 14 15 16
9 1 3 4 5 6 7 8    | 2 10 11 12 13 14 15 16
9 1 10 4 5 6 7 8   | 2 3 11 12 13 14 15 16
9 1 10 2 5 6 7 8   | 4 3 11 12 13 14 15 16
9 1 10 2 11 6 7 8  | 4 3 5 12 13 14 15 16
9 1 10 2 11 3 7 8  | 4 6 5 12 13 14 15 16
9 1 10 2 11 3 12 8 | 4 6 5 7 13 14 15 16
9 1 10 2 11 3 12 4 | 8 6 5 7 13 14 15 16

두 번째 배열의 첫 번째 부분이 특히 중요합니다.

|
| 1
| 2
| 2 3
| 4 3
| 4 3 5
| 4 6 5
| 4 6 5 7
| 8 6 5 7

패턴은 명확해야합니다. 끝에 숫자를 교대로 추가하고 가장 낮은 숫자를 높은 숫자로 바꿉니다. 우리는 항상 우리가 이미 가지고있는 가장 높은 숫자보다 하나 높은 숫자를 추가합니다. 어떤 시점에서 어떤 숫자가 가장 낮은 지 정확히 파악할 수 있다면 쉽게 할 수 있습니다.

이제 더 큰 예제를 통해 패턴을 볼 수 있는지 확인합니다. 위 예제를 구성하기 위해 배열의 크기를 고칠 필요는 없습니다. 어느 시점에서 우리는이 구성을 얻습니다 (두 번째 줄은 모든 숫자에서 16을 뺍니다).

16 24 20 28 18 22 26 30 17 19 21 23 25 27 29 31
0   8  4 12  2  6 10 14  1  3  5  7  9 11 13 15

"1 3 5 7 9 11 13 15"는 모두 2 개, "2 6 10 14"는 4 개, "4 12"는 8 개입니다. 따라서 우리는 다음으로 가장 작은 숫자가 무엇인지 알려주는 알고리즘을 고안 할 수 있습니다. 메커니즘은 이진 숫자의 작동 방식과 거의 같습니다. 배열의 마지막 절반에 대한 비트, 2 분기에 대한 비트 등이 있습니다.

lognlognO(1)

O(n)O(n)

영형()영형(로그)영형(1)

이제 문제는 : 정렬해야 할 부분에 패턴이 있습니까? 32 개의 숫자를 입력하면 "16 12 10 14 9 11 13 15"가 수정됩니다. 여기에 정확히 같은 패턴이 있습니다! "9 11 13 15", "10 14"및 "12"는 앞에서 본 것과 같은 방식으로 함께 그룹화됩니다.

이제이 하위 파트를 재귀 적으로 인터리브하는 것이 요령입니다. "16"과 "12"에서 "12 16"까지 인터리브합니다. "12 16"과 "10 14"에서 "10 12 14 16"까지 인터리브합니다. "10 12 14 16"과 "9 11 13 15"를 "9 10 11 12 13 14 15 16"으로 인터리브합니다. 첫 번째 부분이 정렬됩니다.

영형()영형()

예를 들면 :

Interleave the first half:
1 2 3 4 5 6 7 8    | 9 10 11 12 13 14 15 16
9 2 3 4 5 6 7 8    | 1 10 11 12 13 14 15 16
9 1 3 4 5 6 7 8    | 2 10 11 12 13 14 15 16
9 1 10 4 5 6 7 8   | 2 3 11 12 13 14 15 16
9 1 10 2 5 6 7 8   | 4 3 11 12 13 14 15 16
9 1 10 2 11 6 7 8  | 4 3 5 12 13 14 15 16
9 1 10 2 11 3 7 8  | 4 6 5 12 13 14 15 16
9 1 10 2 11 3 12 8 | 4 6 5 7 13 14 15 16
9 1 10 2 11 3 12 4 | 8 6 5 7 13 14 15 16
Sort out the first part of the second array (recursion not explicit):
8 6 5 7 13 14 15 16
6 8 5 7 13 14 15 16
5 8 6 7 13 14 15 16
5 6 8 7 13 14 15 16
5 6 7 8 13 14 15 16
Interleave again:
5 6 7 8   | 13 14 15 16
13 6 7 8  | 5 14 15 16
13 5 7 8  | 6 14 15 16
13 5 14 8 | 6 7 15 16
13 5 14 6 | 8 7 15 16
Sort out the first part of the second array:
8 7 15 16
7 8 15 16
Interleave again:
7 8 | 15 16
15 8 | 7 16
15 7 | 8 16
Interleave again:
8 16
16 8
Merge all the above:
9 1 10 2 11 3 12 4 | 13 5 14 6 | 15 7 | 16 8

흥미 롭군 공식적인 증거를 기꺼이 작성하려고 하시겠습니까? 비트를 처리하는 다른 알고리즘 (Joe found 논문에서 참조)이 있다는 것을 알고 있습니다. 아마 당신은 그것을 재발견했습니다!
Aryabhata

1

다음은 여분의 스토리지없이 어레이의 절반을 인터리빙하기위한 비 재귀적인 선형 시간 알고리즘입니다.

일반적인 아이디어는 간단합니다. 배열의 전반부를 왼쪽에서 오른쪽으로 걸어 올바른 값을 제자리에 바꿉니다. 진행함에 따라 아직 사용되지 않은 왼쪽 값은 올바른 값으로 비워진 공간으로 교체됩니다. 유일한 트릭은 다시 꺼내는 방법을 알아내는 것입니다.

우리는 N 크기의 배열을 거의 같은 절반으로 나누는 것으로 시작합니다.
[ left_items | right_items ]
처리할수록
[ placed_items | remaining_left_items| swapped_left_items | remaining_right_items]

스왑 공간은 다음과 같은 패턴으로 커집니다. A) 인접한 오른쪽 항목을 제거하고 왼쪽에서 새 항목을 교체하여 공간을 늘립니다. B) 가장 오래된 항목을 왼쪽에서 새 항목으로 바꿉니다. 왼쪽 항목의 번호가 1..N이면이 패턴은

step swapspace index changed
1    A: 1         0
2    B: 2         0
3    A: 2 3       1
4    B: 4 3       0     
5    A: 4 3 5     2
6    B: 4 6 5     1
7    A: 4 6 5 7   3
...

인덱스가 변경된 순서는 정확히 OEIS A025480 이며 간단한 프로세스로 계산할 수 있습니다. 이를 통해 지금까지 추가 된 항목 수만 지정하면 스왑 위치를 찾을 수 있으며 이는 현재 배치중인 항목의 인덱스이기도합니다.

이것이 시퀀스의 전반부를 선형 시간으로 채우는 데 필요한 모든 정보입니다.

중간 점에 도달하면 배열에는 세 부분이 있습니다. [ placed_items | swapped_left_items | remaining_right_items] 교체 된 항목을 스크램블 할 수없는 경우 문제를 절반 크기로 줄이고 반복 할 수 있습니다.

스왑 공간을 해독하려면 다음 속성을 사용합니다. Nappend 및 swap_oldest 작업 을 번갈아 생성 한 시퀀스 N/2에는에 의해 연령이 지정된 항목 이 포함 됩니다 A025480(N/2)..A025480(N-1). (정수 나누기, 값이 작을수록 오래됨).

예를 들어, 왼쪽 절반이 원래 1..19 값을 보유한 경우 스왑 공간은을 포함합니다 [16, 12, 10, 14, 18, 11, 13, 15, 17, 19]. A025480 (9..18)은이며 [2, 5, 1, 6, 3, 7, 0, 8, 4, 9], 가장 오래된 것부터 가장 오래된 것까지의 항목 색인 목록입니다.

따라서 스왑 공간을 확장하고와 교환 S[i]하여 스왑 공간을 해독 할 수 있습니다 S[ A(N/2 + i)]. 이것은 또한 선형 시간입니다.

나머지 합병증은 결국 올바른 값이 더 낮은 지수에 있어야하지만 이미 교체 된 위치에 도달한다는 것입니다. 새 위치를 쉽게 찾을 수 있습니다. 인덱스 계산을 다시 수행하여 항목이 교체 된 위치를 찾으십시오. 스왑되지 않은 위치를 찾을 때까지 몇 단계를 거쳐 체인을 따라야 할 수도 있습니다.

이 시점에서 우리는 어레이의 절반을 병합하고 다른 절반에서는 병합되지 않은 부품의 순서를 정확하게 N/2 + N/4스왑 하여 유지했습니다 . 우리 N + N/4 + N/8 + ....는 엄격하게보다 적은 총 스왑을 위해 나머지 배열을 계속 진행할 수 있습니다 3N/2.

A025480을 계산하는 방법 :
이것은 OEIS a(2n) = n, a(2n+1) = a(n).에서 대체 공식 으로 정의됩니다 a(n) = isEven(n)? n/2 : a((n-1)/2). 이는 비트 단위 연산을 사용하는 간단한 알고리즘으로 이어집니다.

index_t a025480(index_t n){
    while (n&1) n=n>>1;
    return n>>1;  
}

이것은 N. 대한 모든 가능한 값 위에 상각 O (1) 연산이다 (1/2 필요성 1 시프트 필요성 1/4 2 1/8 필요성 3, ...) . 작은 조회 테이블을 사용하여 최하위 0 비트의 위치를 ​​찾는 훨씬 빠른 방법이 있습니다.

그것을 감안할 때 다음은 C로 구현 된 것입니다.

static inline index_t larger_half(index_t sz) {return sz - (sz / 2); }
static inline bool is_even(index_t i) { return ((i & 1) ^ 1); }

index_t unshuffle_item(index_t j, index_t sz)
{
  index_t i = j;
  do {
    i = a025480(sz / 2 + i);
  }
  while (i < j);
  return i;
}

void interleave(value_t a[], index_t n_items)
{
  index_t i = 0;
  index_t midpt = larger_half(n_items);
  while (i < n_items - 1) {

    //for out-shuffle, the left item is at an even index
    if (is_even(i)) { i++; }
    index_t base = i;

    //emplace left half.
    for (; i < midpt; i++) {
      index_t j = a025480(i - base);
      SWAP(a + i, a + midpt + j);
    }

    //unscramble swapped items
    index_t swap_ct  = larger_half(i - base);
    for (index_t j = 0; j + 1 < swap_ct ; j++) {
      index_t k = unshuffle_item(j, i - base);
      if (j != k) {
        SWAP(a + midpt + j, a + midpt + k);
      }
    }
    midpt += swap_ct;
  }
}

3 개의 데이터 위치 중 2 개가 순차적으로 액세스되고 처리되는 데이터의 양이 엄격히 감소하기 때문에 이는 캐시 친화적 인 알고리즘이어야합니다. 루프 시작시 테스트를 무효화하여이 방법을 셔플에서 셔플 로 전환 할 수 있습니다 is_even.

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