Mengira Susun di Jawa

1. Gambaran keseluruhan

Algoritma penyortiran tujuan umum seperti Merge Sort tidak membuat andaian mengenai input, jadi mereka tidak dapat mengalahkan O (n log n) dalam keadaan terburuk. Counting Sort, sebaliknya, mempunyai andaian mengenai input yang menjadikannya algoritma penyusun masa linear.

Dalam tutorial ini, kita akan berkenalan dengan mekanik Counting Sort dan kemudian menerapkannya di Java.

2. Mengira Susun

Mengira pengiraan, berbanding kebanyakan algoritma penyusun klasik, tidak menyusun input yang diberikan dengan membandingkan elemen. Sebaliknya, ia menganggap bahawa elemen input adalah n bilangan bulat dalam julat [0, k ] . Apabila k = O (n), maka pengiraan akan dijalankan dalam masa O (n) .

Oleh itu, perhatikan bahawa kita tidak dapat menggunakan pengiraan sebagai algoritma penyortiran tujuan umum. Namun, apabila input diselaraskan dengan andaian ini, cukup cepat!

2.1. Array Kekerapan

Katakan kita akan menyusun susunan inputdengan nilai dalam julat [0, 5]:

Pertama, kita harus menghitung kejadian setiap nombor dalam array input. Sekiranya kita mewakili pengiraan dengan array C , maka C [i] mewakili frekuensi nombor i dalam array input :

Sebagai contoh, kerana 5 muncul 3 kali dalam array input, nilai untuk indeks 5 sama dengan 3.

Sekarang memandangkan array C, kita harus menentukan berapa banyak elemen yang kurang dari atau sama dengan setiap elemen input. Sebagai contoh:

  • Satu elemen kurang daripada atau sama dengan sifar, atau dengan kata lain, hanya ada satu nilai sifar, yang sama dengan C [0]
  • Dua unsur kurang daripada atau sama dengan satu, iaitu sama dengan C [0] + C [1]
  • Empat nilai kurang dari atau sama dengan dua, iaitu sama dengan C [0] + C [1] + C [2]

Oleh itu, jika kita terus menghitung penjumlahan unsur n berturut-turut dalam C, kita dapat mengetahui berapa banyak unsur kurang dari atau sama dengan nombor n-1 dalam array input. Bagaimanapun, dengan menggunakan formula sederhana ini kita dapat mengemas kini C sebagai berikut:

2.2. Algoritma

Sekarang kita dapat menggunakan array tambahan C untuk menyusun array input. Inilah cara kerja pengiraan:

  • Ia mengulangi susunan input secara terbalik
  • Untuk setiap elemen i, C [i] - 1 mewakili lokasi nombor i dalam susunan yang disusun. Ini kerana fakta bahawa terdapat unsur C [i] kurang dari atau sama dengan i
  • Kemudian, ia mengurangkan C [i] pada akhir setiap pusingan

Untuk menyusun susunan input sampel, pertama kita harus mulakan dengan nombor 5, kerana ini adalah elemen terakhir. Menurut C [5], terdapat 11 unsur yang kurang dari atau sama dengan nombor 5.

Jadi, 5 harus menjadi elemen ke-11 dalam susunan yang disusun, oleh itu indeks 10:

Oleh kerana kita memindahkan 5 ke susunan yang disusun, kita harus mengurangkan C [5]. Elemen seterusnya dalam urutan terbalik adalah 2. Oleh kerana terdapat 4 elemen kurang dari atau sama dengan 2, nombor ini harus menjadi elemen ke-4 dalam susunan yang disusun:

Begitu juga, kita dapat mencari tempat yang tepat untuk elemen seterusnya iaitu 0:

Sekiranya kita terus berulang secara mundur dan memindahkan setiap elemen dengan tepat, kita akan berakhir dengan sesuatu seperti:

3. Mengira Susun - Pelaksanaan Java

3.1. Mengira Array Frekuensi

Pertama, diberi susunan elemen input dan k, kita harus menghitung array C:

int[] countElements(int[] input, int k) { int[] c = new int[k + 1]; Arrays.fill(c, 0); for (int i : input) { c[i] += 1; } for (int i = 1; i < c.length; i++) { c[i] += c[i - 1]; } return c; }

Mari pecahkan tandatangan kaedah:

  • input mewakili sebilangan nombor yang akan kita urutkan
  • The input pelbagai adalah pelbagai integer dalam julat [0, k ] - jadi k mewakili bilangan maksimum di input
  • Jenis pengembalian adalah susunan bilangan bulat yang mewakili array C

Dan inilah kaedah kaedah countElements berfungsi:

  • Pertama, kami memulakan array C. Oleh kerana julat [0, k] mengandungi nombor k + 1 , kami membuat susunan yang mampu mengandungi nombor k + 1
  • Kemudian untuk setiap nombor dalam input, kami mengira kekerapan nombor tersebut
  • Dan akhirnya, kami menambahkan elemen berturut-turut untuk mengetahui berapa banyak elemen yang kurang daripada atau sama dengan nombor tertentu

Kami juga dapat mengesahkan bahawa kaedah countElements berfungsi seperti yang diharapkan:

@Test void countElements_GivenAnArray_ShouldCalculateTheFrequencyArrayAsExpected() { int k = 5; int[] input = { 4, 3, 2, 5, 4, 3, 5, 1, 0, 2, 5 }; int[] c = CountingSort.countElements(input, k); int[] expected = { 1, 2, 4, 6, 8, 11 }; assertArrayEquals(expected, c); }

3.2. Menyusun Susunan Input

Sekarang kita dapat mengira array frekuensi, kita seharusnya dapat menyusun set nombor yang diberikan:

int[] sort(int[] input, int k) { int[] c = countElements(input, k); int[] sorted = new int[input.length]; for (int i = input.length - 1; i >= 0; i--) { int current = input[i]; sorted[c[current] - 1] = current; c[current] -= 1; } return sorted; }

Berikut ialah cara yang semacam Cara berfungsi:

  • Pertama, ia mengira array C
  • Then, it iterates the input array in reverse and for each element in the input, finds its correct spot in the sorted array. The ith element in the input should be the C[i]th element in the sorted array. Since Java arrays are zero-indexed, the C[i]-1 entry is the C[i]th element – for example, sorted[5] is the sixth element in the sorted array
  • Each time we find a match, it decrements the corresponding C[i] value

Similarly, we can verify that the sort method works as expected:

@Test void sort_GivenAnArray_ShouldSortTheInputAsExpected() { int k = 5; int[] input = { 4, 3, 2, 5, 4, 3, 5, 1, 0, 2, 5 }; int[] sorted = CountingSort.sort(input, k); // Our sorting algorithm and Java's should return the same result Arrays.sort(input); assertArrayEquals(input, sorted); }

4. Revisiting the Counting Sort Algorithm

4.1. Complexity Analysis

Most classic sorting algorithms, like merge sort, sort any given input by just comparing the input elements to each other. These type of sorting algorithms are known as comparison sorts. In the worst case, comparison sorts should take at least O(n log n) to sort n elements.

Counting Sort, on the other hand, does not sort the input by comparing the input elements, so it's clearly not a comparison sort algorithm.

Let's see how much time it consumes to sort the input:

  • It computes the C array in O(n+k) time: It once iterates an input array with size n in O(n) and then iterates the C in O(k) – so that would be O(n+k) in total
  • After computing the C, it sorts the input by iterating the input array and performing a few primitive operations in each iteration. So, the actual sort operation takes O(n)

In total, counting sort takes O(n+k) time to run:

O(n + k) + O(n) = O(2n + k) = O(n + k)

If we assume k=O(n), then counting sort algorithm sorts the input in linear time. As opposed to general-purpose sorting algorithms, counting sorts makes an assumption about the input and takes less than the O(n log n) lower bound to execute.

4.2. Stability

A few moments ago, we laid a few peculiar rules about the mechanics of counting sort but never cleared the reason behind them. To be more specific:

  • Why should we iterate the input array in reverse?
  • Why we decrement the C[i] each time we're using it?

Let's iterate from the beginning to better understand the first rule. Suppose we're going to sort a simple array of integers like the following:

In the first iteration, we should find the sorted location for the first 1:

So the first occurrence of number 1 gets the last index in the sorted array. Skipping the number 0, let's see what happens to the second occurrence of number 1:

The appearance order of elements with the same value is different in the input and sorted array, so the algorithm is not stable when we're iterating from the beginning.

What happens if we don't decrement the C[i] value after each use? Let's see:

Both occurrences of number 1 are getting the last place in the sorted array. So if we don't decrement the C[i] value after each use, we could potentially lose a few numbers while sorting them!

5. Conclusion

Dalam tutorial ini, pertama, kami belajar bagaimana Counting Sort berfungsi secara dalaman. Kemudian kami menerapkan algoritma penyortiran ini di Java dan menulis beberapa ujian untuk mengesahkan kelakuannya. Dan akhirnya, kami membuktikan bahawa algoritma adalah algoritma penyusun stabil dengan kerumitan masa linear.

Seperti biasa, contoh kod tersedia di projek GitHub kami, jadi pastikan untuk memeriksanya!