【快速排序、合并排序与分治思想】
(Quick Sort、 Merge Sort、 and Divide &Conquer)
本教程仅供分享与学习使用,不可最为商业用途
一、 如何将递归算法转换为代码:
将一个递归算法实现为代码时,不能去考虑递归每一步的实现细节,因为如果考虑递归算法每一步的实现细节会把人累死的,比如考虑一个10000步的递归算法,每次递归一半,这样就要考虑log210000次递归步骤。递归算法主要要找到递归的终点也就是递归边界,然后去思考递归算法的其中一步是怎么实现的即可。比如,在二分查找用递归实现时,在找到需要寻找的元素后,递归就不用继续了,那么这个就条件就奠定递归边界。再去考虑二分查找的其中一步是如何运行的,这就要去想二分查找的基本思想,就是将一个有序的数组折半查找,如果折半元素大于需要寻找元素的值,函数就去递归查找它的左半边,如果小于需要查找元素的值函数就去递归查找它的右半边直到找到那个元素为止。
注:
递归终点决定了函数什么时候停止,而算法的基本思想决定了递归的每一步该如何实现~
二、 分治思想(Divide and Conquer)
分治思想就是用函数递归自调用的方法将一个非常复杂的问题分解为很多很简单的问题去解决,再将这些简单问题组合起来便得到了原来那个复杂问题的解。这种思想跟数学归纳法很像,比如在玩多美乐骨牌时,一个被摆了很长的多美乐骨牌,如果每个骨牌倒了都可以使下一个骨牌倒(递归过程),那么我们推倒第一个骨牌(第一步递归),所有骨牌就全部倒了,最后使得距离我们很远的最后一个骨牌倒下(最后一个骨牌便是我们需要的解)。在最后一个骨牌倒之前所有倒下的骨牌便是分治思想中每个子问题的解。
三、 合并排序(Merge Sort)
1、 合并排序算法思想:
在想合并排序的基本思想之前,让我们先假设我们有两摞扑克,每摞扑克都已经排好序,假设我们现在想让这两摞扑克组合为一摞扑克,并且组合后的扑克依然是有序的,该怎么办呢?这时我们就需要将这两摞扑克有数字的那一面朝着我们以便于我们可以看到每摞扑克的数字,然后我们比较这两摞扑克最上面两张牌的大小,比较小的那张就从扑克中取出并将这张扑克放到其他位置,然后接着比较这两摞扑克最上面两张扑克的大小,再将比较小的那张取出放到刚才已被取出扑克的上面,循环这个步骤(我们会得到第三摞有序扑克),直到其中一摞扑克被全部取完,那么现在桌面上就只剩下两摞扑克了,将未取完的那一摞扑克放到第三摞扑克的上面,便最后得到了一摞有序的扑克,两摞扑克的排序便完成了。
合并排序的思想其实跟排序两摞有序扑克的方法是一样的,只不过刚才是将一副扑克分成了两摞,而合并排序是将一副有x张牌的无序扑克,分成了x摞扑克,再两两用刚才合并两摞扑克的方法进行合并最终得到一摞有序的扑克。
2、下面我们用PseudoCode来对合并排序进行解释:
(1)分解一摞无序扑克mergeSort函数
mergeSort(A[0…n],p, q)
/*该段中的数组A为一摞无序扑克,p为第一张扑克所在的位置,q为末尾扑克所在的位置*/
If p < q then
/*正因为p要小于q是因为当p等于或大于q时,已经将扑克牌分为了x摞且每摞只有一张扑克牌,已经没法继续分解下去,这边是递归边界*/
mid= (p+q)/2
/*mid为没摞扑克中间那张扑克所在的位置*/
mergeSort(a,p,mid)/*将扑克均分为两摞中的第一摞扑克*/
mergeSort(a,mid+1,q)/*将扑克均分为两摞中的第二摞扑克,分解在每摞只剩一张时结束*/
merge(a,p,mid,q)
/*在扑克分解结束后,用merge函数将每摞扑克用刚才组合两摞有序扑克的方法将每摞扑克进行两两组合,最后组合为一摞有序扑克*/
(2)用merge函数组合每摞有序扑克(刚开始组合每摞只有一张必定是有序的)
Merge(A(0…n),p,mid,q)
/*之所以要传递p,mid,q是因为只有有了p,mid,q才可以找到要合并的两摞扑克,第一罗扑克是第p张到第mid张,第二摞是第mid+1张到q张*/
P1= p
P2= mid + 1
LetB[0…100] be a new array /*数组B是放第三摞牌的位置*/
cnt= 0/*cnt表示第三摞牌已经放了扑克的个数*/
WhileP1≤mid and P2≤q
do
ifA[P1] ≤ A[P2] then
B[cnt++]= A[P1]
P1=P1+1
ifA[P2]≤A[P1] then
B[cnt++]= A[P2]
P2=P2+1
/*这两个if语句是比较两摞牌牌面上的那两张牌哪个小就将哪张扑克放入第三摞*/
/*while语句结束时,一摞扑克已经全部放完,仅还剩下一摞扑克*/
IfP1 > mid then
Fori=P2 to q
Do
B[cnt++]= A[i]
Else
ForI = P1 to mid
Do
B[cnt++]= A[i]
/*这个lf else语句是将剩下的一摞扑克放入第三摞*/
四、 快速排序(Quick Sort)
1、 快速排序基本思想:
快速排序是将个数组的最后一个数作为关键数字,比这个关键数字小的数字全部放到这个关键数字的左边,比这个关键数字大的数字全部放到这个关键数字的右边,然后将这个关键数字左边的所有数字看成一个新的数组,这个关键数字右边的所有数字看成另外一个新的数组(每步递归实现方法),再继续按这个方法进行分解,直到分解到关键数字的左边没有数字,右边也没有数字为止(递归边界),整个数组的排序就已经完成。虽然这种思想也用到了分治的思想,但是此思想不需要合并,因为将这组数字全部分解完就已经是一个排好序的数组了。
2. 下面我们用PseudoCode 来对快速排序进行解释:
quickSort(A[0….n],p,q)
/*p为一个数组的第一个数字的指针位置,q为一个数组最后一个数字的指针位置*/
Ifp < q then
/*找到递归边界,当p=q时,分解就只剩下关键数字了*/
Key= findKey(A[0…n],p, q)
/*key为关键数字。findKey函数使得关键数字的左边都是小于它的数,关键数字的右边都是大于它的数。然后再在quickSort函数中一次递归调用自身分解直至递归边界排序结束*/
quickSort(A,p,key-1)/*关键数字左边的新数组*/
quickSort(A,key+1,q)/*关键数字右边的新数组*/
else
return
/*findKey函数找到关键数字,并且将关键数字左边都是小于它的数,右边都是大于它的数*/
findKey(A, p, q)
i= p – 1/*变量i为比关键数字小的数字放置位置*/
forj = p to q do
ifA[j] ≤ A[q] then
I= I+1
Temp= A[j]
A[j]= A[i]
A[i]= temp
/*这个for循环是从数组第一个元素开始找起只要小于关键数字也就是数组最后一个元素就将该数字一次从最左边开始一次往后放置*/
Temp= A[q]
A[q]= A[i+1]
A[i+1]= temp
ReturnI +1
/*上面这三个语句是将关键数字放置到比他小的所有元素与比他大的所有元素的中间*/
-S tephen.Bo