算法导论实践——选择第i大的数

算法的魅力就在于,明明感觉思路很简单,想要实现却要花上一阵不小的功夫,今天给大家表演的是,第i大数的选取,可以利用这个函数进行中位数的选择。

理论

啰嗦一句“第i大的数选取”的背景,直接扣PPT了,研究来研究去,最后还是为了降低时间复杂度,让我们来看看它是怎么降低时间复杂度的把
在这里插入图片描述

一些约定

  • 数组长度用n表示
  • 算法定义为Select_i_th_Data(A, p, q, k)
    • A是输入数组
    • p是起始下标
    • q是结束下标
    • k表示选择A[p:q]中第k大的数
  • 不会显式地写取整符号,意思到了即可

正式开始

将数组分组,按照每五个一组划分

可以划分为[n/5](向上取整👆)组
在这里插入图片描述

找出每一组的中位数。

原理是排序+简单计算,这里因为数据基数小,只有5个数据,因此无论用什么排序,排序时间都是O(1)。这里假设用插入排序。当然,这里有一个技巧:就是,由于后面会利用到这些中位数,因此,我们直接会将这些中位数交换到数组的最前面
在这里插入图片描述

选择中位数中的中位数(MoM)。

这里可以再次调用我们的算法Select_i_th_Data,去选择MoM。Select_i_th_Data(A,0,n/5,10/n)
在这里插入图片描述

利用选择的MoM划分

将比MoM小的分到左边,比MoM大的分到右边,获取MoM的下标indexOfMoM,并同时获取MoM的名词rankOfMoM,这里用到的技巧是快排里的划分技巧
在这里插入图片描述

递归搜索即可

比较rankOfMoM和k,递归搜索即可

  • rankOfMoM == k, 则MoM是第k大的数
  • rankOfMoM > k,则在大于MoM的部分递归调用函数
  • rankOfMoM < k,则在小于MoM的部分递归调用函数

复杂度分析

事实上,数组划分求中位数部分的时间复杂度为O(n),MoM的搜索复杂度为T(n/5),再来,根据下图有,因此最后的递归部分时间复杂度不超过T(7n/10+6)
在这里插入图片描述
综上所述:
在这里插入图片描述

实践

BigDataMedianUtil.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace BigInteger
{
    public static class BigDataMedianUtil
    {

        //在数组A[p:q]中找到第k大的数
        public static int Select_i_th_Data(int[] A, int p, int q, int k)
        {
            //初值条件,如果A的有效长度小于等于5,则直接返回中位数
            //默认若是偶数,则返回前一个数;若是奇数,则返回中间的数;
            if (q - p <= 4)
            {
                InsertSort(A, p, q);
                return A[p + k - 1];
            }

            int effectiveLength = q - p + 1;//选取A[p:q]部分的长度
            int effectiveEndIndex = p + effectiveLength / 5 - 1;//选取A[p:q]能够被5划分的末尾下标

            //以五个为一组,挑选中位数,把中位数放到数组的前A[p:effectiveEndIndex]部分,方便后面再次调用
            //注意:挑选中位数时,不需要多余的部分,因此直接effectiveLength / 5即可;
            for (int i = 0; i < effectiveLength / 5; i++)
            {
                InsertSort(A, i * 5 + p, i * 5 + 4 + p);
                Swap(A, i + p, i * 5 + 2 + p);
            }

            //对数组的前A[p:effectiveEndIndex]部分,也就是所有组的中位数递归调用Select_i_th_Data,
            //这次需要选出中位数的中位数,因此,k = effectiveLength / 10(向上取整)
            int MoM = Select_i_th_Data(A, p, effectiveEndIndex, (effectiveLength / 10) % 2 == 0 ? (effectiveLength / 10) : (effectiveLength / 10 + 1));

            //利用MoM对A[p:q]部分进行划分,划分过程类似快速排序的划分过程,时间复杂度是O(n)
            //注意:这里进行划分时,需要把多余的部分(不是Partition(A,p,effectiveEndIndex,MoM))基于MoM一起划分,不然就会缺少元素。
            int indexOfMoM = Partition(A, p, q, MoM);//得到MoM的绝对下标
            int rankOfMoM = indexOfMoM + 1 - p;//得到相对A[p:q]来说,第rankOfMoM大的数
            
            //分解+解决,不用合并

            //如果MoM刚好是A[p:q]中第k大的数,则返回MoM;
            if (k == rankOfMoM) return MoM;

            else if (k < rankOfMoM)
            {
                //在数组A[p:indexOfMoM - 1]中找到第k大的数
                return Select_i_th_Data(A, p, indexOfMoM - 1, k);
            }

            else
            {
                //在数组A[indexOfMoM + 1, q]中找到第k - indexOfMoM - 1大的数
                return Select_i_th_Data(A, indexOfMoM + 1, q, k - indexOfMoM - 1);
            }
        }


        public static int Partition(int[] A, int p, int q, int Value)
        {
            int i = p - 1;        
            int key = Value;
            for(int j = p; j < q; j++)
            {
                if(A[j] < key)
                {
                    i++;
                    Swap(A, i, j);
                }
            }
            Swap(A, FindIndexByValue(A, key), i + 1);
            return i + 1;
        }

        public static void Swap(int[] A,int p, int q)
        {
            int mid = A[p];
            A[p] = A[q];
            A[q] = mid;
        }

        public static void InsertSort(int[] A, int p, int q)
        {

            for(int i = p; i <= q - 1; i++)
            {
                int j = i + 1;
                int key = A[j];
                while (key < A[j - 1])
                {
                    A[j] = A[j - 1];
                    j--;
                    if(j == p)
                    {
                        break;
                    }
                }
                A[j] = key;
            }
        }

        public static void GetResult(int[] A)
        {
            for (int i = 0; i < A.Length; i++)
            {
                Console.Write(A[i]);
                Console.Write(" ");
            }
            Console.WriteLine(" ");
        }

        public static int FindIndexByValue(int[] A,int a)
        {
            for(int i = 0; i < A.Length; i++)
            {
                if (A[i] == a)
                {
                    return i;
                }
            }
            return -1;
        }
    }
}

Program.cs

using System;
using BigInteger;

namespace BigInteger
{
    class Program
    {
        static void Main(string[] args)
        {

            int[] A = { 11, 12, 14, 13, 15, 5, 6, 7, 8, 9, 1, 2, 3, 4, 10, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 };
            int Element = BigDataMedianUtil.Select_i_th_Data(A, 0, A.Length - 1, A.Length/2);
            Console.WriteLine(Element);
            
        }
    }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值