|
| 1 | +--- |
| 2 | + |
| 3 | +title: sort-01-bubble sort 冒泡排序算法详解 |
| 4 | +date: 2016-07-14 |
| 5 | +categories: [Algorithm] |
| 6 | +tags: [sort, exchange-sorts] |
| 7 | +published: true |
| 8 | +--- |
| 9 | + |
| 10 | + |
| 11 | +# 排序系列 |
| 12 | + |
| 13 | +[sort-00-排序算法汇总](https://houbb.github.io/2016/07/14/sort-00-overview-sort) |
| 14 | + |
| 15 | +[sort-01-bubble sort 冒泡排序算法详解](https://houbb.github.io/2016/07/14/sort-01-bubble-sort) |
| 16 | + |
| 17 | +[sort-02-QuickSort 快速排序到底快在哪里?](https://houbb.github.io/2016/07/14/sort-02-quick-sort) |
| 18 | + |
| 19 | +[sort-03-SelectSort 选择排序算法详解](https://houbb.github.io/2016/07/14/sort-03-select-sort) |
| 20 | + |
| 21 | +[sort-04-heap sort 堆排序算法详解](https://houbb.github.io/2016/07/14/sort-04-heap-sort) |
| 22 | + |
| 23 | +[sort-05-insert sort 插入排序算法详解](https://houbb.github.io/2016/07/14/sort-05-insert-sort) |
| 24 | + |
| 25 | +[sort-06-shell sort 希尔排序算法详解](https://houbb.github.io/2016/07/14/sort-06-shell-sort) |
| 26 | + |
| 27 | +[sort-07-merge sort 归并排序](https://houbb.github.io/2016/07/14/sort-07-merge-sort) |
| 28 | + |
| 29 | +[sort-08-counting sort 计数排序](https://houbb.github.io/2016/07/14/sort-08-counting-sort) |
| 30 | + |
| 31 | +[sort-09-bucket sort 桶排序](https://houbb.github.io/2016/07/14/sort-09-bucket-sort) |
| 32 | + |
| 33 | +[sort-10-bigfile 大文件外部排序](https://houbb.github.io/2016/07/14/sort-10-bigfile-sort) |
| 34 | + |
| 35 | + |
| 36 | +# 创作目的 |
| 37 | + |
| 38 | +最近想系统整理一下数据库的索引系列,然后就牵扯到了二分查找,二分查找的前提需要排序。 |
| 39 | + |
| 40 | +排序工作中我们用的很多,不过很少去关心实现;面试中,排序的出场率非常高,以此来验证大家是否懂得“算法”。 |
| 41 | + |
| 42 | +无论如何,排序算法都值得每一位极客去学习和掌握。 |
| 43 | + |
| 44 | +# 冒泡排序 |
| 45 | + |
| 46 | +冒泡排序(英语:Bubble Sort)又称为泡式排序,是一种简单的排序算法。 |
| 47 | + |
| 48 | +它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。 |
| 49 | + |
| 50 | +冒泡排序对 n 个项目需要 O(n^2) 的比较次数,且可以原地排序。 |
| 51 | + |
| 52 | +尽管这个算法是最简单了解和实现的排序算法之一,但它对于包含大量的元素的数列排序是很没有效率的。 |
| 53 | + |
| 54 | +## 流程 |
| 55 | + |
| 56 | +冒泡排序算法的运作如下: |
| 57 | + |
| 58 | +1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 |
| 59 | + |
| 60 | +2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 |
| 61 | + |
| 62 | +3. 针对所有的元素重复以上的步骤,除了最后一个。 |
| 63 | + |
| 64 | +4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 |
| 65 | + |
| 66 | +由于它的简洁,冒泡排序通常被用来对于程序设计入门的学生介绍算法的概念。 |
| 67 | + |
| 68 | + |
| 69 | + |
| 70 | +# 代码实现 |
| 71 | + |
| 72 | +## 已有实现的不足 |
| 73 | + |
| 74 | +冒泡排序的文章和实现在网上有很多,讲解的也很详细。 |
| 75 | + |
| 76 | +此处只补充一下自己觉得不足的地方: |
| 77 | + |
| 78 | +(1)大部分实现都只是一个 int 比较的例子,实用性不强。 |
| 79 | + |
| 80 | +(2)没有统一的接口,不便于后期的统一拓展和自适应。(根据不同的数量,选择不同的算法) |
| 81 | + |
| 82 | +(3)没有自适应性的日志输出,不便于学习。 |
| 83 | + |
| 84 | +## 接口定义 |
| 85 | + |
| 86 | +基于上面 3 点,我们做一点小小的改进。 |
| 87 | + |
| 88 | +第一步:统一定义一个排序的接口。 |
| 89 | + |
| 90 | +```java |
| 91 | +package com.github.houbb.sort.api; |
| 92 | + |
| 93 | +import java.util.List; |
| 94 | + |
| 95 | +/** |
| 96 | + * 排序接口 |
| 97 | + * @author binbin.hou |
| 98 | + * @since 0.0.1 |
| 99 | + */ |
| 100 | +public interface ISort { |
| 101 | + |
| 102 | + /** |
| 103 | + * 排序 |
| 104 | + * @param original 原始列表 |
| 105 | + * @since 0.0.1 |
| 106 | + */ |
| 107 | + void sort(List<?> original); |
| 108 | + |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +## 抽象实现 |
| 113 | + |
| 114 | +为了便于后期拓展,统一实现一个抽象父类: |
| 115 | + |
| 116 | +```java |
| 117 | +package com.github.houbb.sort.core.api; |
| 118 | + |
| 119 | +import com.github.houbb.heaven.util.util.CollectionUtil; |
| 120 | +import com.github.houbb.sort.api.ISort; |
| 121 | + |
| 122 | +import java.util.List; |
| 123 | + |
| 124 | +/** |
| 125 | + * 抽象排序实现 |
| 126 | + * @author binbin.hou |
| 127 | + * @since 0.0.1 |
| 128 | + */ |
| 129 | +public abstract class AbstractSort implements ISort { |
| 130 | + |
| 131 | + @Override |
| 132 | + public void sort(List<?> original) { |
| 133 | + //fail-return |
| 134 | + if(CollectionUtil.isEmpty(original) || original.size() == 1) { |
| 135 | + return; |
| 136 | + } |
| 137 | + |
| 138 | + doSort(original); |
| 139 | + } |
| 140 | + |
| 141 | + /** |
| 142 | + * 执行排序 |
| 143 | + * @param original 原始结果 |
| 144 | + * @since 0.0.1 |
| 145 | + */ |
| 146 | + protected abstract void doSort(List<?> original); |
| 147 | + |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | + |
| 152 | +这里很简单,针对空列表,或者大小为1的列表,无需进行排序。 |
| 153 | + |
| 154 | +## 冒泡排序 |
| 155 | + |
| 156 | +接下来我们实现以下冒泡排序即可: |
| 157 | + |
| 158 | +有几点需要说明下: |
| 159 | + |
| 160 | +(1)这里是对 Comparable 对象的支持,本质上和常见的 int 比较一样,这样的适用范围更加广泛一些。 |
| 161 | + |
| 162 | +(2)这里是基于 java 的 list 进行排序,因为个人认为 list 的出场率是高于数组的,当然大家如果想实现数组版本的,也是类似的。 |
| 163 | + |
| 164 | +(3)changeFlag 也就是我们常说的针对冒泡排序的优化,如果没有变更,说明排序完成,可以直接返回了。 |
| 165 | + |
| 166 | +```java |
| 167 | +package com.github.houbb.sort.core.api; |
| 168 | + |
| 169 | +import com.github.houbb.heaven.annotation.ThreadSafe; |
| 170 | +import com.github.houbb.log.integration.core.Log; |
| 171 | +import com.github.houbb.log.integration.core.LogFactory; |
| 172 | + |
| 173 | +import java.util.List; |
| 174 | + |
| 175 | +/** |
| 176 | + * 冒泡排序 |
| 177 | + * @author binbin.hou |
| 178 | + * @since 0.0.1 |
| 179 | + */ |
| 180 | +@ThreadSafe |
| 181 | +public class BubbleSort extends AbstractSort { |
| 182 | + |
| 183 | + private static final Log log = LogFactory.getLog(BubbleSort.class); |
| 184 | + |
| 185 | + @Override |
| 186 | + @SuppressWarnings("unchecked") |
| 187 | + public void doSort(List<?> original) { |
| 188 | + boolean changeFlag; |
| 189 | + |
| 190 | + for(int i = 0; i < original.size()-1; i++) { |
| 191 | + changeFlag = false; |
| 192 | + |
| 193 | + for(int j = 0; j < original.size()-1-i; j++) { |
| 194 | + // 如果 j > j+1 |
| 195 | + Comparable current = (Comparable) original.get(j); |
| 196 | + Comparable next = (Comparable) original.get(j+1); |
| 197 | + if(current.compareTo(next) > 0) { |
| 198 | + swap(original, j, j+1); |
| 199 | + changeFlag = true; |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + // 如果没发生置换,说明后面已经排序完成 |
| 204 | + if(!changeFlag) { |
| 205 | + return; |
| 206 | + } |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + /** |
| 211 | + * 执行数据的交换 |
| 212 | + * @param original 原始 |
| 213 | + * @param i 第一个 |
| 214 | + * @param j 第二个 |
| 215 | + * @since 0.0.1 |
| 216 | + */ |
| 217 | + @SuppressWarnings("unchecked") |
| 218 | + private void swap(List original, |
| 219 | + int i, int j) { |
| 220 | + Object temp = original.get(i); |
| 221 | + original.set(i, original.get(j)); |
| 222 | + original.set(j, temp); |
| 223 | + } |
| 224 | + |
| 225 | +} |
| 226 | +``` |
| 227 | + |
| 228 | +## 工具类 |
| 229 | + |
| 230 | +为了让这个实现类使用起来更加方便,我们就模仿一下 jdk 中的方法。提供一个工具类: |
| 231 | + |
| 232 | +```java |
| 233 | +package com.github.houbb.sort.core.util; |
| 234 | + |
| 235 | +import com.github.houbb.heaven.support.instance.impl.Instances; |
| 236 | +import com.github.houbb.sort.core.api.BubbleSort; |
| 237 | + |
| 238 | +import java.util.List; |
| 239 | + |
| 240 | +/** |
| 241 | + * 排序工具类 |
| 242 | + * @author binbin.hou |
| 243 | + * @since 0.0.1 |
| 244 | + */ |
| 245 | +public final class SortHelper { |
| 246 | + |
| 247 | + private SortHelper(){} |
| 248 | + |
| 249 | + /** |
| 250 | + * 冒泡排序 |
| 251 | + * @param <T> 泛型 |
| 252 | + * @param list 列表 |
| 253 | + * @since 0.0.1 |
| 254 | + */ |
| 255 | + public static <T extends Comparable<? super T>> void bubble(List<T> list) { |
| 256 | + Instances.singleton(BubbleSort.class).sort(list); |
| 257 | + } |
| 258 | + |
| 259 | +} |
| 260 | +``` |
| 261 | + |
| 262 | +# 测试 |
| 263 | + |
| 264 | +我们来验证一下排序算法。 |
| 265 | + |
| 266 | +## 测试代码 |
| 267 | + |
| 268 | +```java |
| 269 | +List<Integer> list = RandomUtil.randomList(5); |
| 270 | +System.out.println("开始排序:" + list); |
| 271 | +SortHelper.bubble(list); |
| 272 | +``` |
| 273 | + |
| 274 | +其中 RandomUtil 是一个随机生成的工具,便于我们测试,实现如下: |
| 275 | + |
| 276 | +```java |
| 277 | +package com.github.houbb.sort.core.util; |
| 278 | + |
| 279 | +import java.util.ArrayList; |
| 280 | +import java.util.List; |
| 281 | +import java.util.Random; |
| 282 | +import java.util.concurrent.ThreadLocalRandom; |
| 283 | + |
| 284 | +/** |
| 285 | + * @author binbin.hou |
| 286 | + * @since 0.0.1 |
| 287 | + */ |
| 288 | +public final class RandomUtil { |
| 289 | + |
| 290 | + private RandomUtil(){} |
| 291 | + |
| 292 | + /** |
| 293 | + * 随机列表 |
| 294 | + * @param size 大小 |
| 295 | + * @return 结果 |
| 296 | + * @since 0.0.1 |
| 297 | + */ |
| 298 | + public static List<Integer> randomList(final int size) { |
| 299 | + List<Integer> list = new ArrayList<>(size); |
| 300 | + |
| 301 | + Random random = ThreadLocalRandom.current(); |
| 302 | + for(int i = 0; i < size; i++) { |
| 303 | + list.add(random.nextInt(100)); |
| 304 | + } |
| 305 | + |
| 306 | + return list; |
| 307 | + } |
| 308 | +} |
| 309 | +``` |
| 310 | + |
| 311 | +## 日志 |
| 312 | + |
| 313 | +为了便于理解,我们可以在排序实现中加一点日志: |
| 314 | + |
| 315 | +```java |
| 316 | +if(current.compareTo(next) > 0) { |
| 317 | + swap(original, j, j+1); |
| 318 | + changeFlag = true; |
| 319 | + if(log.isDebugEnabled()) { |
| 320 | + String format = String.format("i=%s, j=%s, c=%s, n=%s, 排序后: %s", |
| 321 | + i, j, current, next, original); |
| 322 | + log.debug(format); |
| 323 | + } |
| 324 | +} else { |
| 325 | + if(log.isDebugEnabled()) { |
| 326 | + String format = String.format("i=%s, j=%s, c=%s, n=%s, 无变化: %s", |
| 327 | + i, j, current, next, original); |
| 328 | + log.debug(format); |
| 329 | + } |
| 330 | +} |
| 331 | +``` |
| 332 | + |
| 333 | +i,j 代表本次循环的 i,j; c 代表当前值,n 代表 next 下一个值。 |
| 334 | + |
| 335 | +测试日志如下: |
| 336 | + |
| 337 | +``` |
| 338 | +开始排序:[77, 48, 10, 8, 28] |
| 339 | +
|
| 340 | +i=0, j=0, c=77, n=48, 排序后: [48, 77, 10, 8, 28] |
| 341 | +i=0, j=1, c=77, n=10, 排序后: [48, 10, 77, 8, 28] |
| 342 | +i=0, j=2, c=77, n=8, 排序后: [48, 10, 8, 77, 28] |
| 343 | +i=0, j=3, c=77, n=28, 排序后: [48, 10, 8, 28, 77] -- 第一次冒泡,把最大的 77 排序到最右侧 |
| 344 | +
|
| 345 | +i=1, j=0, c=48, n=10, 排序后: [10, 48, 8, 28, 77] |
| 346 | +i=1, j=1, c=48, n=8, 排序后: [10, 8, 48, 28, 77] |
| 347 | +i=1, j=2, c=48, n=28, 排序后: [10, 8, 28, 48, 77] -- 第二次冒泡,把第二大的 48 排序到最右侧 |
| 348 | +
|
| 349 | +i=2, j=0, c=10, n=8, 排序后: [8, 10, 28, 48, 77] -- 这次排序结束后, 28 和 10 已经放在了对应的位置 |
| 350 | +i=2, j=1, c=10, n=28, 无变化: [8, 10, 28, 48, 77] |
| 351 | +
|
| 352 | +i=3, j=0, c=8, n=10, 无变化: [8, 10, 28, 48, 77] |
| 353 | +``` |
| 354 | + |
| 355 | +这个算法,是把小的值放在前面,所以只有当 c > n 的时候,才会发生排序。 |
| 356 | + |
| 357 | +最后 i = 6, j = 2 的时候,已经排序完成。 |
| 358 | + |
| 359 | +# 开源地址 |
| 360 | + |
| 361 | +为了便于大家学习,上面的排序已经开源,开源地址: |
| 362 | + |
| 363 | +> [https://github.com/houbb/sort](https://github.com/houbb/sort) |
| 364 | +
|
| 365 | +欢迎大家 fork/star,鼓励一下作者~~ |
| 366 | + |
| 367 | +# 小结 |
| 368 | + |
| 369 | +希望本文对你有帮助,如果有其他想法的话,也可以评论区和大家分享哦。 |
| 370 | + |
| 371 | +各位**极客**的点赞收藏转发,是老马持续写作的最大动力! |
| 372 | + |
| 373 | +# 参考资料 |
| 374 | + |
| 375 | +[Bubble_sort](https://en.wikipedia.org/wiki/Bubble_sort) |
| 376 | + |
0 commit comments