您的位置:首页 > 编程语言 > Python开发

快速排序

2017-01-30 16:49 453 查看
相关的快速排序的解释很容易找到,这里只贴代码

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

#!/usr/bin/python
#coding: utf-8

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 low

def 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-8

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] = res[high], res[low]
# 从前往后遍历,把大于基准点的元素往后移
while low < high and res[low] <= point:
low += 1
# 元素交换
res[low], res[high] = res[high], res[low]

# 返回中间点的位置
return low

def 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-8

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

def 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-8

def 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 low

def 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-8

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 low

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

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
修改的代码如下:

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-8

def 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 low

def 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


可根据具体问题选择多种合并的优化方案,自行测试即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python 排序