📜  Floyd-Rivest 算法

📅  最后修改于: 2021-09-16 11:10:15             🧑  作者: Mango

Floyd-Rivest 算法是一种选择算法,用于在不同元素的数组中找到第k最小元素。它类似于 QuickSelect 算法,但在实践中具有更好的运行时间。
与 QuickSelect 一样,该算法基于分区的思想。对数组进行分区后,分区元素会在更正的排序位置结束。如果数组具有所有不同的元素,则检索第 (k+1)最小元素与检索排序后的(k+1)元素相同。因为完全排序是昂贵的(需要O(N log N)来计算),Floyd-Rivest 算法利用分区在O(N)时间内完成相同的任务。

算法:

  1. 如果所考虑的数组 S 的大小足够小,则直接应用 QuickSelect 算法来获取第 K 个最小元素。这个大小是算法的一个任意常数,作者选择为600
  2. 否则,选择 2 个枢轴 – newLeftIndex 和 newRightIndex 使用随机采样,以便它们包含第 K 个最大元素的概率最高。然后,该函数被递归调用,数组的左右边界现在设置为 newLeftIndex 和 newRightIndex。
  3. 与 QuickSelect 一样,在对子数组进行分区后,需要设置左右边界,使其包含 K 最大元素。
    围绕 K 对数组进行分区后,第 K 个元素处于正确的排序位置。因此,左右边界的设置方式使得所考虑的子数组包含 array[k]

下面是上述方法的实现。

C++
// C++ implementation of the above approach.
#include 
#include 
using namespace std;
 
// Function to return the
// sign of a number
int sign(double x)
{
    if (x < 0)
        return -1;
    if (x > 0)
        return 1;
    return 0;
}
 
// Function to swap
// two numbers in an array.
void swap(int arr[], int i, int j)
{
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
 
int select(int arr[], int left,
           int right, int k)
{
    while (right > left) {
        if (right - left > 600) {
            // Choosing a small subarray
            // S based on sampling.
            // 600, 0.5 and 0.5
            // are arbitrary constants
            int n = right - left + 1;
            int i = k - left + 1;
            double z = log(n);
            double s = 0.5 * exp(2 * z / 3);
            double sd = 0.5 * sqrt(z * s
                                   * (n - s) / n)
                        * sign(i - n / 2);
 
            int newLeft = max(left,
                              (int)(k - i * s / n + sd));
 
            int newRight = min(right,
                               (int)(k + (n - i) * s / n
                                     + sd));
 
            select(arr, newLeft, newRight, k);
        }
 
        // Partition the subarray S[left..right]
        // with arr[k] as pivot
        int t = arr[k];
        int i = left;
        int j = right;
        swap(arr, left, k);
        if (arr[right] > t) {
            swap(arr, left, right);
        }
 
        while (i < j) {
            swap(arr, i, j);
            i++;
            j--;
 
            while (arr[i] < t)
                i++;
            while (arr[j] > t)
                j--;
        }
 
        if (arr[left] == t)
            swap(arr, left, j);
        else {
            j++;
            swap(arr, right, j);
        }
 
        // Adjust the left and right pointers
        // to select the subarray having k
        if (j <= k)
            left = j + 1;
        if (k <= j)
            right = j - 1;
    }
    return arr[k];
}
 
// Driver code
int main()
{
    int arr[] = { 7, 3, 4, 0, 1, 6 };
    int n = sizeof(arr) / sizeof(int);
 
    // k-th smallest element.
    // In this we get the 2nd smallest element
    int k = 2;
    int res = select(arr, 0, n - 1, k - 1);
    cout << "The " << k << "-th smallest element is "
         << res << endl;
    return 0;
}


Java
// Java implementation of the above approach.
class GFG {
 
    // Function to return
    // the sign of the number
    int sign(double x)
    {
        if (x < 0)
            return -1;
        if (x > 0)
            return 1;
        return 0;
    }
 
    // Function to swap two numbers in an array
    void swap(int arr[], int i, int j)
    {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
 
    // Function to return kth smallest number
    int select(int arr[], int left,
               int right, int k)
    {
        while (right > left) {
            if (right - left > 600) {
                // Choosing a small subarray
                // S based on sampling.
                // 600, 0.5 and 0.5 are
                // arbitrary constants
                int n = right - left + 1;
                int i = k - left + 1;
                double z = Math.log(n);
                double s = 0.5 * Math.exp(2 * z / 3);
 
                double sd = 0.5 * Math.sqrt(z * s * (n - s) / n)
                            * sign(i - n / 2);
 
                int newLeft = Math.max(left,
                                       (int)(k - i * s / n
                                             + sd));
 
                int newRight = Math.min(right,
                                        (int)(k + (n - i) * s / n
                                              + sd));
 
                select(arr, newLeft, newRight, k);
            }
 
            // Partition the subarray S[left..right]
            // with arr[k] as pivot
            int t = arr[k];
            int i = left;
            int j = right;
            swap(arr, left, k);
            if (arr[right] > t) {
                swap(arr, left, right);
            }
 
            while (i < j) {
                swap(arr, i, j);
                i++;
                j--;
 
                while (arr[i] < t)
                    i++;
                while (arr[j] > t)
                    j--;
            }
 
            if (arr[left] == t)
                swap(arr, left, j);
            else {
                j++;
                swap(arr, right, j);
            }
 
            // Adjust the left and right
            // pointers to select the subarray having k
            if (j <= k)
                left = j + 1;
            if (k <= j)
                right = j - 1;
        }
        return arr[k];
    }
 
    // Driver code
    public static void main(String[] args)
    {
        int[] arr = new int[] { 7, 3, 4, 0, 1, 6 };
 
        // k-th smallest element.
        // In this we get the 2nd smallest element
        int k = 2;
        FloydRivest f = new FloydRivest();
        int res = f.select(arr, 0, arr.length - 1, k - 1);
        System.out.println("The " + k
                           + "-th smallest element is " + res);
    }
}


Python3
# Python implementation of the above approach.
import math
import random
 
# Function to return the
# sign of the number
def sign(x):
    if x>0:
        return 1
    elif x<0:
        return -1
    return 0
 
# Function to swap two
# numbers in an array
def swap(arr, i, j):
    temp = arr[i]
    arr[i] = arr[j]
    arr[j] = temp
 
# Function to return kth smallest number
def select(arr: list, left: int,
right: int, k: int):
    while right>left:
 
        # Choosing a small subarray
        # S based on sampling.
        # 600, 0.5 and 0.5 are
        # arbitrary constants
        if right-left > 600:
            n = right - left + 1
            i = k - left + 1
            z = math.log(n)
            s = 0.5 * math.exp(2 * z / 3)
            sd = 0.5 * math.sqrt(z * s * (n-s)/n) * sign(i-n / 2)
            newLeft = int(max(left, k-i * s / n + sd))
            newRight = int(min(right, k + (n - i) * s / n + sd))
            select(arr, newLeft, newRight, k)
        t = arr[k]
        i = left
        j = right
        swap(arr, left, k)
        if arr[right] > t:
            swap(arr, left, right)
        while it:
                j = j-1
 
        if arr[left] == t:
            swap(arr, left, j)
        else:
            j = j + 1
            swap(arr, right, j)
 
        # Updating the left and right indices
        # depending on position of k-th element
        if j<= k:
            left = j + 1
        if k<= j:
            right = j-1
    return arr[k]
 
 
arr = [7, 3, 4, 0, 1, 6]
# k-th smallest element.
# In this the 2nd smallest element is returned.
k = 2
res = select(arr, 0, len(arr)-1, k-1)
print('The {}-th smallest element is {}'.format(k, res))


C#
// C# implementation of the above approach.
using System;
 
class GFG
{
 
    // Function to return
    // the sign of the number
    static int sign(double x)
    {
        if (x < 0)
            return -1;
        if (x > 0)
            return 1;
        return 0;
    }
 
    // Function to swap two numbers in an array
    static void swap(int []arr, int i, int j)
    {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
 
    // Function to return kth smallest number
    static int select(int []arr, int left,
            int right, int k)
    {
        int i;
        while (right > left)
        {
            if (right - left > 600)
            {
                // Choosing a small subarray
                // S based on sampling.
                // 600, 0.5 and 0.5 are
                // arbitrary constants
                int n = right - left + 1;
                i = k - left + 1;
                double z = Math.Log(n);
                double s = 0.5 * Math.Exp(2 * z / 3);
 
                double sd = 0.5 * Math.Sqrt(z * s * (n - s) / n)
                            * sign(i - n / 2);
 
                int newLeft = Math.Max(left,
                                    (int)(k - i * s / n
                                            + sd));
 
                int newRight = Math.Min(right,
                                        (int)(k + (n - i) * s / n
                                            + sd));
 
                select(arr, newLeft, newRight, k);
            }
 
            // Partition the subarray S[left..right]
            // with arr[k] as pivot
            int t = arr[k];
            i = left;
            int j = right;
            swap(arr, left, k);
            if (arr[right] > t)
            {
                swap(arr, left, right);
            }
 
            while (i < j)
            {
                swap(arr, i, j);
                i++;
                j--;
 
                while (arr[i] < t)
                    i++;
                while (arr[j] > t)
                    j--;
            }
 
            if (arr[left] == t)
                swap(arr, left, j);
            else
            {
                j++;
                swap(arr, right, j);
            }
 
            // Adjust the left and right
            // pointers to select the subarray having k
            if (j <= k)
                left = j + 1;
            if (k <= j)
                right = j - 1;
        }
        return arr[k];
    }
 
    // Driver code
    public static void Main()
    {
        int[] arr = { 7, 3, 4, 0, 1, 6 };
 
        // k-th smallest element.
        // In this we get the 2nd smallest element
        int k = 2;
         
        int res = select(arr, 0, arr.Length - 1, k - 1);
        Console.WriteLine("The " + k + "-th smallest element is " + res);
    }
}
 
// This code is contributed by AnkitRai01


Javascript


输出:

The 2-th smallest element is 1

时间复杂度:O(N)

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程