Bagaimana Mencari Elemen Terbesar Kth di Jawa

1. Pengenalan

Dalam artikel ini, kami akan membentangkan pelbagai penyelesaian untuk mencari k ke-elemen terbesar dalam urutan nombor yang unik. Kami akan menggunakan sebilangan bilangan bulat untuk contoh kami.

Kami juga akan membincangkan kerumitan masa dan keadaan terburuk setiap algoritma.

2. Penyelesaian

Sekarang mari kita terokai beberapa penyelesaian yang mungkin - satu menggunakan jenis biasa, dan dua menggunakan algoritma Quick Select yang berasal dari Quick Sort.

2.1. Menyusun

Apabila kita memikirkan masalahnya, mungkin penyelesaian paling jelas yang terlintas di fikiran adalah menyusun susunannya .

Mari tentukan langkah-langkah yang diperlukan:

  • Susun susunan mengikut urutan menaik
  • Oleh kerana elemen terakhir array akan menjadi elemen terbesar, elemen terbesar ke- k berada pada indeks xth , di mana x = panjang (susunan) - k

Seperti yang kita lihat, penyelesaiannya mudah tetapi memerlukan penyusunan keseluruhan susunan. Oleh itu, kerumitan masa akan menjadi O (n * logn) :

public int findKthLargestBySorting(Integer[] arr, int k) { Arrays.sort(arr); int targetIndex = arr.length - k; return arr[targetIndex]; }

Pendekatan alternatif adalah menyusun susunan dalam urutan menurun dan hanya mengembalikan elemen pada (k-1) indeks:

public int findKthLargestBySortingDesc(Integer[] arr, int k) { Arrays.sort(arr, Collections.reverseOrder()); return arr[k-1]; }

2.2. Pilih Pantas

Ini boleh dianggap sebagai pengoptimuman pendekatan sebelumnya. Dalam ini, kami memilih QuickSort untuk disusun. Menganalisis pernyataan masalah, kita sedar bahawa kita tidak benar-benar perlu untuk menyelesaikan pelbagai keseluruhan - kita hanya perlu untuk menyusun semula kandungannya supaya k ke-elemen array adalah k terbesar th atau kecil.

Di QuickSort, kami memilih elemen pangsi dan memindahkannya ke kedudukan yang betul. Kami juga membahagikan susunan di sekelilingnya. Dalam QuickSelect, idea ini adalah untuk berhenti pada titik di mana pivot itu sendiri adalah k ke-unsur terbesar.

Kami dapat mengoptimumkan algoritma lebih jauh jika kami tidak berulang untuk kedua-dua sisi kiri dan kanan pangsi. Kita hanya perlu mengulang salah satu daripadanya mengikut kedudukan pangsi.

Mari lihat idea asas algoritma QuickSelect:

  • Pilih elemen pangsi dan bahagikan susunan dengan sewajarnya
    • Pilih elemen paling kanan sebagai pangsi
    • Rombakan susunan sedemikian rupa sehingga elemen pangsi diletakkan di tempat yang betul - semua elemen yang kurang daripada pangsi berada pada indeks yang lebih rendah, dan elemen yang lebih besar daripada pangsi akan ditempatkan pada indeks yang lebih tinggi daripada pangsi
  • Sekiranya pivot diletakkan pada elemen k ke dalam array, keluar dari proses, kerana pivot adalah elemen terbesar ke- k
  • Sekiranya kedudukan pangsi lebih besar daripada k, maka teruskan prosesnya dengan subarray kiri, jika tidak, lakukan semula proses dengan subarray kanan

Kita boleh menulis logik generik yang boleh digunakan untuk mencari elemen terkecil k juga. Kami akan menentukan kaedah findKthElementByQuickSelect () yang akan mengembalikan elemen k dalam larik yang disusun.

Sekiranya kita menyusun susunan dalam urutan menaik, elemen k dari array akan menjadi elemen terkecil k . Untuk mencari unsur k terbesar, kita dapat melewati k = panjang (Array) - k.

Mari laksanakan penyelesaian ini:

public int findKthElementByQuickSelect(Integer[] arr, int left, int right, int k) { if (k >= 0 && k  k) { return findKthElementByQuickSelect(arr, left, pos - 1, k); } return findKthElementByQuickSelect(arr, pos + 1, right, k - pos + left - 1); } return 0; }

Sekarang mari kita laksanakan kaedah partisi , yang memilih elemen paling kanan sebagai pangsi, meletakkannya pada indeks yang sesuai, dan membahagi susunan sedemikian rupa sehingga elemen pada indeks yang lebih rendah harus kurang daripada elemen pangsi.

Begitu juga, elemen pada indeks yang lebih tinggi akan lebih besar daripada elemen pangsi:

public int partition(Integer[] arr, int left, int right) { int pivot = arr[right]; Integer[] leftArr; Integer[] rightArr; leftArr = IntStream.range(left, right) .filter(i -> arr[i]  arr[i]) .boxed() .toArray(Integer[]::new); rightArr = IntStream.range(left, right) .filter(i -> arr[i] > pivot) .map(i -> arr[i]) .boxed() .toArray(Integer[]::new); int leftArraySize = leftArr.length; System.arraycopy(leftArr, 0, arr, left, leftArraySize); arr[leftArraySize+left] = pivot; System.arraycopy(rightArr, 0, arr, left + leftArraySize + 1, rightArr.length); return left + leftArraySize; }

Terdapat pendekatan yang lebih mudah dan berulang untuk mencapai pemisahan:

public int partitionIterative(Integer[] arr, int left, int right) { int pivot = arr[right], i = left; for (int j = left; j <= right - 1; j++) { if (arr[j] <= pivot) { swap(arr, i, j); i++; } } swap(arr, i, right); return i; } public void swap(Integer[] arr, int n1, int n2) { int temp = arr[n2]; arr[n2] = arr[n1]; arr[n1] = temp; }

Penyelesaian ini berfungsi dalam masa O (n) secara purata. Walau bagaimanapun, dalam keadaan terburuk, kerumitan masa akan menjadi O (n ^ 2) .

2.3. Pilih Pantas Dengan Partisi Rawak

Pendekatan ini adalah sedikit pengubahsuaian dari pendekatan sebelumnya. Sekiranya susunan disusun hampir / sepenuhnya dan jika kita memilih elemen paling kanan sebagai pangsi, partisi subarrays kiri dan kanan akan sangat tidak rata.

Kaedah ini mencadangkan memilih elemen pangsi awal secara rawak. Kita tidak perlu mengubah logik partition.

Daripada memanggil partition , kita memanggil kaedah randomPartition , yang memilih elemen rawak dan menukarnya dengan elemen paling kanan sebelum akhirnya menggunakan kaedah partition .

Mari kita laksanakan kaedah randomPartition :

public int randomPartition(Integer arr[], int left, int right) { int n = right - left + 1; int pivot = (int) (Math.random()) * n; swap(arr, left + pivot, right); return partition(arr, left, right); }

Penyelesaian ini berfungsi lebih baik daripada kes sebelumnya dalam kebanyakan kes.

Kerumitan masa yang diharapkan dari QuickSelect secara rawak adalah O (n) .

Walau bagaimanapun, kerumitan masa terburuk masih kekal O (n ^ 2) .

3. Kesimpulannya

Dalam artikel ini, kami membincangkan penyelesaian yang berbeza untuk mencari elemen k (terbesar atau terkecil) dalam susunan nombor unik. Penyelesaian paling mudah adalah menyusun susunan dan mengembalikan elemen k . Penyelesaian ini mempunyai kerumitan masa O (n * logn) .

Kami juga membincangkan dua variasi Quick Select. Algoritma ini tidak mudah tetapi mempunyai kerumitan masa O (n) dalam kes biasa.

Seperti biasa, kod lengkap untuk algoritma boleh didapati di GitHub.