所见所闻-排序算法题(快排)

1 篇文章 0 订阅
1 篇文章 0 订阅

一道快排题


Q:对一个已知长度的数组或列表,找出第k大的值。要求时间复杂度<=Nlog2N

PS:作为一名一直使用并将长期使用python的程序猿而言,在不考虑时间复杂度和空间复杂度的情况下,第一反应应该是sorted排个序,然后取第k-1个值即可。天真!!!这么简单还是题吗!!!你以为坑只是时间复杂度?!?



一、切题

        平均时间复杂度在Nlog2N级别的算法,应该有如下几个:快排、归并排和堆排。ps:快排是笔者昨天才再一次认真地过了一遍。归并还没看到。堆排在python是有built-in模块支持的。这次咱们先聊一聊快排。



其实在拿到这道题的时候,我的第一反应是遍历选出k个最小值,存放在另一个数组里,在存放的过程中有一个对该数组当前所有元素的遍历。这样便会存在一个隐性问题:1、最终需要而外k的空间复杂度;2kN的时间复杂度。于是乎就有了k+kNNlog2N的大小比较了。



1、快排的多版本介绍

    (1) 题主版


        快排原理描述大致可以理解为:左小右大(左大右小)和分而治之。


   即:1、选取基准数,把小于该数的数全丢左边,大于的都丢右边;2、递归调用该方法。


左丢右丢的思想就是寻找基准数左右符合条件的数(左边大于基准数,右边小于基准数)进行位置互调,直到两者下标重合。


于是:

python里面的思想就可以是:1、任取数组里的一个数(这里我取的是向上取整的中间数),实现左丢右丢;对丢出来的左右列表进行如上递归操作,直到需要递归的列表长度为1(或者可以理解为索引一开始就交叉重合),那说明对一个基准数的遍历已经OK


然后,重点就是递归了。函数要能做到递归,需要“上下兼容”。这个词是在一本编程杂志书看到的,可以这么理解:因为递归函数传入的参数,是需要该函数的输出或者中间输出做铺垫的,该函数的某一个过程的输出很可能就是该函数的输入。因此在设计函数的时候,需要尽可能保证函数传入的参数是灵活可扩展的。所以题主写了两个版本(当然第一个版本被淘汰了,因为扩展性极其差,甚至无法扩展)


    于是乎,第一个版本,函数传入的参数是一个值,即数组“array



def test(array):

    print(array)

    start = 0

    end = len(array) - 1

    index = (start + end) // 2

    number = array[index]

    while start <= end:

        # 保证作比较的数必须大于左边

        while array[start] < number:

            start += 1

            while array[end] > number:

                end -= 1

                if start > end:

                    continue

                array[start], array[end] = array[end], array[start]

                start += 1

                end -= 1

    print(array)  


        这一批代码是没办法向下兼容的。只能实现一次调用,要是想继续调用,至少得写一个扩展函数,写扩展函数的方法并非不行,而是没有必要,所以我们尝试换一种思路进行


      OK,既然区间需要迭代,那就把迭代区间也写到函数里,应该可以避免迭代中进行迭代。

      琢磨一下,慢慢造出第二版,也是最终版


def test(array, start, end):

        # 设置作比较的数

        index = (start + end) // 2

        number = array[index]

        # 设置遍历的索引位置

        i, j = start, end

        print('source:', array)

        print('source:',i, j, index, '---', start, end, '---', array[index])

   

    #
如果索引交叉了,说明对某一个数的对比已经完成一个轮回

   
    while i <= j:

   
        # 保证作比较的数必须大于左边

   
        while array[i] < number:

   
            i += 1

            while array[j] > number:

                j -= 1

            if i > j:

                continue

            array[i], array[j] = array[j], array[i]

            print('running:', array)

            print(i, j, index, '---', start, end, '---', array[index])

            i += 1

            j -= 1

    #
因为作比较的数左右的数存在不稳定性(即:可能左边或者右边的数普遍偏小,导致i或者j步长较大),因此递归的条件应该是整个区间

            # 左递归

            if start < j:

                test(array, start, j)

       
    if end > j:

                test(array, i, end)


递归区间和条件也是比较绕的地方,琢磨了很久,递归的判断条件怎么写,到底递归的必要条件是啥。因为逻辑还是挺绕的,这个条件是在观察中间的输出的基础上推出来的。其实只要遍历的索引重合就说明不用再继续递归了,反过来就是不重合时需要递归,这个想法跟while循环一致。While循环也是主要考虑“什么时候调换位置”,运用的也是所谓的“反过来”的想法实现的。


(2) 填坑版


    填坑版比较符合C系列语言的思想。大致意思就是,随机选取基准数(一般选取首个),先找小于基准数的值(右侧开始左探索),交换位置,记下位置;后找大于基准数的值(左侧开始右探索),交换位置。然后继续重复以上步骤。如果从大到小排序,就必须先左再右,取最后值做基准数(顺序和位置决定了先交换的值的条件)。这是填坑版的一些小小限制,详见:https://blog.csdn.net/code_ac/article/details/74158681


    (3) 插入版

   插入版的主要思想就是,把基准数先拿出来,把小于基准数的值放到该位置,大于基准数的值放到刚才拿出来的数的位置,最后把该基准数插入去大于基准数的值的位置,完成一次整体替换。跟填坑版是很类似很类似的。插入版需要保存两个临时变量,进行三次插入;填坑版需要保存一个临时变量,进行四次插入。详见https://www.cnblogs.com/landpack/p/4781579.html



以上后两种方法,在各大技术分享站有介绍,这里只是做个人的见解分析。

2、题目的最后处理


最后的问题

K个就一定是第K大的????

K个就一定是第K大的????

K个就一定是第K大的????


在后来的题中,出题人给我出了第二题,如何清洗出一个不含重复元素的列表或数组。

直到我自己输出了第一个问题的答案以后,才想到,列表或数组元素是有可能重复的!!!


所以,第k个值不一定是第k大的值(说到这,下次把数据库的四大排序更加分享一下,其应用场景和这个题的k取值紧密相连(如rankdense_rank))。因此我们需要去重再取第K大的值。


于是乎,怎么去重。今天早上我还问了一下我的同事,我说,给你一个列表,你怎么清洗出不重复的元素并找到第k大的值,问了俩。问了一个搞python,一个搞数据库的。玩数据库的说,先放到excel,插进数据库,然后排序选取index=k的。玩python的说,遍历list,插入另一个list2,插入条件是not in list2。我觉得都ok,都有很强的逻辑.个人觉得其实不重复的list它就是集合的概念,其实用pythonset函数就OK。再不然其实用python的字典操作也是OK的,至少它底层也是一种哈希函数的应用,跟集合是异曲同工的。不讨论好坏,后两者本人更认为是pythonic的玩法。


所以。其实这道题,本人认为,应该有两种思路。一种是setsort,另一种是先sortset,两者的区别题主暂时无法做出优劣判断,单方面觉得,会存在一个界点,两者的效率在该界限有胜负变更的变化。如:




类似,仅仅是个人的经验。在python的多线程和多进程编程,就是存在这种边界效应的。


OK,整个排序题的核心代码如下:

    


import random


random.seed(0)

lis = []

for i in range(20):

    lis.append(random.randint(0, 10))

random.shuffle(lis)



def test(array, start, end):

    # 设置作比较的数

    index = (start + end) // 2

    number = array[index]

    # 设置遍历的索引位置

    i, j = start, end

    print('source:', array)

    print('source:',i, j, index, '---', start, end, '---', array[index])


    # 如果索引交叉了,说明对某一个数的对比已经完成一个轮回

    while i <= j:

        # 保证作比较的数必须大于左边

        while array[i] < number:

            i += 1

        while array[j] > number:

            j -= 1


        if i > j:

            continue


        array[i], array[j] = array[j], array[i]

        print('running:', array)

        print(i, j, index, '---', start, end, '---', array[index])

        i += 1

        j -= 1


    # 因为作比较的数左右的数存在不稳定性(即:可能左边或者右边的数普遍偏小,导致i或者j步长较大),因此递归的条件应该是整个区间

    # 左递归

    if start < j:

        test(array, start, j)

    if end > j:

        test(array, i, end)


然后因为早期教育中,这些排序算法都是CC++写的,于是尝试用C也写了一个,如下:


#include <stdio.h>


void test(int array[], int start, int end);


int main() {

    int array[10];

    int i = 0;

    srand(1);

    for (i=0;i<10;i++){

        array[i] = rand()%10;

    }

    for (i=0;i<10;i++){

        printf("%d", array[i]);

    }

    test(array, 0, 9);

    printf("\n--------------\n");

    for (i=0;i<10;i++){

        printf("%d", array[i]);

    }

}



void test(int array[], int start, int end){


    int index = (start + end) / 2;

    int number = array[index];

    int i, j;

    i = start;

    j = end;

    int tmp;

    while (i <= j){

        while (array[i] < number){

            i++;

        }

        while (array[j] > number){

            j--;

        }

        if (i<=j){

        tmp = array[i];

        array[i] = array[j];

        array[j] = tmp;

        i++;

        j--;

        }

    }

    if (start < j){

        test(array, start, j);

    }

    if (end > i){

        test(array, i, end);

    }


}


最后,python版,不考虑复杂度的用法



import random


random.seed(0)

lis = []

for i in range(20):

    lis.append(random.randint(0, 10))

random.shuffle(lis)


def test(array):

    array_ = list(sorted(set(array)))[k]

    print(array_)




人生苦短,我学python



以上为本次分享技术栈,如有不当,请指正


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值