📜  Floyd-Rivest算法

📅  最后修改于: 2021-04-26 09:12:57             🧑  作者: 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


输出:
The 2-th smallest element is 1

时间复杂度:O(N)

参考:
维基百科-弗洛伊德·里维斯特
关于Floyd和Rivest的SELECT算法
维基百科-快速选择