继续读啊哈磊算法有感系列,继续升华。上一篇是冒泡排序,在结尾总结了一下冒泡排序的缺点——时间复杂度O(N*N)太大。这一篇来说一下快速排序,快速排序可以在多数情况下克服冒泡排序的缺点(最坏的情况下和冒泡排序的时间复杂度一样)。下面我们先来说说快速排序的思想与过程,与上一篇从过程到思想的思考方式不同,这一次我们的思考过程是 从思想到过程 ——
快速排序的思想:
利用二分的思想,先在待排序子数组中选定一个基准数(作为中间值)、一个左初始位(待排序子数组的左端点位。若对整个数组进行排序,则左端点位为数组的0索引位),一个右初始位(待排序子数组的右端点位。若对整个数组进行排序,则右端点位为数组的length-1处索引位),然后找到一个位置,该位置左侧的数均小于基准数,右侧的数均大于基准数。在此基础上,对待排序子数组进行二分,分别为待排序子数组中基准数以左,左初始位以右的孙子数组1;和待排序子数组中基准数以右,右初始位以左的孙子数组2。之后进行递归,将孙子数组1和2分别作为待排序子数组进行同上排序。
快速排序的过程(与上一篇中思想就是对过程的抽象与总结相反,过程就是对思想的具体与落实):
1、选定待排序子数组及其基准数、左初始位、右初始位(若左初始位小于右初始位则继续,否则停止);
2、找到基准数的交换位置,在左初始位和右初始位分别安排一名哨兵,先是右哨兵向左走,直到找到第一个比基准数小的数为止;然后是左哨兵向右走,直到找到第一个比基准数大的数为止。交换左右哨兵位置处的数值,然后哨兵继续前行(先右后左),继续交换,直到左右哨兵相遇前,停止。至此第一轮排序结束了,左哨兵以左都小于等于基准数,右哨兵以右都大于等于基准数;
3、交换左哨兵和基准数的数值,至此基准数以左均小于等于基准数,基准数以右都大于等于基准数;
4、以基准数所在的位置对待排序子数组进行二分,分为孙子数组1和2;
5、对孙子数组1和2进行递归,回到第一步,孙子数组1的左初始位为待排序子数组的左初始位并依然以左初始位的数为新的基准数、右初始位为基准数位-1,孙子数组2的左初始位为基准数位+1并以左初始位的数位新的基准数、右初始位为待排序子数组的右初始位。
等到孙子数组2的左初始位和孙子数组1的右初始位相遇时,这场排序就结束了。整个待排序子数组将会按照从小到大进行排序。
接下来我们将具体的过程转换为代码:
#Exchange the two value. function ExchangeValue($i,$j, $studentScore, $studentName) { #Exchange the score. $s = $studentScore[$i] $studentScore[$i] = $studentScore[$j] $studentScore[$j] = $s #Exchange the name. $n = $studentName[$i] $studentName[$i] = $studentName[$j] $studentName[$j] = $n } function SortScores($left, $right, $studentScore, $studentName) { if($left -le $right) { #Sort begin. #The $left is base number, the $left and $right also represents the location of the sentries' start points. $i = $left $j = $right #The right sentry must go first every time while the sentries not meet each other. while($i -ne $j) { while(($studentScore[$j] -ge $studentScore[$left]) -and ($j -gt $i)) { $j-- } #And then the left one. while(($studentScore[$i] -le $studentScore[$left]) -and ($i -lt $j)) { $i++ } #If the left entry doesn't meet the right entry, exchange the left and right number. If($i -lt $j) { ExchangeValue $i $j $studentScore $studentName } } #Exchange the value from the locations of the left entry and the base number. ExchangeValue $left $i $studentScore $studentName #Now "$i" is the new location of the base number. #Sort the new left part. $newRight = $i-1 $newLeft = $i+1 SortScores $left $newRight $studentScore $studentName SortScores $newLeft $right $studentScore $studentName } } function PrintSortedScores($count, $studentScore, $studentName){ #Print the sorted result. Write-Host "Below is the sorted result:" for($i=0;$i -le $count-1;$i++) { $j=$i+1 $tip = "NO."+$j+":"+$studentName[$i]+",score:"+$studentScore[$i] Write-Host $tip -ForegroundColor Green } } #This is the entry of this program. #Collect the students' scores. $count = Read-Host "Enter the amout number of the students" #Convert to int type. $count = [int]$count $studentName = New-Object System.Collections.ArrayList $studentScore = New-Object System.Collections.ArrayList for($i=1;$i -le $count;$i++) { $name = Read-Host "Enter the name" $score= Read-Host "Enter the score" #Convert to int type. $score = [float]$score $studentName.Add($name) $studentScore.Add($score) } $leftNum = 0 $rightNum = $count - 1 SortScores $leftNum $rightNum $studentScore $studentName PrintSortedScores $count $studentScore $studentName
函数SortScores是快速排序算法的核心,其中关键的判断我用 橙色 高亮了出来。
在PowerShell中运行这段代码,并输入一些测试数据,结果如下:
冒泡排序的交换距离是相邻的两个元素,而快速排序中左右哨兵并不是相邻的,所以交换的两个元素也不是相邻的,最坏的情况,左右哨兵碰头的时候交换才会交换相邻的两个元素。如果每次都是最坏的情况(每次都是左右哨兵相邻才交换),那快速排序的时间复杂度和冒泡排序是一样的。但只要不是最坏的情况,快速排序的时间复杂度都要小于冒泡排序,它的平均时间复杂度是O(NlogN)。