算法基础-顺序统计量

前往我的主页以获得更好的阅读体验算法基础-顺序统计量 - DearXuan的主页icon-default.png?t=M276https://blog.dearxuan.com/2022/01/30/%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80-%E9%A1%BA%E5%BA%8F%E7%BB%9F%E8%AE%A1%E9%87%8F/

顺序统计量

将长度为 n 的数组升序排序后,则第 i 个位置的数字是该数组的第 i 小的量,称之为第 i 顺序统计量

数组最小值是第1个顺序统计量,最大值是第n个顺序统计量,中位数(又称下中位数)是第⌊(n+1)/2⌋个顺序统计量

⌊n⌋ 表示对 n 向下取整,⌈n⌉表示对 n 向上取整

最大值和最小值

若想要寻找n个数字里的最大值或最小值,只需要进行(n-1)次比较

int min = a[0];
for(int i=1;i<n;i++){
    if(a[i] > min)
        min = a[i];
}

显然这已经是最优的算法了,我们称他为“遍历查找”,因为该算法是简单地遍历了整个数组来寻找最大或最小值,总共需要(n-1)次比较,即S(n)=n-1

现在我们要研究如何以尽可能低的时间复杂度来同时求出数组的最大值和最小值

传统方法

最容易想到的方法就是重复两次“遍历查找”,分别找出最大和最小值,那么就需要S(n)=2(n-1)次比较,但是这显然不是最佳的方案。

设存在数组A=[9,0,1,2,100]

在寻找最小值时,当遍历到第2个元素时,由于0<9,所以最小值被替换成0,同时我们也可以得知0一定不是最大值,因为有个9比它更大。

在寻找最大值时,采用了相同的算法,导致0又被比较了一遍,而现在0不可能是最大值。

优化算法

通过上面的传统方法,我们可以发现减少比较次数的关键是减少不必要的比较,这就给我们一个思路,将一个数组划分为 k 段,找出这 k 个数的最大最小值,然后分别和整个数组的最大最小值比较

设查找长度为 n 的数组中的最大最小值,需要比较至多 f(n) 次,数组被划分为 n/k 段,每段 k 个数字,每段分别需要比较 f(k) 次就可以得到最大最小值,则共比较就可以得到 n/k 个最大值数组和最小值数组,在查找整个数组的最小值时只需要从最小值数组里寻找就行了,而查找整个数组的最大最小值又需要比较次,于是得到 f(n) 的函数

同理对于长度为k的数组,我们又可以将其分为更小的段,一直分下去,直到 k = 2 时,f(2)=1,因为两个数只需要比较一次就能得到最大最小值,我们假设将长度为n的数组划分为长度为k1的数段,将长度为k(i-1)的数段划分为长度为ki的更小的数段,带入 f(n),得

 由于 kx 一定是正数,因此 i 为 1 时,右边的连加消失,f(n)取到最小值,即k1=2

得到上述结果的前提是事先定义好了min和max的初值,但是实际应用中我们可以根据数组动态调整初值,如果长度为偶数,则先比较前两项,大的作为max,小的作为min,共比较(3n/2-1)次;如果长度为奇数,则min和max都取第一项,因此实际的比较次数应该是(3n/2-2)次,即最终比较次数应该是

 通过理论我们得知了只要把数组中的数两两比较,其中的较小者和最小值比较,而较大者和最大值比较,就可以实现用最少的比较次数同时求出最大最小值

void FindMinAndMax(int* a, int len){
    int min, max, i;
    if(len % 2 == 0){
        //当长度为偶数时,将前两项之间的较大者设为最大值,较小者设为最小值
        if(a[0] < a[1]){
            min = a[0];
            max = a[1];
        }else{
            min = a[1];
            max = a[0];
        }
        //从a[2]和a[3]开始比较,i为3
        i = 3;
    }else{
        //当长度为基数时,将最大值和最小值都设为a[0]
        min = max = a[0];
        //从a[1]和a[2]开始比较,i为2
        i = 2;
    }
    while (i < len){
        if(a[i-1] < a[i]){
            if(a[i-1]<min) min = a[i-1];
            if(a[i]>max) max = a[i];
        }else{
            if(a[i]<min) min = a[i];
            if(a[i-1]>max) max = a[i-1];
        }
        i += 2;
    }
    cout << "min:" << min << endl;
    cout << "max:" << max << endl;
}

第i顺序统计量

如果想要找到数组里的第 i 顺序统计量,也就是第 i 小的数字,通常的办法是把整个数组排序,然后直接取出对应位置的数字。快速排序是一个很好的办法,快速排序将数组划分为两个子数组来排序。实际上,我们只关心其中某一个位置的数字,因此对于快速排序划分出来的两个子数组,我们仅需要排序其中一个就行了

int find(int *a, int left, int right, int k) {
    if (left >= right) {
        return a[left];
    }
    //key为选定的基准值
    int i = left, j = right, key = a[left];
    while (i < j) {
        //遍历数组找到第一个比key大的数
        while (a[i] < key) {
            ++i;
        }
        //遍历数组找到第一个比key小的数
        while (a[j] > key) {
            --j;
        }
        //交换,此时比key小的在左边,比key大的在右边
        if (i < j) {
            int temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
    }
    //此时基准值在整个数组的第i个位置
    //s是基准值在以left开始的数组里的位置,i是基准值的位置
    int s = i - left + 1;
    if (s == k) {//如果s==k,说明基准值就是要查找的数,则返回基准值
        return a[i];
    } else if (s > k) {//如果s>k,说明基准值在要查找的数的右边,则遍历左边数组
        return find(a, left, j, k);
    } else {//如果s<k,说明基准值在要查找的数的左边,则遍历右边数组
        return find(a, j + 1, right, k - s);
    }
}

该方法的比较次数受到随机因素的影响,因此是一个随机算法。在平均情况下,它的时间复杂度能够达到O(n)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: K-Means算法是一种非监督学习的聚类算法,它可以帮助我们将数据集划分成不同的类簇。要实现改良的K-Means算法,可以采用两种方法:一种是改善K-Means算法的聚类算法,另一种是改善K-Means算法的收敛速度。为了改善K-Means算法的聚类效果,可以采用改进的距离度方法,例如使用更加适合数据集的Minkowski距离;另外,可以引入核技巧来改善K-Means算法的聚类精度。为了改善K-Means算法的收敛速度,可以采用增K-Means算法,它可以有效的减少K-Means算法的运行时间。另外,可以采用并行K-Means算法来改善K-Means算法的收敛速度。 ### 回答2: K-means算法是一种常用的聚类算法,但它有一些局限性。为了改良K-means算法,我们可以考虑以下几个方面: 1. 初始化中心点的选择:传统的K-means算法使用随机选择的方法来初始化中心点,这可能导致算法陷入局部最优解。改良的K-means算法可以采用更加智能的初始化方法,例如K-means++算法,在选择初始中心点时考虑数据点的距离。 2. 簇划分的准则:传统的K-means算法使用欧氏距离作为簇划分的准则,但这在处理高维数据时可能存在问题。改良的K-means算法可以采用其他距离度方法,例如余弦相似度或者马氏距离。 3. 簇数选择:K-means算法需要事先确定簇的个数K,但在实际应用中往往很难确定这个参数。改良的K-means算法可以引入一些评估指标,例如轮廓系数或者Gap统计,来辅助选择合适的簇数。 4. 迭代终止条件:传统的K-means算法通常通过判断簇中心的变化是否小于一个阈值来判断迭代是否终止。改良的K-means算法可以引入更加准确的终止条件,例如加入最大迭代次数或者目标函数的收敛程度。 5. 离群值处理:K-means算法对离群值非常敏感,可能会将其归为某个簇中。改良的K-means算法可以考虑在迭代过程中检测离群值,并进行特殊处理,例如将其剔除或者单独作为一个簇。 通过对K-means算法的改良,可以提升其聚类性能并适应更多的应用场景。当然,具体的改良方法需要根据实际问题和数据特点进行调整。 ### 回答3: 改良的k-means算法是对传统的k-means算法进行改进和优化,以提高聚类效果和算法的运行效率。具体改良如下: 1. 初始聚类中心的选择优化:传统k-means算法采用随机选取k个样本作为初始聚类中心,但这种方式容易导致算法收敛到局部最优解。改良的方法是采用k-means++算法,该算法能够选择更合理的初始聚类中心,提高聚类效果。 2. 动态调整聚类中心:传统k-means算法是通过迭代迭代计算来更新聚类中心,直到满足停止准则。改良的方法是在每次迭代中,根据当前的聚类结果动态调整聚类中心的位置,使聚类中心能更好地适应数据分布,加速算法的收敛速度。 3. 距离度优化:传统k-means算法通常使用欧氏距离来度样本之间的相似度,但当数据分布存在明显的偏斜或离群点时,欧氏距离容易受到影响。改良的方法是采用改进的距离度,如余弦相似度、马哈拉诺比斯距离等,根据实际数据情况选择最适合的度方式,提高聚类精度。 4. 并行计算加速:传统k-means算法顺序计算的,随着数据的增大,算法的运行效率较低。改良的方法是采用并行计算的方式,通过多线程或分布式计算的方式,将数据分成多个小批进行聚类计算,最后再合并聚类结果,加速算法的运行速度。 5. 收敛速度的优化:传统k-means算法通常设置一个最大迭代次数,而且每次迭代都需要重新计算样本点到聚类中心的距离,算法收敛速度慢。改良的方法是引入更快速的收敛准则,如当聚类中心的变化小于一定阈值时,判定算法已经收敛,提前终止迭代,减少计算。 通过以上改良,k-means算法可以更好地适应不同类型的数据分布,提高聚类的准确性和算法的运行效率。但需要根据具体问题的特点选择合适的改良方法,以获得最佳的聚类效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dear_Xuan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值