정렬 된 정수 목록에서 균형 BST 생성


15

고유 한 정렬 된 정수 목록이 제공되면 재귀를 사용하지 않고 배열로 표시되는 균형 이진 검색 트리를 만듭니다.

예를 들면 다음과 같습니다.

func( [1,2,3,5,8,13,21] ) => [5,2,13,1,3,8,21]

시작하기 전에 힌트 : 우리는 실제로 입력 정수 (또는 그 문제에 대한 비슷한 객체)에 대해 생각할 필요가 없도록이 문제를 상당히 단순화 할 수 있습니다.

입력 목록이 이미 정렬되어 있다는 것을 알고 있다면 내용은 관련이 없습니다. 우리는 단순히 원래 배열에 대한 인덱스로 생각할 수 있습니다.

입력 배열의 내부 표현은 다음과 같습니다.

func( [0,1,2,3,4,5,6] ) => [3,1,5,0,2,4,6]

이것은 비교 가능한 객체를 다루는 것을 쓰는 것이 아니라 [0, n) 범위에서 결과 배열로 매핑하는 함수 만 작성하면된다는 것을 의미합니다. 새로운 순서를 가지면 간단히 입력 값에 매핑을 적용하여 반환 배열을 만들 수 있습니다.

유효한 솔루션은 다음과 같아야합니다.

  • 요소가없는 배열을 받아들이고 빈 배열을 반환합니다.
  • 길이 n 의 정수 배열을 허용하고 정수 배열을 리턴
    • n 과 다음 최고 전력 2-1 사이의 길이 입니다.
    • 루트 노드가 0 위치에 있고 높이가 log (n) 과 동일한 BST를 나타내는 배열입니다. 여기서 0은 누락 된 노드를 나타냅니다 (또는 null언어가 허용하는 경우 유사한 값). 빈 노드, 현재는 단지 나무의 끝 부분에 존재해야하는 경우 (예 [2,1,0])

입력 정수 배열은 다음을 보장합니다.

  • 값은 0보다 큰 32 비트 부호있는 정수입니다.
  • 값은 고유합니다.
  • 값은 위치 0부터 오름차순입니다.
  • 값이 희박 할 수 있습니다 (즉, 서로 인접하지 않음).

ascii 문자 수에 의해 가장 간결한 코드가 승리하지만 특정 언어에 대한 우아한 솔루션을 보는 데 관심이 있습니다.

테스트 사례

함유 단순 어레이에 대한 출력 1n여러 대 n. 전술 한 바와 같이, 후행 0은 선택적이다.

[]
[1]
[2,1,0]
[2,1,3]
[3,2,4,1,0,0,0]
[4,2,5,1,3,0,0]
[4,2,6,1,3,5,0]
[4,2,6,1,3,5,7]
[5,3,7,2,4,6,8,1,0,0,0,0,0,0,0]
[6,4,8,2,5,7,9,1,3,0,0,0,0,0,0]
[7,4,9,2,6,8,10,1,3,5,0,0,0,0,0]
[8,4,10,2,6,9,11,1,3,5,7,0,0,0,0]
[8,4,11,2,6,10,12,1,3,5,7,9,0,0,0]
[8,4,12,2,6,10,13,1,3,5,7,9,11,0,0]
[8,4,12,2,6,10,14,1,3,5,7,9,11,13,0]
[8,4,12,2,6,10,14,1,3,5,7,9,11,13,15]

프로그래밍 퍼즐이든 코드 골프이든이 사이트의 모든 질문에는 객관적인 주요 우승 기준이 있어야합니다.
Howard

@Howard 감사합니다. 우승자를위한 확실한 기준으로 업데이트되었습니다.
Jake Wharton

1
현재로서는 가장 쉬운 것보다는 어려운 경우를 다루는 테스트 사례를 갖는 것이 매우 유용합니다.
피터 테일러

재귀를 배제 할 이유가 있습니까? 나는 재귀 적 인 해결책을보고 있지 않지만 인공적이고 불필요한 것으로 보입니다.
dmckee --- 전 운영자 고양이

1
누군가 목록이 BST를 어떻게 나타내는 지 설명 할 수 있습니까?
justinpc

답변:


4

루비 , 143

s=ARGV.size;r,q=[],[[0,s]];s.times{b,e=q.shift;k=Math::log2(e-b).to_i-1;m=(e-b+2)>(3<<k)?b+(2<<k)-1:e-(1<<k);r<<ARGV[m];q<<[b,m]<<[m+1,e]};p r

기본적으로 트리에서 BFS를 수행하는 다음 코드의 압축 된 버전입니다.

def l(n)
    k = Math::log2(n).to_i-1
    if n+2 > (3<<k) then
        (2<<k)-1
    else
        n-(1<<k) 
    end
end

def bfs(tab)
  result = []
  queue = [[0,tab.size]]
  until queue.empty? do
    b,e = queue.shift
    m = b+l(e-b)
    result << tab[m]
    queue << [b,m] if b < m
    queue << [m+1,e] if m+1 < e
  end
  result
end

p bfs(ARGV)

또한 DFS가 아닌 BFS이기 때문에 비 재귀 솔루션의 요구 사항은 중요하지 않으며 일부 언어는 단점이 있습니다.

편집 : 그의 의견에 대해 @PeterTaylor에게 감사드립니다!


@PeterTaylor 의도는 4의 왼쪽에 3을 넣는 것이지만 공백이 없으므로 잘못되었습니다. 지적 해 주셔서 감사합니다!
dtldarek

@PeterTaylor 점심 시간에 고쳐 졌으니 이제 작동합니다.
dtldarek

4

자바 , 252

좋아, 여기 내 시도가있다. 나는 비트 연산으로 놀고 있었고 원래 배열의 인덱스에서 BST의 요소 인덱스를 계산하는이 직접적인 방법을 생각해 냈습니다.

압축 버전

public int[]b(int[]a){int i,n=1,t;long x,I,s=a.length,p=s;int[]r=new int[(int)s];while((p>>=1)>0)n++;p=2*s-(1l<<n)+1;for(i=0;i<s;i++){x=(i<p)?(i+1):(p+2*(i-p)+1);t=1;while((x&1<<(t-1))==0)t++;I=(1<<(n-t));I|=((I-1)<<t&x)>>t;r[(int)I-1]=a[i];}return r;}

긴 버전은 다음과 같습니다.

public static int[] makeBst(int[] array) {
  long size = array.length;
  int[] bst = new int[array.length];

  int nbits = 0;
  for (int i=0; i<32; i++) 
    if ((size & 1<<i)!=0) nbits=i+1;

  long padding = 2*size - (1l<<nbits) + 1;

  for (int i=0; i<size; i++) {
    long index2n = (i<padding)?(i+1):(padding + 2*(i-padding) + 1);

    int tail=1;
    while ((index2n & 1<<(tail-1))==0) tail++;
    long bstIndex = (1<<(nbits-tail));
    bstIndex = bstIndex | ((bstIndex-1)<<tail & index2n)>>tail;

    bst[(int)(bstIndex-1)] = array[i];
  }
 return bst;
}

당신은 문자 수를 필요로하고, 이것은 현재 골프되지 않습니다.
dmckee --- 전 운영자 고양이

@dmckee 나는 압축 버전과 문자 수를 포함하도록 게시물을 편집했습니다
mikail sheikh

좋은 공연. 나는 그 공간 중 일부가 불필요하다고 확신합니다. c에서는 int[] b(int[] a)다음과 같이 잘 표현됩니다 int[]b(int[]a).
dmckee --- 전 운영자 고양이

당신은이 a.length배열의 할당에. 로 변경하십시오 s. for (여러 번 간격을 제거하십시오 . 루프 각각은 만들어 int i=0int t=0. 함께 만들기 n( int n=0,i,t;그때) 및 i=0루프와의 t=1내부. 내부 long xlong I함께 선언하고 s루프 ( long s=a.length,I,x;x=../ I=..) 에서 초기화하십시오 . 이진 AND 주위에 공백이 없어야합니다 &.
Jake Wharton

또한 I=I|..쓸 수 있습니다I|=..
Jake Wharton

3
def fn(input):
    import math
    n = len(input)
    if n == 0:
        return []
    h = int(math.floor(math.log(n, 2)))
    out = []
    last = (2**h) - 2**(h+1) + n

    def num_children(level, sibling, lr):
        if level == 0:
            return 0
        half = 2**(level-1)
        ll_base = sibling * 2**level + lr * (half)
        ll_children = max(0, min(last, ll_base + half - 1) - ll_base + 1)
        return 2**(level-1) - 1 + ll_children

    for level in range(h, -1, -1):
        for sibling in range(0, 2**(h-level)):
            if level == 0 and sibling > last:
                break
            if sibling == 0:
                last_sibling_val = num_children(level, sibling, 0)
            else:
                last_sibling_val += 2 + num_children(level, sibling - 1, 1) \
                    + num_children(level, sibling, 0)
            out.append(input[last_sibling_val])
    return out

2

이것이 트리의 끝에 빈 노드가 있어야한다는 요구 사항에 정확히 맞는지 확실하지 않으며 간결한 상을 수상하지는 않지만 확실하다고 생각하며 테스트 사례가 있습니다. :)

public class BstArray {
    public static final int[] EMPTY = new int[] { };
    public static final int[] L1 = new int[] { 1 };
    public static final int[] L2 = new int[] { 1, 2 };
    public static final int[] L3 = new int[] { 1, 2, 3 };
    public static final int[] L4 = new int[] { 1, 2, 3, 5 };
    public static final int[] L5 = new int[] { 1, 2, 3, 5, 8 };
    public static final int[] L6 = new int[] { 1, 2, 3, 5, 8, 13 };
    public static final int[] L7 = new int[] { 1, 2, 3, 5, 8, 13, 21 };
    public static final int[] L8 = new int[] { 1, 2, 3, 5, 8, 13, 21, 35 };
    public static final int[] L9 = new int[] { 1, 2, 3, 5, 8, 13, 21, 35, 56 };
    public static final int[] L10 = new int[] { 1, 2, 3, 5, 8, 13, 21, 35, 56, 91 };

    public static void main(String[] args) {
        for (int[] list : Arrays.asList(EMPTY, L1, L2, L3, L4, L5, L6, L7, L8, L9, L10)) {
            System.out.println(Arrays.toString(list) + " => " + Arrays.toString(bstListFromList(list)));
        }
    }

    private static int[] bstListFromList(int[] orig) {
        int[] bst = new int[nextHighestPowerOfTwo(orig.length + 1) - 1];

        if (orig.length == 0) {
            return bst;
        }

        LinkedList<int[]> queue = new LinkedList<int[]>();
        queue.push(orig);

        int counter = 0;
        while (!queue.isEmpty()) {
            int[] list = queue.pop();
            int len = list.length;

            if (len == 1) {
                bst[counter] = list[0];
            } else if (len == 2) {
                bst[counter] = list[1];
                queue.add(getSubArray(list, 0, 1));
                queue.add(new int[] { 0 });
            } else if (len == 3) {
                bst[counter] = list[1];
                queue.add(getSubArray(list, 0, 1));
                queue.add(getSubArray(list, 2, 1));
            } else {
                int divide = len / 2;
                bst[counter] = list[divide];
                queue.add(getSubArray(list, 0, divide));
                queue.add(getSubArray(list, divide + 1, len - (divide + 1)));
            }
            counter++;
        }

        return bst;
    }

    private static int nextHighestPowerOfTwo(int n) {
        n--;
        n |= n >> 1;
        n |= n >> 2;
        n |= n >> 4;
        n |= n >> 8;
        n |= n >> 16;
        n++;

        return n;
    }

    private static int[] getSubArray(int[] orig, int origStart, int length) {
        int[] list = new int[length];
        System.arraycopy(orig, origStart, list, 0, length);
        return list;
    }
}

2

골프 스크립트 ( 99 89)

~]:b[]:^;{b}{{:|.,.2base,(2\?:&[-)&2/]{}$0=&(2/+:o[=]^\+:^;|o<.!{;}*|o)>.!{;}*}%:b}while^p

기본적으로 파이썬 솔루션의 직선 포트는 거의 같은 방식으로 작동합니다.

@petertaylor의 입력으로 10 자씩 이미 개선 된 더 많은 "골프"로 상당히 향상 될 수 있습니다. :)


나는 여전히 GolfScript 답변을 아직 끝내지 못했지만 70 세 이하에서 가능해야한다고 생각합니다. 그래도 쉽게 개선 할 수 있습니다. !{;}{}if단지가 될 수 !{;}*있기 때문에 !보장 반환 0또는 1. 당신은 당신이 사용하는 그렇다면, 변수 알파벳이 아닌 토큰을 사용할 수 있습니다 ^대신 r, |대신 x, &대신 y당신이 모든 공백을 제거 할 수 있습니다.
피터 테일러

@PeterTaylor 덕분에 :) golfscript 아주 새로운 여전히 비 숫자 변수에 대해 알고하지 않았다
요아킴 이삭손

2

자바 192

입력의 색인을 출력의 색인에 맵핑합니다.

int[]b(int[]o){int s=o.length,p=0,u=s,i=0,y,r[]=new int[s],c[]=new int[s];while((u>>=1)>0)p++;for(int x:o){y=p;u=i;while(u%2>0){y--;u/=2;}r[(1<<y)-1+c[y]++]=x;i+=i>2*s-(1<<p+1)?2:1;}return r;}

긴 버전 :

static int[] bfs(int[] o) {
  int rowCount = 32 - Integer.numberOfLeadingZeros(o.length); // log2
  int slotCount = (1<<rowCount) - 1; // pow(2,rowCount) - 1

  // number of empty slots at the end
  int emptySlots = slotCount - o.length;
  // where we start to be affected by these empty slots
  int startSkippingAbove = slotCount - 2 * emptySlots; // = 2 * o.length - slotCount

  int[] result = new int[o.length];
  int[] rowCounters = new int[rowCount]; // for each row, how many slots in that row are taken
  int i = 0; // index of where we would be if this was a complete tree (no trailing empty slots)
  for (int x : o) {
    // the row (depth) a slot is in is determined by the number of trailing 1s
    int rowIndex = rowCount - Integer.numberOfTrailingZeros(~i) - 1;
    int colIndex = rowCounters[rowIndex]++; // count where we are
    int rowStartIndex = (1 << rowIndex) - 1; // where this row starts in the result array

    result[rowStartIndex + colIndex] = x;

    i++;
    // next one has to jump into a slot that came available by not having slotCount values
    if (i > startSkippingAbove) i++;
  }

  return result;
}

2

Wolfram Mathematica 11, 175 바이트

g[l_]:=(x[a_]:=Floor@Min[i-#/2,#]&@(i=Length[a]+1;2^Ceiling@Log2[i]/2);Join@@Table[Cases[l//.{{}->{},b__List:>(n[Take[b,#-1],b[[#]],Drop[b,#]]&@x[b])},_Integer,{m}],{m,x[l]}])

이 함수 g[l]는 입력 a List(예 :) 를 사용하여 원하는 형식의 l={1,2,3,4,...}a List를 반환합니다 . 다음과 같이 작동합니다.

  • x[a_]:=Floor@Min[i-#/2,#]&@(i=Length[a]+1;2^Ceiling@Log2[i]/2) 목록을 가져와 관련 BST의 루트를 찾습니다.
    • i=Length[a]+1 목록 길이에 대한 바로 가기
    • 2^Ceiling@Log2[i]/2 루트 값의 상한
    • Min[i-#/2,#]&@(...)#내부의 내용을 나타내는 두 가지 인수의 최소값(...)
  • l//.{...} 다음에 나오는 교체 규칙을 반복해서 적용하십시오. l
  • {}->{} 할 일 없음 (무한 루프를 피하기위한 가장 중요한 경우)
  • b__List:>(n[Take[b,#-1],b[[#]],Drop[b,#]]&@x[b])분할 List로를{{lesser}, root, {greater}}
  • Cases[...,_Integer,{m}] 모든 정수를 레벨 (깊이)로 가져갑니다 m
  • Table[...,{m,1,x[l]}]모든 들어 m최대 x[l](이상 실제로 필요합니다).

다음을 실행하여 테스트 할 수 있습니다.

Table[g[Range[a]], {a, 0, 15}]//MatrixForm

이 구현에는 후행 0이 포함되지 않습니다.


1

파이썬 ( 175 171)

상당히 압축되었지만 여전히 읽을 수 있습니다.

def f(a):
 b=[a]
 while b:
  c,t=((s,2**(len(bin(len(s)))-3))for s in b if s),[]
  for x,y in c:
   o=min(len(x)-y+1,y/2)+(y-1)/2
   yield x[o]
   t+=[x[:o],x[o+1:]]
  b=t

결과를 다시 산출하므로 결과를 반복하거나 목록으로 표시 할 수 있습니다.

>>> for i in range(1,17): print i-1,list(f(range(1,i)))
 0 []
 1 [1]
 2 [2, 1]
 3 [2, 1, 3]
 4 [3, 2, 4, 1]
 5 [4, 2, 5, 1, 3]
 6 [4, 2, 6, 1, 3, 5]
 7 [4, 2, 6, 1, 3, 5, 7]
 8 [5, 3, 7, 2, 4, 6, 8, 1]
 9 [6, 4, 8, 2, 5, 7, 9, 1, 3]
10 [7, 4, 9, 2, 6, 8, 10, 1, 3, 5]
11 [8, 4, 10, 2, 6, 9, 11, 1, 3, 5, 7]
12 [8, 4, 11, 2, 6, 10, 12, 1, 3, 5, 7, 9]
13 [8, 4, 12, 2, 6, 10, 13, 1, 3, 5, 7, 9, 11]
14 [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13]
15 [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]

@dtldarek 그의 의견은 제거 된 것으로 보이지만, 지금은 테스트 사례를 통과 한 것으로 보입니다.
Joachim Isaksson

사람들이 버그라고 말하는 의견 때문에 @dtldarek의 답변을 올리지 않도록 내 의견을 삭제했습니다.
피터 테일러

@PeterTaylor 글쎄, 당신의 배려에 감사합니다 ;-)
dtldarek

1

자바

이것은 직접 계산 솔루션입니다. 나는 그것이 효과가 있다고 생각하지만 실제로는 무해한 부작용이 있습니다. 생성 된 배열이 손상되었을 수 있지만 검색에 영향을주는 방식은 아닙니다. 0 (널) 노드를 생성하는 대신 도달 할 수없는 노드를 생성합니다. 즉, 노드가 검색 중에 트리에서 이미 발견되었습니다. 이진 검색 트리 배열의 정규 제곱의 인덱스 배열을 불규칙한 크기의 이진 검색 트리 배열에 매핑하여 작동합니다. 적어도 나는 그것이 작동한다고 생각합니다.

import java.util.Arrays;

public class SortedArrayToBalanceBinarySearchTreeArray
{
    public static void main(String... args)
    {
        System.out.println(Arrays.toString(binarySearchTree(19)));
    }

    public static int[] binarySearchTree(int m)
    {
        int n = powerOf2Ceiling(m + 1);
        int[] array = new int[n - 1];

        for (int k = 1, index = 1; k < n; k *= 2)
        {
            for (int i = 0; i < k; ++i)
            {
                array[index - 1] = (int) (.5 + ((float) (m)) / (n - 1)
                        * (n / (2 * k) * (1 + 2 * index) - n));
                ++index;
            }
        }

        return array;
    }

    public static int powerOf2Ceiling(int n)
    {
        n--;
        n |= n >> 1;
        n |= n >> 2;
        n |= n >> 4;
        n |= n >> 8;
        n |= n >> 16;
        n++;

        return n;
    }

}

다음은 더 간단한 버전입니다 (함수와 이름이 쌍을 이루고 있습니다). 여전히 공백이 있지만 승리에 대해 걱정하지 않습니다. 또한이 버전은 실제로 배열을 사용합니다. 다른 하나는 배열에서 가장 높은 색인을 위해 int를 가져 왔습니다.

public static int[] b(int m[])
{
    int n = m.length;
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    n++;

    int[] a = new int[n - 1];

    for (int k = 1, j = 1, i; k < n; k *= 2)
    {
        for (i = 0; i < k; ++i)
        {
            a[j - 1] = m[(int) (.5 + ((float) m.length) / (n - 1)
                    * (n / (2 * k) * (1 + 2 * j) - n)) - 1];
            ++j;
        }
    }

    return a;
}

이것은 code-golf 이므로 메소드 / 이름 / 등을 가능한 한 짧게 줄이십시오. 모든 공백 (및 불필요한 메소드 / 자료)을 제거하고 문자 수를 삽입하십시오. 그렇지 않으면, 당신의 위대한 일.
Justin

@ 제이크 와튼. 직접 매핑 솔루션을보고 싶습니다. 값이 반올림되는 연속적인 수학적 매핑에 의존하기 때문에 매우 큰 배열에서 작동하는 100 % 확신이 없습니다. 확실히 작동하는 것 같지만 그것을 증명하는 방법을 모르겠습니다.
metaphyze

1

GolfScript ( 79 77 70 자)

질문의 예에서 함수를 사용 하므로이 함수를 만들었습니다. 제거{}:f;스택에 입력을 받고 BST를 스택에 남겨 두는 표현식을 남기기 를 5 개의 문자가 절약됩니다.

{[.;][{{.!!{[.,.)[1]*{(\(@++}@(*1=/()\@~]}*}%.{0=}%\{1>~}%.}do][]*}:f;

온라인 데모 (참고 : 앱을 준비하는 데 약간의 시간이 소요될 수 있습니다. 3 초 동안 실행되기 전에 두 번 시간이 초과되었습니다).

공백을 사용하여 구조를 표시하십시오.

{
    # Input is an array: wrap it in an array for the working set
    [.;]
    [{
        # Stack: emitted-values working-set
        # where the working-set is essentially an array of subtrees
        # For each subtree in working-set...
        {
            # ...if it's not the empty array...
            .!!{
                # ...gather into an array...
                [
                    # Get the size of the subtree
                    .,
                    # OEIS A006165, offset by 1
                    .)[1]*{(\(@++}@(*1=
                    # Split into [left-subtree-plus-root right-subtree]
                    /
                    # Rearrange to root left-subtree right-subtree
                    # where left-subtree might be [] and right-subtree might not exist at all
                    ()\@~
                ]
            }*
        }%
        # Extract the leading element of each processed subtree: these will join the emitted-values
        .{0=}%
        # Create a new working-set of the 1, or 2 subtrees of each processed subtree
        \{1>~}%
        # Loop while the working-set is non-empty
        .
    }do]
    # Stack: [[emitted values at level 0][emitted values at level 1]...]
    # Flatten by joining with the empty array
    []*
}:f;

1

J , 52 바이트

t=:/:(#/:@{.(+:,>:@+:@i.@>:@#)^:(<.@(2&^.)@>:@#`1:))

이 함수는 정렬 된 목록을 가져와 이진 트리 순서로 반환

나무의 모양은 동일하지만 바닥 수준이 줄어 듭니다.

  • `1: 1로 시작
  • <.@(2&^.)@>:@# log2의 플로어별로 반복 (길이 + 1)
  • +: , >:@+:@i.@>:@# 루프 : 홀수 1,3 .. 2 * length + 1을 가진 마지막 벡터의 이중을 추가합니다
  • # /:@{. 필요한 수의 품목 만 가져 와서 정렬 지수를 얻습니다.
  • /: 주어진 색인에 정렬 색인을 적용하십시오

TIO


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