Jak połączyć dwie posortowane tablice w posortowaną tablicę? [Zamknięte]


160

Zapytano mnie o to w wywiadzie i oto rozwiązanie, które podałem:

public static int[] merge(int[] a, int[] b) {

    int[] answer = new int[a.length + b.length];
    int i = 0, j = 0, k = 0;
    while (i < a.length && j < b.length)
    {
        if (a[i] < b[j])
        {
            answer[k] = a[i];
            i++;
        }
        else
        {
            answer[k] = b[j];
            j++;
        }
        k++;
    }

    while (i < a.length)
    {
        answer[k] = a[i];
        i++;
        k++;
    }

    while (j < b.length)
    {
        answer[k] = b[j];
        j++;
        k++;
    }

    return answer;
}

Czy istnieje skuteczniejszy sposób na zrobienie tego?

Edycja: poprawione metody długości.


30
Wygląda na całkiem dobrą odpowiedź. Ten problem będzie miał co najwyżej złożoność O (n) i twoja odpowiedź to osiągnie. Wszystko inne będzie mikrooptymalizacją.
Drew Hall

3
Dobrze się spisałeś! Zasadniczo jest to część sortowania przez scalanie: łączenie dwóch posortowanych strumieni (z taśmy lub dysku) w inny posortowany strumień.
Vladimir Dyuzhev

9
Masz pracę?
Shai

5
Możesz również użyć operatora potrójnego: while (i < a.length && j < b.length) answer[k++] = a[i] < b[j] ? a[i++] : b[j++]; Specyfikacja języka Java: operator warunkowy? : .
Anton Dozortsev

1
Zapomniałeś skomentować !!!
LiziPizi

Odpowiedzi:


33

Niewielkie ulepszenie, ale po zakończeniu pętli głównej możesz użyć System.arraycopydo skopiowania końca jednej z tablic wejściowych, gdy dojdziesz do końca drugiej. Nie zmieni to jednak O(n)charakterystyki wydajności Twojego rozwiązania.


109
public static int[] merge(int[] a, int[] b) {

    int[] answer = new int[a.length + b.length];
    int i = 0, j = 0, k = 0;

    while (i < a.length && j < b.length)  
       answer[k++] = a[i] < b[j] ? a[i++] :  b[j++];

    while (i < a.length)  
        answer[k++] = a[i++];

    while (j < b.length)    
        answer[k++] = b[j++];

    return answer;
}

Jest trochę bardziej kompaktowy, ale dokładnie taki sam!


Do osoby, która powiedziała, że ​​spowodowało to wyjątek dotyczący indeksu poza granicami, jakich danych wejściowych używasz? U mnie działa we wszystkich przypadkach.
Mike Saull,

1
Użyj pętli for, aby scalić wiersze deklarujące zmienne pętli i sterowanie pętlą. Oszczędnie używaj podwójnych pustych linii - wygląda na to, że między symetrycznymi „kopiami końcowymi” nie ma sensu.
siwobrody

58

Dziwię się, że nikt nie wspomniał o tej znacznie fajniejszej, wydajniejszej i kompaktowej implementacji:

public static int[] merge(int[] a, int[] b) {
    int[] answer = new int[a.length + b.length];
    int i = a.length - 1, j = b.length - 1, k = answer.length;

    while (k > 0)
        answer[--k] =
                (j < 0 || (i >= 0 && a[i] >= b[j])) ? a[i--] : b[j--];
    return answer;
}

Punkty zainteresowania

  1. Zauważ, że wykonuje taką samą lub mniejszą liczbę operacji jak każdy inny algorytm O (n) , ale dosłownie w jednej instrukcji w pojedynczej pętli while!
  2. Jeśli dwie tablice mają w przybliżeniu ten sam rozmiar, to stała dla O (n) jest taka sama. Jednak jeśli tablice są naprawdę niezrównoważone, wygrałyby wersje z System.arraycopy, ponieważ wewnętrznie może to zrobić za pomocą jednej instrukcji asemblera x86.
  3. Zauważ a[i] >= b[j]zamiast a[i] > b[j]. Gwarantuje to "stabilność", która jest definiowana tak, że gdy elementy a i b są równe, chcemy elementów z a przed b.

To naprawdę bardzo fajne podejście. Miałem problemy z uzyskaniem dobrych testów porównawczych dla moich algorytmów sortowania przez scalanie w języku Swift lang. Zamiana tego dała mi to, czego potrzebowałem, bardzo dziękuję
Chackle,

Jaki jest punkt (j <0) w pętli while? Przy okazji, +1, to jest naprawdę fajne! Dzięki za udostępnienie.
Hengameh,

2
@Hengameh w przypadku j < 0, bjest już wyczerpany, więc nadal dodajemy pozostałe aelementy do answer tablicy
Natan Streppel

6
Zbyt „sprytne” i trudne do odczytania w mojej głowie. Wolę łatwiejszy do odczytania kod, zwłaszcza, że ​​tak naprawdę nie osiągasz żadnej poprawy wydajności z tym kodem.
Kevin M

1
plus punkt za Uwaga i a [i]> = b [j] zamiast a [i]> b [j]. To gwarantuje "stabilność"
Yan Khonski

16

Wszelkie ulepszenia, które można by wprowadzić, byłyby mikro-optymalizacjami, ogólny algorytm jest poprawny.


Jeśli a jest duże, a b jest małe, to ten algorytm jest nieprawidłowy.
Jack

7
To nie jest złe, ale nie wydajne.
jack

@jack, jak możesz to zrobić szybciej niż O (n), kiedy tworzysz tablicę n elementów?
Będzie

@will System.arrayCopy()jest głupio szybki, ponieważ wykorzystuje memcpywywołania zoptymalizowane pod kątem procesora . Istnieje więc możliwość poprawy wydajności poprzez kopiowanie fragmentów. Istnieje również możliwość binarnego wyszukiwania granic.
slim

Zwłaszcza jeśli możesz użyć posortowanej natury, aby pominąć większość wpisów i nigdy ich nie porównywać. Właściwie możesz pokonać O (n).
Tatarize

10

To rozwiązanie jest również bardzo podobne do innych postów z tym wyjątkiem, że używa System.arrayCopy do kopiowania pozostałych elementów tablicy.

private static int[] sortedArrayMerge(int a[], int b[]) {
    int result[] = new int[a.length +b.length];
    int i =0; int j = 0;int k = 0;
    while(i<a.length && j <b.length) {
        if(a[i]<b[j]) {
            result[k++] = a[i];
            i++;
        } else {
            result[k++] = b[j];
            j++;
        }
    }
    System.arraycopy(a, i, result, k, (a.length -i));
    System.arraycopy(b, j, result, k, (b.length -j));
    return result;
}

7

Oto zaktualizowana funkcja. Usuwa duplikaty, miejmy nadzieję, że ktoś uzna to za przydatne:

public static long[] merge2SortedAndRemoveDublicates(long[] a, long[] b) {
    long[] answer = new long[a.length + b.length];
    int i = 0, j = 0, k = 0;
    long tmp;
    while (i < a.length && j < b.length) {
        tmp = a[i] < b[j] ? a[i++] : b[j++];
        for ( ; i < a.length && a[i] == tmp; i++);
        for ( ; j < b.length && b[j] == tmp; j++);
        answer[k++] = tmp;
    }
    while (i < a.length) {
        tmp = a[i++];
        for ( ; i < a.length && a[i] == tmp; i++);
        answer[k++] = tmp;
    }
    while (j < b.length) {
        tmp = b[j++];
        for ( ; j < b.length && b[j] == tmp; j++);
        answer[k++] = tmp;
    }
    return Arrays.copyOf(answer, k);
}

+1, dzięki za udostępnienie. Jedno pytanie: dlaczego wybrałeś typ tablicy i typ zmiennej „temp”, długi?
Hengameh,

(Mam wątpliwości co do nazwy metody.)
Greybeard

5

Można to zrobić w 4 stwierdzeniach, jak poniżej

 int a[] = {10, 20, 30};
 int b[]= {9, 14, 11};
 int res[]=new int[a.legth+b.length]; 
 System.arraycopy(a,0, res, 0, a.length); 
 System.arraycopy(b,0,res,a.length, b.length);
 Array.sort(res)


5
Nie rozumiem, dlaczego ta odpowiedź otrzymała głosy negatywne. Prawdą jest, że nie jest skuteczny. Ale czasami wszystko, czego potrzebujesz, to jak najszybciej wykonać pracę. Jeśli masz do czynienia z bardzo małymi tablicami, powiedzmy mniej niż 100 elementów, wolałbym użyć powyższego kodu, zamiast pisać długi kod, który nie wprowadzi żadnych ważnych ulepszeń wydajności. Dlatego dziękuję Sudhirowi za dostarczenie tego łatwego rozwiązania i SANN3 za jego edycję.
Ahmedov

2
Niepisana przesłanka jest taka, że sortfunkcja nie może używać siebie jako metody sortowania. To byłaby nieskończona regresja zamiast rekurencji. Inną przesłanką jest to, że merge_array jest funkcją implementującą sort. Zatem ta odpowiedź jest bezużyteczna w najbardziej prawdopodobnym kontekście.
Aki Suihkonen

Zadane pytanie nie wspominało, że wymagany kod dotyczy tylko małej tablicy. Tak więc ta odpowiedź byłaby myląca, chyba że wyraźnie określa jej ograniczenia. Spójrz także na moją odpowiedź poniżej. Potrzeba tej samej liczby wierszy, aby napisać wydajny kod, który działa dla dowolnej wielkości tablicy :)
Shital Shah

Pytanie zakładało, że tablice są już posortowane. Gdyby tablice były bardzo duże, rozwiązanie to zatrzymałoby się i działało słabo. Jestem więc pewien, że uzyskasz wymagane wyniki końcowe, ale aplikacja nie będzie działać i nie dostaniesz pracy, gdybym przeprowadzał rozmowę kwalifikacyjną.
Kevin M

Funkcja Array.sort () korzysta z funkcji TimSort, która w dużym stopniu znajdzie posortowane przebiegi i zastosuje do nich sortowanie przez scalanie. Co dziwne, ten kod nie może być nawet winny za „nieefektywny”, w rzeczywistości zakończy się w czasie O (n) z powodu posortowanych przebiegów. Możesz przeprowadzić na nim kilka testów porównawczych, szanse są dobre, że dość często pokona kod OP.
Tatarize

4

Musiałem to napisać w javascript, oto jest:

function merge(a, b) {
    var result = [];
    var ai = 0;
    var bi = 0;
    while (true) {
        if ( ai < a.length && bi < b.length) {
            if (a[ai] < b[bi]) {
                result.push(a[ai]);
                ai++;
            } else if (a[ai] > b[bi]) {
                result.push(b[bi]);
                bi++;
            } else {
                result.push(a[ai]);
                result.push(b[bi]);
                ai++;
                bi++;
            }
        } else if (ai < a.length) {
            result.push.apply(result, a.slice(ai, a.length));
            break;
        } else if (bi < b.length) {
            result.push.apply(result, b.slice(bi, b.length));
            break;
        } else {
            break;
        }
    }
    return result;
}

4

Kolekcje Apache obsługują metodę sortowania od wersji 4; możesz to zrobić za pomocącollate metody w:

org.apache.commons.collections4.CollectionUtils

Oto cytat z javadoc:

collate(Iterable<? extends O> a, Iterable<? extends O> b, Comparator<? super O> c)

Łączy dwie posortowane kolekcje, a i b, w jedną, posortowaną listę, w taki sposób, że zachowana zostaje kolejność elementów według komparatora c.

Nie wymyślaj koła na nowo! Referencje dokumentu: http://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/CollectionUtils.html


4

GallopSearch Merge: O (log (n) * log (i)) zamiast O (n)

Poszedłem do przodu i zastosowałem sugestię siwobrodego w komentarzach. Głównie dlatego, że potrzebowałem wysoce wydajnej wersji tego kodu o znaczeniu krytycznym.

  • Kod wykorzystuje wyszukiwanie galopowe, które jest O (log (i)), gdzie i jest odległością od bieżącego indeksu, do którego istnieje odpowiedni indeks.
  • Kod używa binarySearch for po tym, jak wyszukiwanie galopowe zidentyfikowało właściwy zakres. Ponieważ galop ogranicza to do mniejszego zakresu, wynikowe wyszukiwanie binarne jest również O (log (i))
  • Galop i scalanie są wykonywane wstecz. Nie wydaje się to krytyczne dla misji, ale pozwala na łączenie tablic na miejscu. Jeśli w jednej z tablic jest wystarczająco dużo miejsca na przechowywanie wartości wyników, możesz po prostu użyć jej jako tablicy scalającej i tablicy wyników. W takim przypadku należy określić prawidłowy zakres w tablicy.
  • Nie wymaga przy tym alokacji pamięci (duże oszczędności w operacjach krytycznych). Po prostu upewnia się, że nie może i nie może nadpisać żadnych nieprzetworzonych wartości (co można zrobić tylko wstecz). W rzeczywistości używasz tej samej tablicy zarówno dla danych wejściowych, jak i wyników. Nie przyniesie to żadnych złych skutków.
  • Konsekwentnie używałem Integer.compare (), aby można było to zmienić do innych celów.
  • Jest pewna szansa, że ​​trochę się wygłupiałem i nie wykorzystałem informacji, które wcześniej udowodniłem. Na przykład wyszukiwanie binarne w zakresie dwóch wartości, dla których jedna wartość została już sprawdzona. Mógłby istnieć również lepszy sposób na określenie głównej pętli, odwracająca wartość c nie byłaby potrzebna, gdyby zostały połączone w dwie operacje po kolei. Ponieważ wiesz, że za każdym razem będziesz robić jedno, potem drugie. Jest miejsce na trochę lakieru.

Powinien to być najbardziej efektywny sposób, aby to zrobić, ze złożonością czasową O (log (n) * log (i)) zamiast O (n). I w najgorszym przypadku złożoność czasowa O (n). Jeśli twoje tablice są zlepione i mają razem długie ciągi wartości, będzie to przyćmić każdy inny sposób, w przeciwnym razie będzie po prostu lepszy od nich.

Ma dwie wartości odczytu na końcach tablicy scalającej i wartość zapisu w tablicy wyników. Po ustaleniu, która wartość końcowa jest mniejsza, wykonuje galopowe przeszukiwanie tej tablicy. 1, 2, 4, 8, 16, 32 itd. Gdy znajdzie zakres, w którym wartość odczytu drugiej tablicy jest większa. Przeszukuje binarnie w tym zakresie (przecina zakres o połowę, przeszukuje właściwą połowę, powtarza do pojedynczej wartości). Następnie tablica kopiuje te wartości do pozycji zapisu. Należy pamiętać, że kopia jest z konieczności przenoszona w taki sposób, że nie może nadpisać tych samych wartości z obu tablic odczytujących (co oznacza, że ​​tablica zapisu i tablica odczytu mogą być takie same). Następnie wykonuje tę samą operację dla drugiej tablicy, o której wiadomo, że jest mniejsza niż nowa odczytana wartość drugiej tablicy.

static public int gallopSearch(int current, int[] array, int v) {
    int d = 1;
    int seek = current - d;
    int prevIteration = seek;
    while (seek > 0) {
        if (Integer.compare(array[seek], v) <= 0) {
            break;
        }
        prevIteration = seek;
        d <<= 1;
        seek = current - d;
        if (seek < 0) {
            seek = 0;
        }
    }
    if (prevIteration != seek) {
        seek = binarySearch(array, seek, prevIteration, v);
        seek = seek >= 0 ? seek : ~seek;
    }
    return seek;
}

static public int binarySearch(int[] list, int fromIndex, int toIndex, int v) {
    int low = fromIndex;
    int high = toIndex - 1;
    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = list[mid];
        int cmp = Integer.compare(midVal, v);
        if (cmp < 0) {
            low = mid + 1;
        } else if (cmp > 0) {
            high = mid - 1;
        } else {
            return mid;// key found
        }
    }
    return -(low + 1);// key not found.
}

static public int[] sortedArrayMerge(int[] a, int[] b) {
    return sortedArrayMerge(null, a, a.length, b, b.length);
}

static public int[] sortedArrayMerge(int[] results, int[] a, int aRead, int b[], int bRead) {
    int write = aRead + bRead, length, gallopPos;
    if ((results == null) || (results.length < write)) {
        results = new int[write];
    }
    if (aRead > 0 && bRead > 0) {
        int c = Integer.compare(a[aRead - 1], b[bRead - 1]);
        while (aRead > 0 && bRead > 0) {
            switch (c) {
                default:
                    gallopPos = gallopSearch(aRead, a, b[bRead-1]);
                    length = (aRead - gallopPos);
                    write -= length;
                    aRead = gallopPos;
                    System.arraycopy(a, gallopPos--, results, write, length);
                    c = -1;
                    break;
                case -1:
                    gallopPos = gallopSearch(bRead, b, a[aRead-1]);
                    length = (bRead - gallopPos);
                    write -= length;
                    bRead = gallopPos;
                    System.arraycopy(b, gallopPos--, results, write, length);
                    c = 1;
                    break;
            }
        }
    }
    if (bRead > 0) {
        if (b != results) {
            System.arraycopy(b, 0, results, 0, bRead);
        }
    } else if (aRead > 0) {
        if (a != results) {
            System.arraycopy(a, 0, results, 0, aRead);
        }
    }
    return results;
}

Powinien to być najbardziej efektywny sposób, aby to zrobić.


Niektóre odpowiedzi miały zduplikowaną możliwość usuwania. Będzie to wymagało algorytmu O (n), ponieważ musisz faktycznie porównać każdy element. Więc tutaj jest osobna, do zastosowania po fakcie. Nie możesz galopować przez wiele wpisów do końca, jeśli chcesz spojrzeć na wszystkie z nich, chociaż możesz galopować przez duplikaty, jeśli masz ich dużo.

static public int removeDuplicates(int[] list, int size) {
    int write = 1;
    for (int read = 1; read < size; read++) {
        if (list[read] == list[read - 1]) {
            continue;
        }
        list[write++] = list[read];
    }
    return write;
}

Aktualizacja: poprzednia odpowiedź, nie okropny kod, ale wyraźnie gorszy od powyższego.

Kolejna niepotrzebna hiperoptymalizacja. Nie tylko wywołuje arraycopy dla bitów końcowych, ale także dla początku. Przetwarzanie dowolnego wprowadzającego nienakładania się w O (log (n)) przez wyszukiwanie binarne w danych. O (log (n) + n) jest O (n) iw niektórych przypadkach efekt będzie dość wyraźny, szczególnie w sytuacjach, gdy w ogóle nie ma nakładania się między scalanymi tablicami.

private static int binarySearch(int[] array, int low, int high, int v) {
    high = high - 1;
    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = array[mid];
        if (midVal > v)
            low = mid + 1;
        else if (midVal < v)
            high = mid - 1;
        else
            return mid; // key found
    }
    return low;//traditionally, -(low + 1);  // key not found.
}

private static int[] sortedArrayMerge(int a[], int b[]) {
    int result[] = new int[a.length + b.length];
    int k, i = 0, j = 0;
    if (a[0] > b[0]) {
        k = i = binarySearch(b, 0, b.length, a[0]);
        System.arraycopy(b, 0, result, 0, i);
    } else {
        k = j = binarySearch(a, 0, a.length, b[0]);
        System.arraycopy(a, 0, result, 0, j);
    }
    while (i < a.length && j < b.length) {
        result[k++] = (a[i] < b[j]) ? a[i++] : b[j++];
    }
    if (j < b.length) {
        System.arraycopy(b, j, result, k, (b.length - j));
    } else {
        System.arraycopy(a, i, result, k, (a.length - i));
    }
    return result;
}

1
Głosowano za rozpoczęciem robienia czegoś z symetrią, ale po co na tym poprzestać? Użyj wyszukiwania galopującego, niech zwraca indeks po kluczach równości. Użyj kopii tablicy, jeśli więcej niż 3 elementy, tylko. Zauważ, że po tej kopii nic się nie zmieniło, oprócz a) indeksu początkowego w jednym wejściu i tablicy wyjściowej b) twojej wiedzy o tym, który „następny” element jest mniejszy.
siwobrody

To jest całkowicie to, co robi zaimplementowany Arrays.sort. W najgorszym przypadku jest to sortowanie przez scalanie. Myślę, że w razie potrzeby zamieniają 2 elementy, ale wpadają w arraycopy na więcej niż 2 elementy. Nie jestem pewien, czy następny element byłby sprawdzany liniowo, czy binarnie. Spekulatywne sprawdzenie, czy można galopować na większą odległość, byłoby całkiem sporą zaletą. Na przykład sprawdź 8 z ​​wyprzedzeniem, a jeśli możesz skopiować, zaoszczędziłeś sobie 7 operacji rzeczy, na które nie musisz patrzeć.
Tatarize

@greybeard ... i gotowe. Cofnąłem się również, aby móc ponownie użyć tej samej pamięci.
Tatarize

Dobrze, że zmotywowałeś się do balistyki. Przyjrzyjmy się dokładniej opadom czasu w ciągu dnia.
siwobrody

That is totally what the implemented Arrays.sort does( To : z pierwszej rewizji Twojej odpowiedzi - lub - z mojego komentarza z 19 lutego?) - nie możesz znaleźć ani w JDK 8 firmy Sunsoft: do której implementacji Arrays.sortsię odnosisz?
siwobrody

2

Oto skrócony formularz napisany w javascript:

function sort( a1, a2 ) {

    var i = 0
        , j = 0
        , l1 = a1.length
        , l2 = a2.length
        , a = [];

    while( i < l1 && j < l2 ) {

        a1[i] < a2[j] ? (a.push(a1[i]), i++) : (a.push( a2[j]), j++);
    }

    i < l1 && ( a = a.concat( a1.splice(i) ));
    j < l2 && ( a = a.concat( a2.splice(j) ));

    return a;
}

1
    public class Merge {

    // stably merge a[lo .. mid] with a[mid+1 .. hi] using aux[lo .. hi]
    public static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {

        // precondition: a[lo .. mid] and a[mid+1 .. hi] are sorted subarrays
        assert isSorted(a, lo, mid);
        assert isSorted(a, mid+1, hi);

        // copy to aux[]
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k]; 
        }

        // merge back to a[]
        int i = lo, j = mid+1;
        for (int k = lo; k <= hi; k++) {
            if      (i > mid)              a[k] = aux[j++];
            else if (j > hi)               a[k] = aux[i++];
            else if (less(aux[j], aux[i])) a[k] = aux[j++];
            else                           a[k] = aux[i++];
        }

        // postcondition: a[lo .. hi] is sorted
        assert isSorted(a, lo, hi);
    }

    // mergesort a[lo..hi] using auxiliary array aux[lo..hi]
    private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
        if (hi <= lo) return;
        int mid = lo + (hi - lo) / 2;
        sort(a, aux, lo, mid);
        sort(a, aux, mid + 1, hi);
        merge(a, aux, lo, mid, hi);
    }

    public static void sort(Comparable[] a) {
        Comparable[] aux = new Comparable[a.length];
        sort(a, aux, 0, a.length-1);
        assert isSorted(a);
    }


   /***********************************************************************
    *  Helper sorting functions
    ***********************************************************************/

    // is v < w ?
    private static boolean less(Comparable v, Comparable w) {
        return (v.compareTo(w) < 0);
    }

    // exchange a[i] and a[j]
    private static void exch(Object[] a, int i, int j) {
        Object swap = a[i];
        a[i] = a[j];
        a[j] = swap;
    }


   /***********************************************************************
    *  Check if array is sorted - useful for debugging
    ***********************************************************************/
    private static boolean isSorted(Comparable[] a) {
        return isSorted(a, 0, a.length - 1);
    }

    private static boolean isSorted(Comparable[] a, int lo, int hi) {
        for (int i = lo + 1; i <= hi; i++)
            if (less(a[i], a[i-1])) return false;
        return true;
    }


   /***********************************************************************
    *  Index mergesort
    ***********************************************************************/
    // stably merge a[lo .. mid] with a[mid+1 .. hi] using aux[lo .. hi]
    private static void merge(Comparable[] a, int[] index, int[] aux, int lo, int mid, int hi) {

        // copy to aux[]
        for (int k = lo; k <= hi; k++) {
            aux[k] = index[k]; 
        }

        // merge back to a[]
        int i = lo, j = mid+1;
        for (int k = lo; k <= hi; k++) {
            if      (i > mid)                    index[k] = aux[j++];
            else if (j > hi)                     index[k] = aux[i++];
            else if (less(a[aux[j]], a[aux[i]])) index[k] = aux[j++];
            else                                 index[k] = aux[i++];
        }
    }

    // return a permutation that gives the elements in a[] in ascending order
    // do not change the original array a[]
    public static int[] indexSort(Comparable[] a) {
        int N = a.length;
        int[] index = new int[N];
        for (int i = 0; i < N; i++)
            index[i] = i;

        int[] aux = new int[N];
        sort(a, index, aux, 0, N-1);
        return index;
    }

    // mergesort a[lo..hi] using auxiliary array aux[lo..hi]
    private static void sort(Comparable[] a, int[] index, int[] aux, int lo, int hi) {
        if (hi <= lo) return;
        int mid = lo + (hi - lo) / 2;
        sort(a, index, aux, lo, mid);
        sort(a, index, aux, mid + 1, hi);
        merge(a, index, aux, lo, mid, hi);
    }

    // print array to standard output
    private static void show(Comparable[] a) {
        for (int i = 0; i < a.length; i++) {
            StdOut.println(a[i]);
        }
    }

    // Read strings from standard input, sort them, and print.
    public static void main(String[] args) {
        String[] a = StdIn.readStrings();
        Merge.sort(a);
        show(a);
    }
}

Co to skopiować a[mid+1 .. hi]do auxza?
siwobrody

1

Myślę, że wprowadzenie listy pominięć dla większej posortowanej tablicy może zmniejszyć liczbę porównań i przyspieszyć proces kopiowania do trzeciej tablicy. Może to być dobre, jeśli tablica jest zbyt duża.


1
public int[] merge(int[] a, int[] b) {
    int[] result = new int[a.length + b.length];
    int aIndex, bIndex = 0;

    for (int i = 0; i < result.length; i++) {
        if (aIndex < a.length && bIndex < b.length) {
            if (a[aIndex] < b[bIndex]) {
                result[i] = a[aIndex];
                aIndex++;
            } else {
                result[i] = b[bIndex];
                bIndex++;
            }
        } else if (aIndex < a.length) {
            result[i] = a[aIndex];
            aIndex++;
        } else {
            result[i] = b[bIndex];
            bIndex++;
        }
    }

    return result;
}

2
Jakieś wyjaśnienie byłoby miłe. :)
gsamaras

1
public static int[] merge(int[] a, int[] b) {
    int[] mergedArray = new int[(a.length + b.length)];
    int i = 0, j = 0;
    int mergedArrayIndex = 0;
    for (; i < a.length || j < b.length;) {
        if (i < a.length && j < b.length) {
            if (a[i] < b[j]) {
                mergedArray[mergedArrayIndex] = a[i];
                i++;
            } else {
                mergedArray[mergedArrayIndex] = b[j];
                j++;
            }
        } else if (i < a.length) {
            mergedArray[mergedArrayIndex] = a[i];
            i++;
        } else if (j < b.length) {
            mergedArray[mergedArrayIndex] = b[j];
            j++;
        }
        mergedArrayIndex++;
    }
    return mergedArray;
}

Jaka jest w tym zbawcza łaska? Można go skurczyć for (int i, j, k = i = j = 0 ; k < c.length ; ) c[k++] = b.length <= j || i < a.length && a[i] < b[j] ? a[i++] : b[j++];. Czym się to różni od odpowiedzi Andrew z 2014 roku ?
siwobrody

1

Algorytm można ulepszyć na wiele sposobów. Na przykład rozsądne jest sprawdzenie, czy a[m-1]<b[0]lub b[n-1]<a[0]. W żadnym z tych przypadków nie ma potrzeby wykonywania więcej porównań. Algorytm mógłby po prostu skopiować tablice źródłowe w wynikowej we właściwej kolejności.

Bardziej skomplikowane ulepszenia mogą obejmować wyszukiwanie elementów przeplatających i uruchamianie algorytmu scalania tylko dla nich. Może to zaoszczędzić dużo czasu, gdy rozmiary scalonych tablic różnią się dziesiątkami razy.


W przypadku tego ulepszenia lepiej byłoby sprawdzić, gdzie pierwszy element znajdowałby się w drugiej tablicy za pomocą funkcji wyszukiwania binarnego, a następnie rozpocząć tworzenie kopii danych w tablicy. Wtedy w przypadku, gdy jeden z tych sprawdzeń jest prawdziwy, miałby po prostu Arraycopy all, a następnie Arraycopy the ternary i otrzymujesz ten sam wynik. Ale w przypadku niewielkiego nakładania się wystarczy wykonać odpowiedni algorytm tylko podczas nakładania, a nie innym razem. Ponieważ utkniesz z O (n), wcześniejsze użycie szybkiej komendy O (logn) nic nie kosztuje.
Tatarize

1

Ten problem jest związany z algorytmem scalania, w którym dwie posortowane pod-tablice są łączone w jedną posortowaną pod-tablicę. W CLR książka daje przykład algorytmu i czyści konieczność sprawdzenia, czy koniec został osiągnięty przez dodanie wartości wskaźnikowych (coś, porównuje i „większy niż jakiejkolwiek innej wartości”) na końcu każdej tablicy.

Napisałem to w Pythonie, ale powinno też ładnie przełożyć się na Javę:

def func(a, b):
    class sentinel(object):
        def __lt__(*_):
            return False

    ax, bx, c = a[:] + [sentinel()], b[:] + [sentinel()], []
    i, j = 0, 0

    for k in range(len(a) + len(b)):
        if ax[i] < bx[j]:
            c.append(ax[i])
            i += 1
        else:
            c.append(bx[j])
            j += 1

    return c

zbiorcze kopiowanie elementów raz na drugą stronę, aby (umiejętnie) użyć wartownika…
szarobrody

1

Możesz użyć 2 wątków, aby wypełnić wynikową tablicę, jeden z przodu, jeden z tyłu.

Może to działać bez synchronizacji w przypadku liczb, np. Jeśli każdy gwint wstawia połowę wartości.


0
//How to merge two sorted arrays into a sorted array without duplicates?
//simple C Coding
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

main()
{
    int InputArray1[] ={1,4,5,7,8,9,12,13,14,17,40};
    int InputArray2[] ={4,5,11,14,15,17,18,19,112,122,122,122,122};
    int n=10;
    int OutputArray[30];
    int i=0,j=0,k=0;
    //k=OutputArray
    while(i<11 && j<13)
    {
        if(InputArray1[i]<InputArray2[j])
        {
            if (k == 0 || InputArray1[i]!= OutputArray[k-1])
            {
                OutputArray[k++] = InputArray1[i];
            }
            i=i+1;
        }
        else if(InputArray1[i]>InputArray2[j])
        {
            if (k == 0 || InputArray2[j]!= OutputArray[k-1])
            {
                OutputArray[k++] = InputArray2[j];
            }
            j=j+1;
        }
        else
        {
            if (k == 0 || InputArray1[i]!= OutputArray[k-1])
            {
                OutputArray[k++] = InputArray1[i];
            }
            i=i+1;
            j=j+1;
        }
    };
    while(i<11)
    {
        if(InputArray1[i]!= OutputArray[k-1])
            OutputArray[k++] = InputArray1[i++];
        else
            i++;
    }
    while(j<13)
    {
        if(InputArray2[j]!= OutputArray[k-1])
            OutputArray[k++] = InputArray2[j++];
        else
            j++;
    }
    for(i=0; i<k; i++)
    {
        printf("sorted data:%d\n",OutputArray[i]);
    };
}

0
public static int[] merge(int[] listA, int[] listB) {
        int[] mergedList = new int[ listA.length + listB.length];
        int i = 0; // Counter for listA
        int j = 0; // Counter for listB
        int k = 0; // Counter for mergedList
        while (true) {
            if (i >= listA.length && j >= listB.length) {
                break;
            }
            if (i < listA.length && j < listB.length) { // If both counters are valid.
                if (listA[i] <= listB[j]) {
                    mergedList[k] = listA[i];
                    k++;
                    i++;
                } else {
                    mergedList[k] = listB[j];
                    k++;
                    j++;
                }
            } else if (i < listA.length && j >= listB.length) { // If only A's counter is valid.
                mergedList[k] = listA[i];
                k++;
                i++;
            } else if (i <= listA.length && j < listB.length) { // If only B's counter is valid
                mergedList[k] = listB[j];
                k++;
                j++;
            }
        }
        return mergedList;
    }

0
var arrCombo = function(arr1, arr2){
  return arr1.concat(arr2).sort(function(x, y) {
    return x - y;
  });
};

2
Ta odpowiedź nie dotyczy języka programowania Java, chociaż byłaby to dobra odpowiedź w przypadku javascript.
gknicker

To była część rozmowy kwalifikacyjnej. W takich przypadkach nie oczekuje się od Ciebie pisania „normalnego” kodu, jak powyżej. Szukają „wydajnego” kodu i pokazania, że ​​rozumiesz stosowane algorytmy.
d11wtq

0

Moim ulubionym językiem programowania jest JavaScript

function mergeSortedArrays(a, b){
    var result = [];

    var sI = 0;
    var lI = 0;
    var smallArr;
    var largeArr;
    var temp;

    if(typeof b[0] === 'undefined' || a[0]<b[0]){
        smallArr = a;
        largeArr = b;
    } else{
        smallArr = b;
        largeArr = a;
    }

    while(typeof smallArr[sI] !== 'undefined'){
        result.push(smallArr[sI]);
        sI++;

        if(smallArr[sI]>largeArr[lI] || typeof smallArr[sI] === 'undefined'){
            temp = smallArr;
            smallArr = largeArr;
            largeArr = temp;
            temp = sI;
            sI = lI;
            lI = temp;
        }
    }
    return result;
}

0

Może użyj System.arraycopy

public static byte[] merge(byte[] first, byte[] second){
    int len = first.length + second.length;
    byte[] full = new byte[len];
    System.arraycopy(first, 0, full, 0, first.length);
    System.arraycopy(second, 0, full, first.length, second.length);
    return full;
}

3
Po prostu je scalasz; Twoja wynikowa tablica sama w sobie nie jest posortowana, co było wymagane.
Sanjeev Dhiman

0
public static void main(String[] args) {
    int[] arr1 = {2,4,6,8,10,999};
    int[] arr2 = {1,3,5,9,100,1001};

    int[] arr3 = new int[arr1.length + arr2.length];

    int temp = 0;

    for (int i = 0; i < (arr3.length); i++) {
        if(temp == arr2.length){
            arr3[i] = arr1[i-temp];
        }
        else if (((i-temp)<(arr1.length)) && (arr1[i-temp] < arr2[temp])){
                arr3[i] = arr1[i-temp];
        }
        else{
            arr3[i] = arr2[temp];
            temp++;
        }
    }

    for (int i : arr3) {
        System.out.print(i + ", ");
    }
}

Wynik to:

1, 2, 3, 4, 5, 6, 8, 9, 10, 100, 999, 1001,


Mylące dla nazwania indeksu na arr2nie ind2, ale temp.
siwobrody

0

Możesz użyć operatorów trójskładnikowych, aby kod był nieco bardziej zwarty

public static int[] mergeArrays(int[] a1, int[] a2) {
    int[] res = new int[a1.length + a2.length];
    int i = 0, j = 0;

    while (i < a1.length && j < a2.length) {
        res[i + j] = a1[i] < a2[j] ? a1[i++] : a2[j++];
    }

    while (i < a1.length) {
        res[i + j] = a1[i++];
    }

    while (j < a2.length) {
        res[i + j] = a2[j++];
    }

    return res;
}

0
public static int[] mergeSorted(int[] left, int[] right) {
    System.out.println("merging " + Arrays.toString(left) + " and " + Arrays.toString(right));
    int[] merged = new int[left.length + right.length];
    int nextIndexLeft = 0;
    int nextIndexRight = 0;
    for (int i = 0; i < merged.length; i++) {
        if (nextIndexLeft >= left.length) {
            System.arraycopy(right, nextIndexRight, merged, i, right.length - nextIndexRight);
            break;
        }
        if (nextIndexRight >= right.length) {
            System.arraycopy(left, nextIndexLeft, merged, i, left.length - nextIndexLeft);
            break;
        }
        if (left[nextIndexLeft] <= right[nextIndexRight]) {
            merged[i] = left[nextIndexLeft];
            nextIndexLeft++;
            continue;
        }
        if (left[nextIndexLeft] > right[nextIndexRight]) {
            merged[i] = right[nextIndexRight];
            nextIndexRight++;
            continue;
        }
    }
    System.out.println("merged : " + Arrays.toString(merged));
    return merged;
}

Tylko trochę różni się od oryginalnego rozwiązania


0

Aby zaznaczyć dwie posortowane tablice w złożoności czasowej O (m + n), użyj poniższego podejścia z tylko jedną pętlą. m i n to długość pierwszej tablicy i drugiej tablicy.

public class MargeSortedArray {
    public static void main(String[] args) {
        int[] array = new int[]{1,3,4,7};
        int[] array2 = new int[]{2,5,6,8,12,45};
        int[] newarry = margeToSortedArray(array, array2);
        //newarray is marged array
    }

    // marge two sorted array with o(a+n) time complexity
    public static int[] margeToSortedArray(int[] array, int[] array2) {
        int newarrlen = array.length+array2.length;
        int[] newarr = new int[newarrlen];

        int pos1=0,pos2=0;
        int len1=array.length, len2=array2.length;

        for(int i =0;i<newarrlen;i++) {     
            if(pos1>=len1) {
                newarr[i]=array2[pos2];
                pos2++;
                continue;
            }
            if(pos2>=len2) {
                newarr[i]=array[pos1];
                pos1++;
                continue;
            }

            if(array[pos1]>array2[pos2]) {
                newarr[i]=array2[pos2];
                pos2++;
            } else {
                newarr[i]=array[pos1];
                pos1++;
            }   
        }

        return newarr;
    }

}

0
var arr1 = [2,10,20,30,100];
var arr2 = [2,4,5,6,7,8,9];
var j = 0;
var i =0;
var newArray = [];

for(var x=0;x< (arr1.length + arr2.length);x++){
    if(arr1[i] >= arr2[j]){                //check if element arr2 is equal and less than arr1 element
        newArray.push(arr2[j]);
      j++;
    }else if(arr1[i] < arr2[j]){            //check if element arr1 index value  is less than arr2 element
        newArray.push(arr1[i]);
        i++;
    }
    else if(i == arr1.length || j < arr2.length){    // add remaining arr2 element
        newArray.push(arr2[j]);
        j++
    }else{                                                   // add remaining arr1 element
        newArray.push(arr1[i]); 
        i++
    }

}

console.log(newArray);

-1

Ponieważ pytanie nie zakłada żadnego konkretnego języka. Oto rozwiązanie w Pythonie. Zakładając, że tablice są już posortowane.

Podejście 1 - używanie tablic numpy: import numpy

arr1 = numpy.asarray([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 11, 14, 15, 55])
arr2 = numpy.asarray([11, 32, 43, 45, 66, 76, 88])

array = numpy.concatenate((arr1,arr2), axis=0)
array.sort()

Podejście 2 - Używając listy, zakładając, że listy są sortowane.

list_new = list1.extend(list2)
list_new.sort()

Since the question doesn't assume any specific languageod 2011/5/11/19: 43, jest otagowany java .
siwobrody

Twoje rozwiązanie nie korzysta z list faktów są już posortowane, a jego czas wykonania nie jest O (n), ponieważ .sort()jest O(n log n)w najlepszym przypadku
dark_ruby

-1

Oto moja implementacja java, która usuwa duplikaty.

public static int[] mergesort(int[] a, int[] b) {
    int[] c = new int[a.length + b.length];
    int i = 0, j = 0, k = 0, duplicateCount = 0;

    while (i < a.length || j < b.length) {
        if (i < a.length && j < b.length) {
            if (a[i] == b[j]) {
                c[k] = a[i];
                i++;j++;duplicateCount++;
            } else {
                c[k] = a[i] < b[j] ? a[i++] : b[j++];
            }
        } else if (i < a.length) {
            c[k] = a[i++];
        } else if (j < a.length) {
            c[k] = b[j++];
        }
        k++;
    }

    return Arrays.copyOf(c, c.length - duplicateCount);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.