快速排序

来源:互联网 发布:苹果越狱软件源大全 编辑:程序博客网 时间:2024/06/07 09:51

相关的快速排序的解释很容易找到,这里只贴代码

传统的快速排序,不添加任何优化

#!/usr/bin/python#coding: utf-8def Partition(res, low, high):    # 把第一个元素作为基准点    point = res[low]    while low < high:        # 把小于基准点的元素都放在基准点的左边,大于基准点的元素都放在基准点的右边        # 接下来的两个while循环中的 <= 和 >= 中不能把等号去掉,如果去掉的话对相同的元素不能排序,在while循环中将死循环        # 从后往前遍历,把小于基准点的元素往前移        while low < high and res[high] >= point:            high -= 1        # 元素交换        res[low], res[high] = res[high], res[low]        # 从前往后遍历,把大于基准点的元素往后移        while low < high and res[low] <= point:            low += 1        # 元素交换        res[low], res[high] = res[high], res[low]    # 返回中间点的位置    return lowdef Quick_Sort(res, low, high):    if low < high:        # 获取基准点        point = Partition(res, low, high)        # 递归调用基准点的左边        Quick_Sort(res, low, point - 1)        # 递归调用基准点的右边        Quick_Sort(res, point + 1, high)if __name__ == "__main__":    res = [51, 45, 15, 78, 84, 51, 24, 51]      Quick_Sort(res, 0, len(res) - 1)    print res    res = [51, 45, 15, 78, 84, 15, 85, 51]      Quick_Sort(res, 0, len(res) - 1)    print res

当前的快速排序虽然能够实现对序列的排序,但是仍然有些不足之处,因为每一次取的这个元素并不一定是中间的元素,不会使左右两边恰好有相等的元素。

举例如下:

[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]  

这样一个序列,在排序过程中,取第一个元素作为基准点,这样经过一轮排序之后,

[0, 8, 7, 6, 5, 4, 3, 2, 1, 9]

只完成了一组元素的排序,效率并不高。

而且需要10趟快速排序才能得到最后的结果

每一趟的结果如下:

[0, 8, 7, 6, 5, 4, 3, 2, 1, 9]
[0, 8, 7, 6, 5, 4, 3, 2, 1, 9]
[0, 1, 7, 6, 5, 4, 3, 2, 8, 9]
[0, 1, 7, 6, 5, 4, 3, 2, 8, 9]
[0, 1, 2, 6, 5, 4, 3, 7, 8, 9]
[0, 1, 2, 6, 5, 4, 3, 7, 8, 9]
[0, 1, 2, 3, 5, 4, 6, 7, 8, 9]
[0, 1, 2, 3, 5, 4, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


对传统快速排序进行第一次优化

使用三数取中法。

三数取中法:从开头选一个元素,结尾选一个元素,中间选一个元素,取三个元素中间的那一个作为基准点(第一个元素)进行递归,最小的元素放在中间,最大的元素放在结尾。

代码如下(使用三数取中法):

#!/usr/bin/python#coding: utf-8def Partition(res, low, high):        # 三数取中法进行优化    # 获取中间的那个数的下表    m = low + (high - low) / 2    if res[low] > res[high]:    res[low], res[high] = res[high], res[low]    if res[m] > res[high]:    res[m], res[high] = res[high], res[m]    # 经过前面的两步讨论,已经使high点索引的元素变成最大的    # 接下来把low点索引的元素变成m和low这两个索引点的最大值    if res[m] > res[low]:    res[m], res[low] = res[low], res[m]    point = res[low]    while low < high:        # 把小于基准点的元素都放在基准点的左边,大于基准点的元素都放在基准点的右边        # 接下来的两个while循环中的 <= 和 >= 中不能把等号去掉,如果去掉的话对相同的元素不能排序,在while循环中将死循环        # 从后往前遍历,把小于基准点的元素往前移        while low < high and res[high] >= point:            high -= 1        # 元素交换        res[low], res[high] = res[high], res[low]        # 从前往后遍历,把大于基准点的元素往后移        while low < high and res[low] <= point:            low += 1        # 元素交换        res[low], res[high] = res[high], res[low]    # 返回中间点的位置    return lowdef Quick_Sort(res, low, high):    if low < high:        # 获取基准点        point = Partition(res, low, high)        # 递归调用基准点的左边        Quick_Sort(res, low, point - 1)        # 递归调用基准点的右边        Quick_Sort(res, point + 1, high)if __name__ == "__main__":    res = [51, 45, 15, 78, 84, 51, 24, 51]      Quick_Sort(res, 0, len(res) - 1)    print res    res = [51, 45, 15, 78, 84, 15, 85, 51]      Quick_Sort(res, 0, len(res) - 1)    print res    res = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]      Quick_Sort(res, 0, len(res) - 1)    print res

其中新增代码为:

    # 三数取中法进行优化    # 获取中间的那个数的下表    m = low + (high - low) / 2    if res[low] > res[high]:    res[low], res[high] = res[high], res[low]    if res[m] > res[high]:    res[m], res[high] = res[high], res[m]    # 经过前面的两步讨论,已经使high点索引的元素变成最大的    # 接下来把low点索引的元素变成m和low这两个索引点的最大值    if res[m] > res[low]:    res[m], res[low] = res[low], res[m]

经过优化以后只需要6次就能得到最终的结果

每一趟的结果如下:

[1, 2, 3, 4, 0, 5, 6, 7, 8, 9]
[0, 1, 2, 4, 3, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

相对于第一种来说,进行了一定的优化。


对传统快速排序进行第二次优化(优化掉不必要的赋值)

在Partiton函数中,每一次找出不满足条件的都要进行一次交换,但是对于基准点来说,在每一趟中都要进行多次的交换,这样会消耗掉部分的时间,可以通过直接赋值的方式来进行相应的优化。

即对于需要交换的两个元素,如果不是基准点的话,直接把该元素赋值到相应的位置,基准点不进行赋值。

代码如下:

#!/usr/bin/python#coding: utf-8def Partition(res, low, high):    # 把第一个元素作为基准点    point = res[low]    while low < high:        # 把小于基准点的元素都放在基准点的左边,大于基准点的元素都放在基准点的右边        # 接下来的两个while循环中的 <= 和 >= 中不能把等号去掉,如果去掉的话对相同的元素不能排序,在while循环中将死循环        # 从后往前遍历,把小于基准点的元素往前移        while low < high and res[high] >= point:            high -= 1        # 元素交换        res[low] = res[high]        # 从前往后遍历,把大于基准点的元素往后移        while low < high and res[low] <= point:            low += 1        # 元素交换        res[high] = res[low]    res[low] = point    # 返回中间点的位置    return lowdef Quick_Sort(res, low, high):    if low < high:        # 获取基准点        point = Partition(res, low, high)        # 递归调用基准点的左边        Quick_Sort(res, low, point - 1)        # 递归调用基准点的右边        Quick_Sort(res, point + 1, high)if __name__ == "__main__":    res = [51, 45, 15, 78, 84, 51, 24, 51]      Quick_Sort(res, 0, len(res) - 1)    print res    res = [51, 45, 15, 78, 84, 15, 85, 51]      Quick_Sort(res, 0, len(res) - 1)    print res    res = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]      Quick_Sort(res, 0, len(res) - 1)    print res
相比于传统的快速排序,只是对Partition函数进行了优化,优化部分如下:

def Partition(res, low, high):    # 把第一个元素作为基准点    point = res[low]    while low < high:        # 把小于基准点的元素都放在基准点的左边,大于基准点的元素都放在基准点的右边        # 接下来的两个while循环中的 <= 和 >= 中不能把等号去掉,如果去掉的话对相同的元素不能排序,在while循环中将死循环        # 从后往前遍历,把小于基准点的元素往前移        while low < high and res[high] >= point:            high -= 1        # 元素交换        res[low] = res[high]        # 从前往后遍历,把大于基准点的元素往后移        while low < high and res[low] <= point:            low += 1        # 元素交换        res[high] = res[low]    res[low] = point    # 返回中间点的位置    return low

经过这种优化方式以后,并不会对快速排序所需要的次数有所减少,只是减少了不必要的赋值操作。


对传统快速排序进行第三次优化(优化小数组时的排序方案)

对于一些数据量较小的情况,没有必要进行快速排序,因为这样并不会对时间有太好的优化,反而会消耗大量的内存,可以使用直接插入排序(因为直接插入排序是简单排序中性能最好的)。

给定一个数组,何时选择直接插入排序?何时选择快速排序?

如果说数组的长度小于7,使用直接插入排序是最好的;如果大于等于7,则选择快速排序,因为数组长度为7是快速排序效率最高的分界点。

#!/usr/bin/python#coding: utf-8def ISort(res, low, high):    def Insert_Sort(res, num):        u"直接插入排序"        for i in range(1, num):            # 如果说当前元素比上一个元素小,则进行交换,把当前的 res[i] 插入到相应的位置            if res[i] < res[i - 1]:                temp = res[i]                j = i - 1                # 继续往前遍历,如果说当前位置的值大于temp的话,则把当前值后移一位                # while语句退出的条件是li列表越界或者是当前j位置的值大于temp                while j >= 0 and res[j] > temp:                    res[j + 1] = res[j]                    j -= 1                # 当前j所表示的是所能放temp的前一位, 所以要加1                res[j + 1] = temp        return res    # 把排序的序列重新赋值给res    res[low : ] = Insert_Sort(res[low : ], high - low + 1)def Partition(res, low, high):    point = res[low]    while low < high:        # 把小于基准点的元素都放在基准点的左边,大于基准点的元素都放在基准点的右边        # 接下来的两个while循环中的 <= 和 >= 中不能把等号去掉,如果去掉的话对相同的元素不能排序,在while循环中将死循环        # 从后往前遍历,把小于基准点的元素往前移        while low < high and res[high] >= point:            high -= 1        # 元素交换        res[low], res[high] = res[high], res[low]        # 从前往后遍历,把大于基准点的元素往后移        while low < high and res[low] <= point:            low += 1        # 元素交换        res[low], res[high] = res[high], res[low]    # 返回中间点的位置    return lowdef Quick_Sort(res, low, high):    if high - low > 7:        # 获取基准点        point = Partition(res, low, high)        # 递归调用基准点的左边        Quick_Sort(res, low, point - 1)        # 递归调用基准点的右边        Quick_Sort(res, point + 1, high)    else:        # 调用直接插入排序        ISort(res, low, high)if __name__ == "__main__":    res = [51, 45, 15, 78, 84, 51, 24, 51]    Quick_Sort(res, 0, len(res) - 1)    print res    res = [51, 45, 15, 78, 84, 15, 85, 51]    Quick_Sort(res, 0, len(res) - 1)    print res    res = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]      Quick_Sort(res, 0, len(res) - 1)    print res


这种优化方案个人并不推荐,代码量增加太多,

就是把直接插入排序和快速排序结合起来即可。


对传统快速排序进行第四次优化(优化递归操作)

通过减少递归调用的次数(尾递归的方式),优化空间的利用率。


#!/usr/bin/python#coding: utf-8def Partition(res, low, high):    point = res[low]    while low < high:        # 把小于基准点的元素都放在基准点的左边,大于基准点的元素都放在基准点的右边        # 接下来的两个while循环中的 <= 和 >= 中不能把等号去掉,如果去掉的话对相同的元素不能排序,在while循环中将死循环        # 从后往前遍历,把小于基准点的元素往前移        while low < high and res[high] >= point:            high -= 1        # 元素交换        res[low], res[high] = res[high], res[low]        # 从前往后遍历,把大于基准点的元素往后移        while low < high and res[low] <= point:            low += 1        # 元素交换        res[low], res[high] = res[high], res[low]    # 返回中间点的位置    return lowdef Quick_Sort(res, low, high):    while low < high:        # 获取基准点        point = Partition(res, low, high)        if point - low < high - point:        # 递归调用基准点的左边        Quick_Sort(res, low, point - 1)        low = point + 1        else:        # 递归调用基准点的右边        Quick_Sort(res, point + 1, high)        high = point - 1if __name__ == "__main__":    res = [51, 45, 15, 78, 84, 51, 24, 51]      Quick_Sort(res, 0, len(res) - 1)    print res    res = [51, 45, 15, 78, 84, 15, 85, 51]      Quick_Sort(res, 0, len(res) - 1)    print res    res = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]      Quick_Sort(res, 0, len(res) - 1)    print res
修改的代码如下:

def Quick_Sort(res, low, high):    while low < high:        # 获取基准点        point = Partition(res, low, high)        if point - low < high - point:        # 递归调用基准点的左边        Quick_Sort(res, low, point - 1)        low = point + 1        else:        # 递归调用基准点的右边        Quick_Sort(res, point + 1, high)        high = point - 1


四步优化中前面三步可以在一块使用,第四种不能和第三种在写在同一个代码中

第一二三种优化放一块的代码如下:

#!/usr/bin/python#coding: utf-8def ISort(res, low, high):    def Insert_Sort(res, num):        u"直接插入排序"        for i in range(1, num):            # 如果说当前元素比上一个元素小,则进行交换,把当前的 res[i] 插入到相应的位置            if res[i] < res[i - 1]:                temp = res[i]                j = i - 1                # 继续往前遍历,如果说当前位置的值大于temp的话,则把当前值后移一位                # while语句退出的条件是li列表越界或者是当前j位置的值大于temp                while j >= 0 and res[j] > temp:                    res[j + 1] = res[j]                    j -= 1                # 当前j所表示的是所能放temp的前一位, 所以要加1                res[j + 1] = temp        return res    # 把排序的序列重新赋值给res    res[low : ] = Insert_Sort(res[low : ], high - low + 1)def Partition(res, low, high):    # 三数取中法进行优化    # 获取中间的那个数的下表    m = low + (high - low) / 2    if res[low] > res[high]:        res[low], res[high] = res[high], res[low]    if res[m] > res[high]:        res[m], res[high] = res[high], res[m]    # 经过前面的两步讨论,已经使high点索引的元素变成最大的    # 接下来把low点索引的元素变成m和low这两个索引点的最大值    if res[m] > res[low]:        res[m], res[low] = res[low], res[m]    point = res[low]    while low < high:        # 把小于基准点的元素都放在基准点的左边,大于基准点的元素都放在基准点的右边        # 接下来的两个while循环中的 <= 和 >= 中不能把等号去掉,如果去掉的话对相同的元素不能排序,在while循环中将死循环        # 从后往前遍历,把小于基准点的元素往前移        while low < high and res[high] >= point:            high -= 1        # 元素赋值        res[low] = res[high]        # 从前往后遍历,把大于基准点的元素往后移        while low < high and res[low] <= point:            low += 1        # 元素赋值        res[high] = res[low]    # 返回中间点的位置    return lowdef Quick_Sort(res, low, high):    if high - low > 7:        # 获取基准点        point = Partition(res, low, high)        # 递归调用基准点的左边        Quick_Sort(res, low, point - 1)        # 递归调用基准点的右边        Quick_Sort(res, point + 1, high)    else:        # 调用直接插入排序        ISort(res, low, high)if __name__ == "__main__":    res = [51, 45, 15, 78, 84, 51, 24, 51]    Quick_Sort(res, 0, len(res) - 1)    print res    res = [51, 45, 15, 78, 84, 15, 85, 51]    Quick_Sort(res, 0, len(res) - 1)    print res    res = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]      Quick_Sort(res, 0, len(res) - 1)    print res

可根据具体问题选择多种合并的优化方案,自行测试即可。

0 0