3.29《算法图解》笔记——Chapter 4 Quick Sorting

算法图解笔记——Chapter 4 Quick Sorting
Author: Seven Zou
Email: zoushiqi0404@gmail.com
Language: Python2.7


4 快速排序

  • 一种经典的递归类方法,D&C (Divide and Conquer)
  • 在上述方法基础上,了解快速排序

4.1 D&C

本节所讲延续了上一个Chapter中Recusive概念。D&C所表达的是一种解决方法,一种解决问题的思路。针对这种方法,打算借用书中的例子来记录学习。
Case 1:假设你有一块土地,打算将其均匀分割成方块,并分出的方块尽可能大。
在这里插入图片描述
如下图所示,分法各式各样,但是不能保证分出来的方块是最大的。
在这里插入图片描述
这时引入D&C策略进行解决问题,D&C算法是递归的,满足如下条件。

  • 1.找出基线条件,这种条件必须尽可能简单;
  • 2.不断将问题分解(或者说缩小规模),直到符合基线条件。

e.g. 如果一条边 25 c m 25cm 25cm,另一边长 50 c m 50cm 50cm,那么可使用的最大方块为 25 m × 25 m 25m \times 25m 25m×25m。则对这块地分割完成。

Solution to Case 1:
路线:1.找出基线条件。2. 不断缩小规模,直到符合基线条件。

  • 1.可以对这块土地的长进行以64基数进行分割,但会余 400 m 400m 400m长。
    在这里插入图片描述

  • 2.此处Attention,那也可对余下的土地进行相同的分割操作。有结论适用于这小块地的最大方块,也是适用于整块地的最大方块。[Euclid Algorithm]那么由此结论,问题就可以被逐步简化,变成了分割 640 m × 400 m 640m \times 400m 640m×400m的问题。

  • 3.以此类推,余下的土地会被继续分割。

  • 4.最终,会被分为最大方块为 80 m × 80 m 80m \times 80m 80m×80m的最小单元。
    在这里插入图片描述

Case 2:对数组[1 2 3]求和。
在学习本节之前,我的第一反应也是应用循环进行求和。

def sum(arr):
	total = 0
	for x in arr:
		total += x
	return total

print sum([1, 2, 3])

Solution to Case2:应用递归思想进行求解的话。
-1.找出基线条件。继续简化问题,可以容易的得出,其最小单元为 ∅ , n o   e l m e n t \empty,no \text{ } elment ,no elment或者 α , o n e   e l m e n t \alpha,one \text{ } elment α,one elment
-2.缩小规模。 s u m ( [ 1   2   3 ] ) sum([1 \text{ } 2 \text{ } 3]) sum([1 2 3]) 1 + s u m ( [ 2   3 ] ) 1+sum([2\text{ }3]) 1+sum([2 3])相比,显然是后者可以达到缩小规模的效果。所以,可以应用此种方法进行解决本Case。


4.2 快速排序

本节可接3.27节选择排序之后。两者相较,快速排序更块得多。继续Case2讨论。
在Case2中,建立了最小单元,也是根本不需要进行排序得数组。因此,在这种情况下,只需要原样返回数组(不用排序)。

def quicksort(array):
	if len(array) < 2:
		return array

但针对多元素数组而言,就会变得麻烦了许多。引入快速排序的路线:
-1.在数组中选择一个元素,称为基准值(pivot);
-2.找出比基准值小、和比基准值大的元素,这一动作称为分区(partitioning);
-3.虽然此时得到了三部分,[小于基准值的子数组][基准值][大于基准值的子数组],但是子数组是无序的,这里就可对子数组进行快速排序,再进行合并就可以得到一个有序数组。

quicksort([15,10] + [33] +quicksort([]))
Output:[10,15,33]

Code for 快速排序:

def quicksort(array):
	if len(array) < 2:
		return array	# 基线条件:为空或只包含一个元素的数组是“有序”的
	else:
		pivot = array[0]	# 递归条件
		less = [i for i in array[1:] if i <= pivot]
		# 由所以小于基准值的元素组成的子数组
		greater = [i for i in array[1:] if i > pivot]
		# 由所以大于基准值的元素组成的子数组
	return quicksort(less) + [pivot] + quicksort(greater)

print(quicksort([10,5,2,3]))
print(quicksort([10,5,2,3,11,15,12]))
Output:[2, 3, 5, 10]
		[2, 3, 5, 10, 11, 12, 15]
4.3 大 O O O表示法

如下给出常见的大 O O O运行时间。
在这里插入图片描述
还有一种合并排序(merge sort),其运行时间为 O ( n l o g n ) O(n log n) O(nlogn),比选择排序快。快速排序根据情况不同其运行时间也不同,在平均情况下为
O ( n l o g n ) O(n log n) O(nlogn),在最糟情况下为 O ( n 2 ) O(n^2) O(n2)

-1.合并排序
对于如下打印列表中每个元素的简单函数。思路为遍历列表中的每个元素并将其打印出来,迭代整个列表一次,运行时间为 O ( n ) O(n) O(n)

def print_items(list):
	for item in list:
		print item

如使其打印每个元素前都休眠1秒钟,则修改如下。

from time import sleep
def print_items2(list):
	for item in list:
		sleep(1)
		print item

如输入[2 4 6 8 10],则两函数的输出实际上是不同的。

print_items:   2 4 6 8 10
print_items2: 2 <sleep> 4 <sleep> 6 <sleep> 8 <sleep> 10

虽然二者在大 o o o表示法里速度相同,但实际上,还是print_items更快一些。 c c c是算法所需的固定时间量,被称为常量。比如print_items可以需要 10 m s × n 10ms \times n 10ms×n,而print_items2需要 1 s × n 1s \times n 1s×n。通常不考虑这个常量,因为如果两种算法的大 O O O运行时间不同,这种常量无关紧要。但有时,其影响可能很大,快速查找和合并查找就是一个case,快速查找的常量比合并查找要小,所以在运行时间都是 O ( n l o g n ) O(nlogn) O(nlogn)情况下,快速查找的速度要更快。(其平均情况比最糟情况更多)

-2. 平均情况和最糟情况

快速排序的性能高度依赖于所选的基准值。假设选取总是把第一个元素作为基准值,那么数组没有分为两部分,子数组中始终有一个为空,调用栈会占用很长。在这里插入图片描述
假设总是选取中间元素作为基准值那么调用栈会短的多。
在这里插入图片描述
针对第二种假设,层数(调用栈的高度)为 O ( l o g n ) O(logn) O(logn),而每层需要时间为 O ( n ) O(n) O(n)。则整个算法需要运行时间为 O ( n ) × O ( l o g n ) = O ( n l o g n ) O(n) \times O(logn)=O(nlogn) O(n)×O(logn)=O(nlogn),此为最佳情况(平均情况)。
在第一种假设,层数为 O ( n ) O(n) O(n),则整个运行时间 O ( n ) × O ( n ) = O ( n 2 ) O(n) \times O(n)=O(n^2) O(n)×O(n)=O(n2),此为最糟情况。
只要每次都随机地选择一个数组元素作为基准值,那么快速排序的平均运行时间就为 O ( n l o g n ) O(nlogn) O(nlogn)

Reference

[美]Aditya Bhargava/袁国忠, 算法图解, 北京:人民邮电出版社, 2017.3.


附个人Github地址: https://github.com/shiqi0404/Algorithm_Diagram,其中包括笔记、Code还有书本pdf版。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值