📜  使用最小比较的第二个最小元素

📅  最后修改于: 2021-04-17 11:56:40             🧑  作者: Mango

给定一个整数数组,在小于2n的比较中找到最小(或最大)元素,并且该元素大于(或小于)该元素。给定的数组不一定要排序。允许额外的空间。

例子:

Input: {3, 6, 100, 9, 10, 12, 7, -1, 10}
Output: Minimum: -1, Second minimum: 3

我们已经在下面的文章中讨论了一种方法。
查找数组中的最小和第二个最小元素

如果数组元素的大小较大(例如,大字符串),则比较数组元素可能会非常昂贵。我们可以最小化上述方法中使用的比较次数。

这个想法是基于锦标赛树。将给定数组中的每个元素视为叶节点。

比赛树

首先,我们找到最低限度的元素,如建造锦标赛三通所说明的那样。为了构建树,我们将所有相邻的元素对(叶节点)彼此进行比较。现在我们有n / 2个元素,它们比它们的对应元素要小(从每对元素中,较小的元素形成的级别高于叶子的级别)。同样,在n / 4对中的每一个中找到较小的元素。继续此过程,直到创建树的根为止。根是最小值。
接下来,我们遍历树,并在此过程中丢弃根大于最小元素的子树。在丢弃之前,我们更新第二个最小的元素,它将保留我们的结果。这里要理解的是树是高度平衡的,我们只需要花费logn时间来遍历所有与步骤1中的最小值进行比较的元素。因此,总的时间复杂度为n + logn 。辅助空间为O(2n),因为叶节点的数量将近似等于内部节点的数量。

// C++ program to find minimum and second minimum
// using minimum number of comparisons
#include 
using namespace std;
  
// Tournament Tree node
struct Node
{
    int idx;
    Node *left, *right;
};
  
// Utility function to create a tournament tree node
Node *createNode(int idx)
{
    Node *t = new Node;
    t->left = t->right = NULL;
    t->idx = idx;
    return t;
}
  
// This function traverses tree across height to
// find second smallest element in tournament tree.
// Note that root is smallest element of tournament
// tree.
void traverseHeight(Node *root, int arr[], int &res)
{
    // Base case
    if (root == NULL || (root->left == NULL &&
                         root->right == NULL))
        return;
  
    // If left child is smaller than current result,
    // update result and recur for left subarray.
    if (res > arr[root->left->idx] &&
       root->left->idx != root->idx)
    {
        res = arr[root->left->idx];
        traverseHeight(root->right, arr, res);
    }
  
    // If right child is smaller than current result,
    // update result and recur for left subarray.
    else if (res > arr[root->right->idx] &&
             root->right->idx != root->idx)
    {
        res = arr[root->right->idx];
        traverseHeight(root->left, arr, res);
    }
}
  
// Prints minimum and second minimum in arr[0..n-1]
void findSecondMin(int arr[], int n)
{
    // Create a list to store nodes of current
    // level
    list li;
  
    Node *root = NULL;
    for (int i = 0; i < n; i += 2)
    {
        Node *t1 = createNode(i);
        Node *t2 = NULL;
        if (i + 1 < n)
        {
            // Make a node for next element
            t2 = createNode(i + 1);
  
            // Make smaller of two as root
            root = (arr[i] < arr[i + 1])? createNode(i) :
                                       createNode(i + 1);
  
            // Make two nodes as children of smaller
            root->left = t1;
            root->right = t2;
  
            // Add root
            li.push_back(root);
        }
        else
            li.push_back(t1);
    }
  
    int lsize = li.size();
  
    // Construct the complete tournament tree from above
    // prepared list of winners in first round.
    while (lsize != 1)
    {
        // Find index of last pair
        int last = (lsize & 1)? (lsize - 2) : (lsize - 1);
  
        // Process current list items in pair
        for (int i = 0; i < last; i += 2)
        {
            // Extract two nodes from list, make a new
            // node for winner of two
            Node *f1 = li.front();
            li.pop_front();
  
            Node *f2 = li.front();
            li.pop_front();
            root = (arr[f1->idx] < arr[f2->idx])?
                createNode(f1->idx) : createNode(f2->idx);
  
            // Make winner as parent of two
            root->left = f1;
            root->right = f2;
  
            // Add winner to list of next level
            li.push_back(root);
        }
        if (lsize & 1)
        {
            li.push_back(li.front());
            li.pop_front();
        }
        lsize = li.size();
    }
  
    // Traverse tree from root to find second minimum
    // Note that minimum is already known and root of
    // tournament tree.
    int res = INT_MAX;
    traverseHeight(root, arr, res);
    cout << "Minimum: " << arr[root->idx]
        << ", Second minimum: " << res << endl;
}
  
// Driver code
int main()
{
    int arr[] = {61, 6, 100, 9, 10, 12, 17};
    int n = sizeof(arr)/sizeof(arr[0]);
    findSecondMin(arr, n);
    return 0;
}

输出:

Minimum: 6, Second minimum: 9

我们可以通过避免创建叶节点来优化上述代码,因为该信息存储在数组本身中(并且我们知道元素是成对比较的)。