知识点7:常见的排序算法--快速排序

快速排序原理

快速排序源于分治策略,是指将一个整体划分为规模更小,但是结构和原理都相同的小部分进行递归操作,最终得出原问题的结果。
恩,看不懂,难理解,怎么办?不怕,我们来举几个假设:
假设1:对数组【9,4】按从小到大进行排序,怎么办呢?那当然是 9>4 ,所以交换两者,结果为【4,9】。多简单,是吧。
假设2:对数组【4,2,9】按从小到大进行排序呢?如果以分治算法为例的话,它是这样的:

1.对确定的数组,选定一个基底,将其作为数组排序的分界线,小的往左,右的往大。在这里,以数组首元素【4】为例进行划分,得出【2,4,9】。还可以是吧。
那么,可能会有人问:怎么去进行划分呢?我们来看一下:
首先,选定了基底【4】,将其保存在变量base中。,取左下标(left)和有下标(right),代表需要排序的数组空间。初始化时一般为left=0;right = 数组的长度-1。
然后,我们从数据右侧开始进行比较,如果数组右侧的数字>基底,则往前移一位(right-1),再进行比较,如此循环,直到右侧的数字<=基底或者left>right时
此时,取其下标,例子中为【1】,如果此时right>left ,则把数组right下标的值赋值给left对应的下标。
接着,从数组的左侧开始比较,如果数组左侧的数字 <= 基底,则往后挪一位(left+1),再进行比较,如此循环,直到左侧的数字>基底或者left>ight时此时,取其下标,例子中为【2】  如果此时left<right ,则把数组left下标的值赋值给right对应的下标。
把base的值赋值给arr[left].
是不是有点似懂非懂的感觉了?不怕,我们再来详细扩展一下:
假设3:对数组【4,2,9,8,3,6,5】按从小到大进行排序呢?
首先,选定基底(4)(一般为数组首元素,因为易于控制)
其次,从右侧开始比较,此时下标(right)=6。
    ->   (5>4)   ->right-1(5),对应值为6  ->   (6>4)  -> right-1(4),对应值为3  -> (3<4) ,停止循环 ,right = 4
    数据交换->  (4>0)    ->a[0]=a[4]  ->[3 2 9 8 3 6 5]
 接着。从左侧开始比较,此时下标(left) = 0;
    ->   (4==4)  ->left+1(1),对应值为2   ->   (2<4)   ->left+1(2),对应值为9  ->  (9>4)  ,停止循环 ,left = 2;
    数据交换->(2 < 4)  ->a[4]=a[2] ->[3 2 9 8 9 6 5]  
最后,将base保存的基底赋值为left ,即a[2] = base ->[3 2 4 9 8 6 5]
看到这里,细心的你是否发现问题了呢?我们一开始确定的基数为【4】,如今经过运算后,发现我们已经以4为分界线。小的往左,有的往大了是吧。是就对应了我们在假设2中说的分治算法的思路。也就是说,我们已经把数组分成了两个小部分。但是这两部分里面数组还是无序的,所以我们就要开始递归操作这个步骤,直到把数组分解两个数字为一组的小部分,按照假设1进行排序,最终得出的结果就是我们想要的啦。我们再耗些脑细胞,继续推断下去吧~
前面说到,经过分治算法的策略,我们把【4,2,9,8,3,6,5】变成了【3,2,4,9,8,6,5】
那么,接下来,我们把数组分成3部分【【3,2】【4】【9,8,6,5】】
因为中间本来是我们的基底,所以不再计算,把目光瞄向左侧和右侧分别再进行分治策略。
左侧为例:
可以假设为:对数组【3,2】按从小到大进行排序:
1.确定基底 3 (base)
2.从右侧开始比较 ,直到 arr[right] <= base ,此时 right = 1 因为 right>left -> a[left]=arr[right] ->【2,2】
3.从左侧开始比较,直到arr[left] >base 但运算后发现right == left(都为1),退出循环
4.将base保存的基底复制为left ,即a[1] = base ->[2 3]
右侧为例:
可以假设为:对数组【9,8,6,5】按从小到大进行排序:
1.确定基底9(base)
2.从右侧开始比较 ,直到 arr[right] <= base ,此时right=3; 因为right>left -> a[left] = arr[right]  ->【5,8,6,5】
3.从左侧开始比较,直到arr[left] >base 但运算后发现right == left(都为3),退出循环
4.将base保存的基底复制为left ,即a[3] = base ->[5,8,6,9]
此时,再次完成分治,可以把数组分成【【5,8,6】,【9】】
继续执行假设:
可以假设为:对数组【5,8,6】按从小到大进行排序:
1.确定基底5(base)
2.从右侧开始比较 ,直到 arr[right] <= base 但运算后得出left == right (都为0),退出循环
3.从左侧开始比较,直到arr[left] > base ,此时left=0;因为 left==right 不执行赋值操作
4.将base保存的基底复制为left ,即a[0] = base ->[5,8,6]
此时,继续完成分治(有人说,有吗,它明明就是不变啊,怎么会分治了?如果我们再以基底为分界线的话,你可能就发现它可以分成【【5】,【8,6】】,这自然就是分治了是吧)
继续假设:对数组【8,6】按从小到大进行排序:
1.确定基底 8 (base)
2.从右侧开始比较 ,直到 arr[right] <= base ,此时 right = 1 因为 right>left -> a[left]=arr[right] ->【6,6】
3.从左侧开始比较,直到arr[left] >base 但运算后发现right == left(都为1),退出循环
4.将base保存的基底复制为left ,即a[1] = base ->[6,8]

好了,到了这一步,我们可算是把整一个分治算法给脑补了一遍,可不可怕,真可怕啊。但是我们也有了收获,不是吗,仔细观察我们的思路,你就发现,我们其实就是不断地把大问题分解成小问题(分治策略),然后再小问题里里面重复着相应的操作,直到得出这个结果(递归)。所以,到了这里,你应该了解分治策略和递归了吧~
好了,既然知道了套路,那么我们就把套路变成代码吧~

快速排序的实现

void quickSort(int[] arr,int low,int height){
/*控制程序,在以上分析中,我们出现基底恰好就是最大或者最小值的情况
*此时,数组不会有两种情况,故而需要对程序进行控制,对不符合条件的数组进行终止
*/
if(low>height){
    return;
    }

//第一步:
int base = arr[low];    
int left = low;
int right = height;
while(left != right){
    //第二步:
    while( left<right  && arr[right] > base){
        right--;
    }
    if(right>left){
        arr[left] = arr[right];
    }
    //第三步:
    while(left<right && arr[left] <= base){
        left++;
    }
    if(left<right){
        arr[right] = a[left];
    }
}
//第四步
arr[left] = base;

quickSort(arr,low,left-1);
quickSort(left+1,height);   
}

至此,我们的快速排序算法算告一段落了,代码中注释的部分建议对着上面的原理里面的思路来分析为什么要这么写,有没有更好的写法之类的,多想这些问题,可以帮助你更好地理解快速排序的思路,所以该勤奋的时候就不要懒惰,赶紧趁现在来梳理一下思路吧~

下一章:知识点8:常见的排序算法–选择排序,敬请期待

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页