三种快排算法理解

快速排序是目前比较常用的排序算法,也是需要掌握的排序算法,光听它的名字就知道这种算法的运算速度很快,没错!这是目前已知的算法中平均排序速率最快的。当然这里是说只使用一种排序算法比较的前提下。

快速排序算法主要分为以下几步:

1)选择基准值
2)双指针操作将小于基准的放左边,大于的放右边
3)重复2操作,直至结束

快速排序算法是利用排序轮数不变,每轮排序只比较了log2n次来提高排序速度,这与堆排序,归并排序的原理类似。但是快速排序没有初始建堆的时间和不占用相同大小排序序列空间的优势。

目前快速排序算法主要有三种:左右指针法,挖坑法,前后指针法

下面我先给出递归操作的代码:

public static void quicksort(int[] arrs,int start,int end){
       if(start>=end)
           return;
       int mid = sort(arrs, start, end);
       quicksort(arrs, start, mid-1);

       quicksort(arrs, mid+1, end);
}

左右指针法:
大致过程是先以arrs[0]作为基准,然后让j往又从左走,i从左往右走,(注意如果以arrs[0]作为基准,那么就必须让j先执行,具体原因下面说),然后当出现arrs[j]小于基准时,arrs[i]大于基准时,交换arrs[i]和arrs[j]的值,然后继续执行。直到i,j相遇,最后交换arrs[start]与arrs[i]的值即可。

public static int sort1(int[] arrs,int start,int end){
       int i=start,j=end;
       int tmp = arrs[i];
       while(i<j){
          while (arrs[j] >= tmp && i<j)
               j--;
           while (arrs[i] <= tmp && i<j)
               i++;
           swap(arrs[i], arrs[j]);
       }
       swap(arrs[i], arrs[start]);
       return i;
}

这种方式也是我们最常见的方式,但是这种方式在测试的时候一不小心就会出现上面的错误,现在我们就说一下出错的原因:

eq:2, 6, 9, 3, 0, 1 -->2, 1, 9, 3, 0, 6–>2, 1, 0, 3, 9, 6 ------------->3, 1, 0, 2, 9, 6

例如上面的一组数,首先以2为基准,第一次循环i指向6,j指向1,i,j交换,到第二列数;

然后进行第二次循环i指向9,j指向0,i,j交换,到第三列数;

此时如果让i先走,i遇到3停止,接着j走,但是i,j相遇,不能继续执行,i,j交换后退出循环,此时交换arrs[start]和arrs[i];到第四列数;

这时就会发现一轮交换下来的数出现了错误,而让j先走的话就不会出现这样的错误,读者可以按照上面的数和代码自己分析。

挖坑法:

大致过程也是先以arrs[0]作为基准并赋给一个临时变量tmp,然后让j往又从左走,i从左往右走,(这种方式也需要j先走),然后当出现arrs[j]小于基准时,将arrs[j]赋值给arrs[i],然后当arrs[i]大于基准,将arrs[i]赋值给arrs[j],继续执行。直到i,j相遇,最后将tmp赋值给arrs[i];

public static int sort2(int[] arrs,int start,int end){
   int i=start,j=end;
   int tmp = arrs[i];
   while(i<j){
       while (arrs[j] >= tmp && i<j)
           j--;
       arrs[i]=arrs[j];
       while (arrs[i] <= tmp && i<j)
           i++;
       arrs[j]=arrs[i];
   }
   arrs[i]=tmp;
   return i;
}

这种方式和它的名字相同,先将arrs[0]的值挖出来赋给tmp,这时只有arrs[0]一个坑,所以也需要j先走,当j退出内循环时,挖出arrs[j]的值赋给arrs[i],此时arrs[j]里面的值就会变成一个废值,接着继续执行,最后再将tmp的值赋给最后一个废坑中。

前后指针法:

大致过程也是先以arrs[0]作为基准并赋给一个临时变量tmp,利用两个指针怕q,p分别指向下一个位置和当前位置,当小于基准时,p跟在q的后面,如果出现大于基准时,q向后移动,p不动,然后当再次出现小于基准的值时且p没有跟在q的后面,p,q位置上的值交换,继续执行,最后将p指向的值与start位置上的值互换。

public static int sort3(int[] arrs,int start,int end){
   int p=start,q = start+1;
   int tmp = arrs[p];
   while(q<=end) {
       while(tmp>=arrs[q] && ++p!=q)
           swap(arrs[p], arrs[q]);
       q++;
   }
   arrs[start] = arrs[p];
   arrs[p] = tmp;
   return p;
}

这是这三种快速排序算法设计思路最奇特的方法,而且这种方法还可以用于链表的排序,个人还是比较欣赏这种算法的设计者,而且这种思路的代码设计也比较巧妙。

以上三种都是快速排序的实现方式,三种方法虽然想法上不太一致。但都达到了快速排序的要求。

缺陷:快速排序也有自身的缺点,因为快速排序对于基准值的要求比较高,如果基准值选择不当,就会导致排序效率严重降低。

eq 1,2,3,4,5,6,7,8

比如上面的这个序列如果使用快速排序并以第一个数为基准值,则排序的时间复杂度则为(n*n),而且快速排序还是一种不稳定的排序。但是这并不能掩盖它的优势,它依旧比较火的排序。

当然,网上对于基准值的选择也有一些优化的手段,具体的方法可以参考下面参照链接。

后续:很遗憾java8中的Collections.sort()方法并没有使用快速排序算法,可能是因为快速排序对于基准值难以把握。那就有点奇怪了,快速排序算法明明号称是速度第一的排序算法,为什么java8不用它,难道编写java8的人不知道吗?当然不是,值得一提的是java8中的sort方法并没有单独的利用某一个排序算法,而是充分利用了八大排序算法的优势,当排序序列小于32时使用折半查找的直接插入算法,当大于32时使用归并排序算法分割序列,序列小于32时依旧使用折半查找的直接插入算法,不过其中还有很多很多的优化策略,例如当一对序列进行归并时,归并算法需要重新分配与之长度相同的一段数组空间,很是浪费空间,但是java8先计算出不需要排序的子序列,然后只new出较短序列长度相同的数组存储临时值,与普通的归并算法比较,可以节省至少一半的空间。再其次是较短序列排序时,使用直插排序更好,所以当分隔到32长度时,选择使用直插算法。然而排序直接插入算法的时间复杂度为(nn),而java8利用折半查找的方式让每一轮比较只进行log2N次,这样总的排序效率可以达到(nlog2N)的效果,而且任何序列都可以保证等等。总的来说,每一种排序算法都有其优势和劣势,但是如果能够充分利用各个排序算法的优势,就能够达到最佳的效果。

参照:快速排序(三种算法实现和非递归实现)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 当然可以。 快速排序算法(Quick Sort)是一种排序算法,是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 基本步骤: 1. 从数列中挑出一个元素,称为“基准”(pivot), 2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。 3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。 快速排序的时间复杂度为O(nlogn)。 示例代码(使用递归实现): ``` def quick_sort(arr, left, right): if left >= right: return pivot = arr[left] l, r = left, right while l < r: while l < r and arr[r] ### 回答2: 当然可以帮您写一个快速排序算法的教案。下面是一个简单的教案示范: 标题:快速排序算法教案 目标:介绍快速排序算法的原理及实现步骤,帮助学生掌握快速排序的基本思想和算法实现。 教学内容: 1. 引入快速排序算法的概念和背景,介绍其在实际应用中的重要性和优点。 2. 解释快速排序算法的基本原理: - 选择一个基准元素(pivot),将待排序数组分成两个子数组。 - 将小于基准元素的元素放在左侧子数组,将大于基准元素的元素放在右侧子数组。 - 对左右子数组递归地进行快速排序,直到排序完成。 3. 用示例图解说明快速排序算法的执行过程,让学生理解其分治策略和递归调用。 4. 根据算法原理,给出快速排序算法的伪代码: - 定义函数 quicksort(arr, low, high): - 如果 low < high: - 设置基准元素 pivot = arr[high] - 设置划分点 i = low - for j 从 low 到 high-1: - 如果 arr[j] < pivot: - 交换 arr[i] 和 arr[j] - i++ - 交换 arr[i] 和 arr[high] - 递归调用 quicksort(arr, low, i-1) - 递归调用 quicksort(arr, i+1, high) 5. 介绍优化和改进的快速排序算法,如随机选择基准元素、三数取中等。 6. 分析快速排序的时间复杂度和空间复杂度,引导学生思考其优缺点以及适用场景。 7. 给出一个能够测试快速排序算法正确性的案例,并让学生通过手动执行算法来验证结果。 8. 布置练习,要求学生独立编写快速排序算法的代码,并对已知数据进行排序实践。 9. 总结快速排序算法的特点和应用范围,鼓励学生深入学习和探索其它算法。 ### 回答3: 当然可以帮您写一个快速排序算法的教案。快速排序是一种常用且高效的排序算法,其基本思想是通过分治的思想将一个待排序的数组分成两个子数组,再对子数组分别进行排序,最终将这些子数组合并成一个有序的序列。 教案的步骤如下: 1. 引入快速排序的概念和背景,并解释快速排序的优点和适用场景。 2. 介绍快速排序的基本思想和算法流程: - 选择一个基准元素(通常选择第一个元素)作为分界点。 - 将比基准元素小的元素移动到基准元素的左边,将比基准元素大的元素移动到右边。 - 对左右两个子数组递归地调用快速排序算法。 3. 使用伪代码或具体的代码示例演示快速排序算法的实现过程,包括递归调用和基准元素的选取。 4. 分析算法的时间复杂度,并解释快速排序的优化方式,如随机选取基准元素或三数取中法。 5. 通过实际例子演示快速排序算法的操作步骤和排序结果。 6. 对比快速排序和其他排序算法(如冒泡排序、插入排序等)的优缺点和性能,并讨论快速排序的应用场景。 7. 提供一些练习题,供学生巩固和深入理解快速排序算法。 在教学中,可以适当地加入一些图示,以便让学生更好地理解算法的执行过程。另外,可以结合编程实践,让学生通过实际编写代码来实现快速排序算法,进一步提高他们的理解和应用能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值