zoukankan      html  css  js  c++  java
  • python的两种随机数选取key值的快速排序算法的实现

    零:环境

    python 3.6.5

    JetBrains PyCharm 2018.1.4 x64

    一:基于百度百科的思路

    源地址:https://baike.baidu.com/item/快速排序算法/369842?fromtitle=快速排序&fromid=2084344&fr=aladdin

    原文:

    设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它左边,所有比它大的数都放到它右边,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
    一趟快速排序的算法是:
    1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
    2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
    3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]的值交换;
    4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换;
    5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

      分析:

        这种快速排序的方法可以理解成通过一轮交换将key值交换到某个正确位置上,该位置的左侧的全部元素都不高于key,右侧元素都不低于key

        百度百科选取的左侧的第一个元素作为key值,然后首先从右侧开始找,使得在右侧的合适的值找到之后,发生交换时,key值肯定会随之被来回交换,可以理解成每次寻找都是在找key值对面一侧的适合元素,key值被动的变化位置,到最后被交换到key所应该在的合适的位置

        而random选取随机值的话因为key位置的不确定性,所以为了在遇到key值时,为了能将key值也像百度百科那样随之来回交换到正确位置,在寻找合适元素时的判断条件不应该添加等号,以下是例子。

        

    [76, 2, 32, 4, 56, 65, 45, 78, 32, 96, 32, 32, 42, 1, 78, 4, 126]
    v_数组 = [126, 2, 32, 4, 56, 65, 45, 78, 32, 96, 32, 32, 42, 1, 78, 4, 76]

        

          首先一开始假设我们随机到第五个元素为key值,即56为key值,左边界为0,右边界为len(v_数组)-1,即16

        1:第一轮交换

        假设我们先从左侧开始找

        根据上面的快速排序原则,我们从左边找的时候先找第一个比key值大的数,但是为了让我们能够匹配到key值,我们要先找第一个大于等于key值的数

        根据描述可以看到第一个元素就是

        所以我们开始进入到元素交换环节

        

    v_数组[v_左边界],v_数组[v_右边界] = v_数组[v_右边界],v_数组[v_左边界]

        此时的数组情况应该为v_左边界:0  v_右边界:16

    [76, 2, 32, 4, 56, 65, 45, 78, 32, 96, 32, 32, 42, 1, 78, 4, 126]

        2:第二轮交换

        左侧找完了我们该从右侧开始找

        同上面的原理,找到第一个小于等于key的数

        因为126大于key值56,所以v_右边界-=1

        此时5<key,所以v_右边界不变,为15,开始交换

        此时的数组情况应该为v_左边界:0  v_右边界:15

    [4, 2, 32, 4, 56, 65, 45, 78, 32, 96, 32, 32, 42, 1, 78, 76, 126]

        3:第三轮交换

        这时又开始从左侧开始找

        我们左侧的v_左边界在+1过程中遇到了key值56,为了抓住这个变量我们要就这样停止下来,开始交换

        此时的数组情况应该为v_左边界:4  v_右边界:15

    [4, 2, 32, 4, 76, 65, 45, 78, 32, 96, 32, 32, 42, 1, 78, 56, 126]

        ……以此类推

        结束条件应当是v_左边界<=v_old右边界(v_old右边界为一开始的右边界,因为左右边界一直在变化所以先提取存起来)

        结束的时候v_左边界就是key值所在位置

        但是,假如左侧和右侧都找到了和key一样的key值的话就陷入了死循环

        所以为了避免死循环应该在循环判断时添加一个条件,如果左右的值相等且都为key的话,那么从左侧找时那就继续+1,从右侧找时那就继续-1

        其实交换是可以节省的,可以左右都找到或到临界点时才交换,

        以下就是节省了一次交换的参考代码:

     1 def f_第四题_左右交换_随机key(v_数组,t_左边界,t_右边界,t_key位置):
     2     t_old左边界, t_old右边界, t_oldKey = t_左边界, t_右边界, v_数组[t_key位置]
     3     while t_左边界 < t_右边界:
     4         #   用小于号或大于号而不加上等于号是为了把key值能够交换到中间位置而省去了移除插入操作
     5         #   判断左右相等是为了防止左右都遇到了和key一样的值而死循环
     6         while (v_数组[t_左边界] < t_oldKey or v_数组[t_左边界] == v_数组[t_右边界] == t_oldKey) and t_左边界 < t_右边界 :
     7             t_左边界 += 1
     8         while (v_数组[t_右边界] > t_oldKey or v_数组[t_左边界] == v_数组[t_右边界] == t_oldKey) and t_左边界 < t_右边界:
     9             t_右边界 -= 1
    10         if t_左边界 < t_右边界:
    11             v_数组[t_左边界], v_数组[t_右边界] = v_数组[t_右边界], v_数组[t_左边界]
    12         pass
    13     if t_old左边界 < t_左边界 - 1:
    14         f_第四题_左右交换_随机key(g_数组,t_old左边界, t_左边界 - 1, random.randint(t_old左边界, t_左边界 - 1))
    15     if t_左边界 + 1 < t_old右边界:
    16         f_第四题_左右交换_随机key(g_数组,t_左边界 + 1, t_old右边界, random.randint(t_左边界 + 1, t_old右边界))
    17     pass

        其实非递归写法也比较简单

        可以定义一个伪栈v_上下界栈,在栈里添加左右边界和key位置,然后在函数退出时再Pop最后一个元素

        以下是参考代码:

     1 def f_第四题_左右交换_随机key(v_数组,t_左边界,t_右边界,t_key位置):
     2     """
     3     百度的快排的无递归算法实现
     4     :param v_数组: 数组参数
     5     :param t_左边界: 想要排序的数组的左边界
     6     :param t_右边界: 想要排序的数组的右边界
     7     :param t_key位置: key的位置
     8     :return: 无返回值暂时,可以修改……
     9     """
    10     v_上下界栈 = [[t_左边界, t_右边界, v_数组[random.randint(t_左边界,t_右边界)]]]
    11 
    12     while v_上下界栈:
    13         t_左边界,t_右边界,t_oldKey = v_上下界栈[-1][0],v_上下界栈[-1][1],v_上下界栈[-1][2]
    14         while t_左边界 < t_右边界:
    15             while (v_数组[t_左边界] < t_oldKey or v_数组[t_左边界] == v_数组[t_右边界] == t_oldKey) and t_左边界 < t_右边界:
    16                 t_左边界 += 1
    17             while (v_数组[t_右边界] > t_oldKey or v_数组[t_左边界] == v_数组[t_右边界] == t_oldKey) and t_左边界 < t_右边界:
    18                 t_右边界 -= 1
    19             if t_左边界 < t_右边界:
    20                 v_数组[t_左边界], v_数组[t_右边界] = v_数组[t_右边界], v_数组[t_左边界]
    21             pass
    22 
    23         if v_上下界栈[-1][0] < t_左边界 - 1:
    24             v_上下界栈.insert(-2, [v_上下界栈[-1][0], t_左边界 - 1, v_数组[random.randint(v_上下界栈[-1][0], t_左边界 - 1)]])
    25         if t_左边界 + 1 < v_上下界栈[-1][1]:
    26             v_上下界栈.insert(-2, [t_左边界 + 1, v_上下界栈[-1][1], v_数组[random.randint(t_左边界 + 1, v_上下界栈[-1][1])]])
    27         v_上下界栈.pop(-1)
    28         pass
    29     pass

    二:老师所讲的思路

        老师的思路是都从一侧开始寻找

        1:先找第一个比key大的数,我们用v_下标来遍历,找到之后那个位置我们就认为他是key值可能会插入的位置,并将v_下标赋值给v_插入位置。

          从左往右找的效果会造成v_插入位置处的值的左侧必定都不大于key,但是右侧并不保证

           2:在找到可能的插入位置之后再从v_插入位置处开始定义v_下标,v_下标负责寻找第一个比key小的值,然后找到之后将v_插入位置处的值与v_下标处的值交换,此时v_插入位置处的值是比key小的,所以v_插入位置要+=1

        3:此时v_下标继续往后查找直到结尾

        以下是例子

        我们还以那个数组为例

        

    v_数组 = [126, 2, 32, 4, 56, 65, 45, 78, 32, 96, 32, 32, 42, 1, 78, 4, 76]

        我们还是假设56是key值

        1:第一步

        从左侧的第一个元素开始寻找第一个比key大的数,我们可以看到第一个元素就是

        所以定义v_插入位置 = 0

        2:第二步

        定义v_下标 = v_插入位置

        开始寻找第一个比key大的数,此时v_下标为1

        所以我们要开始交换了

        结果如下

    [2, 126, 32, 4, 56, 65, 45, 78, 32, 96, 32, 32, 42, 1, 78, 4, 76]

        3:第三步

        v_插入位置处的值变成了小于key的值,所以v_插入位置需要加一

        但是有一种特殊情况,当加一后遇到了key值之后我们要跳过这个key,所以还要继续+1

        4:重复第二三步

        因为我们忽略了key,所以key的位置必定不变,交换到最后v_插入位置的左侧必定都小于等于key,右侧都大于等于key,但是key还在原位,我们需要最后将key插入到v_插入位置然后再删除原来位置的key

        以下是参考代码,关于特殊情况会在代码里注释

     1 def f_随机数版本(p_列表,p_左边界,p_右边界,p_key位置):
     2     v_插入位置,v_下标 = p_左边界,p_左边界
     3     v_key = p_列表[p_key位置]
     4     #   跳过key位置
     5     #   因为我们这里最后是先insert再pop,所以插入到p_右边界+1即为插入到最后,不会出错
     6     while v_下标<=p_右边界 and (p_列表[v_下标] < v_key or v_下标==p_key位置):
     7         v_下标+=1
     8     #   插入位置更新为下标位置
     9     v_插入位置 = v_下标
    10     #   带等于号是为了能够判断到最后一个元素
    11     while v_下标<=p_右边界:
    12         while v_下标 <= p_右边界 and (p_列表[v_下标] >= v_key or v_下标==p_key位置):
    13             v_下标+=1
    14         #   当v_下标有意义时,才交换
    15         if v_下标<=p_右边界:
    16             p_列表[v_下标],p_列表[v_插入位置] = p_列表[v_插入位置],p_列表[v_下标]
    17             v_插入位置+=1
    18             #   跳过key位置
    19             if v_插入位置 == p_key位置:
    20                 v_插入位置+=1
    21     #   将Key值插入到指定位置
    22     p_列表.insert(v_插入位置,v_key)
    23     #   如果插入位置在key之前,则key位置就会往后推迟一位,所以需要pop +1 
    24     if v_插入位置 < p_key位置:
    25         p_列表.pop(p_key位置 + 1)
    26     else:
    27         p_列表.pop(p_key位置)
    28         #   插入位置在key之后的话,因为pop了key,所以插入位置处前面的元素会少,插入位置会指向下一个元素,所以为了正确指向key要-1
    29         v_插入位置-=1
    30     if v_插入位置-1>p_左边界:
    31         f_随机数版本(p_列表,p_左边界,v_插入位置-1,random.randint(p_左边界,v_插入位置-1))
    32     if v_插入位置+1<p_右边界:
    33         f_随机数版本(p_列表,v_插入位置+1,p_右边界,random.randint(v_插入位置+1,p_右边界))
    34 
    35     pass

        以上皆为自己的思考

        转载请注明出处

  • 相关阅读:
    【DDD】领域驱动设计实践 —— 架构风格及架构实例
    【DDD】领域驱动设计精要
    Zynq UltraScale+ cross compiler
    Platform device/driver注册过程 (转)
    static inline extern等概念
    (int argc, char *argv[]) 指针数组
    linux man 1,2,3 命令
    指针左值错误
    arm ds5 编译选项
    在JTAG菊花链拓扑对设备编程
  • 原文地址:https://www.cnblogs.com/dofstar/p/11281920.html
Copyright © 2011-2022 走看看