zoukankan      html  css  js  c++  java
  • 数据结构学习笔记2:尝试手写一个冒泡排序

    说起数组排序,我们大家都经常会用到Arrays.sort()方法或者是直接使用其他封装好的轮子的排序方法。这些API虽然大家都会用,偶尔静下心来扪心自问,我真的知道数组排序是怎么玩儿的嘛?还是我只是会用API而已?说到这里,很多同学就会说:“不要重复造轮子,有现成你不用非得自己写是怎么回事儿,你写的就比人家的好吗?”

    这里我澄清一下,我不是说自己写的就好。首先自己写的轮子大概率是不如大厂的巨巨们封装出来的轮子的,但是为什么还要写呢?原因很简单,是为了学习或者说为了明白为什么这样做。有一位对我影响很深的前辈说过一句话,我觉得让我受用终身。

    不要重复造轮子的前提是你得有能造轮子的能力!

    如果自己连轮子都造不出来,谈何重复造呢?所以啊,我觉得有现成的轮子了并不是我们不学习的理由,而这些轮子恰恰是我们前进的方向和奋斗的动力。都是开发工程师,我为什么实现不了这个功能?我为什么做不到这么好?他们是怎么做的?咳咳咳……扯远了。我们还是回过头来自己动脑思考一下并动手写一下目前常见的数组排序算法吧。

    我们先从冒泡排序开始


    冒泡排序

    说起冒泡排序这可能是绝大多数同学听说过甚至自己动手写过的第一个排序算法,那你还记得冒泡排序的特点是什么吗?百度百科对其的说明是:

    它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

    这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

    看明白了吗?如果没看明白也没有关系,我们通过画图+举例的方式来走一遍这个思想。我个人觉得画图+举例的方式来学习和理解数据结构和算法是一种捷径,当明白了其核心思想后,通过画图+举例就可以更清晰的明白我们每一步该干什么。最后只是将你的想法翻译成代码交给计算机就可以了。现在我们先举个例子,假设我们有数组items,它的长度为5,包含元素及其顺序为:4 3 6 8 7 为了方便大家学习,我先画张图来表述一下这个数组,当然我画的这张图每个元素之间有空隙,不符合数组是连续的内存空间的规定,但是这样比较方便理解,大家无视就好。

    我们要一次比较两个相邻的元素,如果顺序错误就把他们交换过来,放在图上就是,我们依次比较下标 0-1 1-2 2-3 3-4 如果其中有顺序错误的我们就交换这两个下标的值。过程如图所示:

    那我们现在既然理解其原理也通晓其执行过程了,那我们只需要将其翻译成代码就可以了。

        /**
         * 冒泡排序算法
         * @param items 需要排序的数组
         */
        private static void bubbleSort(int[] items) {
            int length = items.length;
            for (int i = 0; i < length; i++) {
                for (int j = 0; j < length - i -1; j++) {
                    // 如果当前元素比其后相邻的元素要大,则不符合规则进行交换
                    if (items[j] > items[j+1]) {
                        int temp = items[j];
                        items[j] = items[j+1];
                        items[j+1] = temp;
                    }
                }
            }
        }
    

    怎么样,是不是很好理解?如果你看代码还是没有看明白的话,我建议还是通过画图+举例的方式再对着代码走一次,我敢保证你一定有收获!但是我们这个代码只是一个Demo级别,如果你真的想在系统中用的话,那我们还得对其进行一些打磨。

    如果作为一个生产级别的代码的话,我们为了避免其错误,我们首先要对方法传入值的边界值进行测试。那放到刚才的方法里面,我们就要考虑:

    1. 数组items为空可以吗?
    2. 数组items长度为1可以吗?

    那我们依次来看一下这些边界值,先看第一个参数items的边界值。items是空或者长度是1我们的代码还可以运行吗?当然可以,但是里面实际的交换代码不会执行罢了。我们来看,如果items是空的,那么length是0。当代码执行到for (int i = 0; i < length; i++) 这一行的时候,会发现int i不符合i < length这个条件,因为0不肯能小于0啊。所以里面的代码全部都不会执行了,方法结束了;那如果items的长度是1呢,我们再来看啊。首先for (int i = 0; i < length; i++) 这个代码执行没有问题,但是里面的for (int j = 0; j < n - i - 1; ++j) {又出问题了,因为1-0-1 = 0,j<0的判断又为false了,因为0不可能小于0,所以里面的代码也不会执行了,这个for循环直接结束,进入i++操作,下次判断i < length因为i和length的值都是1,这个for循环也结束了,然后方法结束。

    虽然这么看来items为空或者其中只包含一个元素,对我们的代码执行没有影响。但是这样真的是最优解吗?我们可以思考一下,如果数组中没有元素或者只有一个元素,那么这个数组有必要进行排序吗?当然没有啦。那根据我们考虑的情况,我们可以对代码进行一下优化,如果数组为空或者数组中只包含一个元素就直接退出方法。

        /**
         * 冒泡排序算法
         * @param items 需要排序的数组
         */
        private static void bubbleSort(int[] items) {
            int length = items.length;
            if (length >= 1) {
                return;
            }
            for (int i = 0; i < length; i++) {
                for (int j = 0; j < length - i -1; j++) {
                    if (items[j] > items[j+1]) {
                        int temp = items[j];
                        items[j] = items[j+1];
                        items[j+1] = temp;
                    }
                }
            }
        }
    

    然后我们接着来看这段代码,如果放到生产上可以运行了吗?这已经是最优解了吗?我们再来看一下我们之前举得那个例子:

    我们发现第一次循环之后就已经是排序之后结果了,后面的每次循环都没有意义了。那这样,我们的循环是不是在空跑浪费时间?我们应该怎么优化呢?相信很多有经验的同学都想到了加标志符。那我们再修改一下代码。

        /**
         * 冒泡排序算法
         * @param items 需要排序的数组
         */
        private static void bubbleSort(int[] items) {
            int length = items.length;
            if (length >= 1) {
                return;
            }
            for (int i = 0; i < length; i++) {
                boolean flag = false;
                for (int j = 0; j < length - i -1; j++) {
                    if (items[j] > items[j+1]) {
                        int temp = items[j];
                        items[j] = items[j+1];
                        items[j+1] = temp;
                        // 表示有数据交换
                        flag = true;
                    }
                }
                // 没有数据交换,提前退出
                if (!flag) {
                    return;
                }
            }
        }
    

    到这里,大家有没有明白一个道理,如果功力不是很深厚的话,想尝试一次性写出一个非常成熟的生产级甚至商业级的代码是非常困难的。只有对代码不断进行优化,才能最终达到想要结果。这个过程既是迭代代码,也是迭代自己,往更高更好的方向不断奋进!

  • 相关阅读:
    【AMAD】django-channels -- 为Django带来异步开发
    【AMAD】django-crispy-forms -- 不要再重复编写Django Form了!
    【AMAD】django-compressor -- 将JS和CSS文件压缩为一个缓存文件
    【AMAD]django-filter -- 一个通用的,基于用户选择的Django Queryset 过滤系统
    【AMAD】django-taggit -- 一个简单的,通用的django tagging模块
    【AMAD】django-debug-toolbar -- 一个可配置的panel,展示当前request/response的debug信息
    【amad】cookiecutter -- 一个命令行工具,使用项目模版来构建项目
    十步学习法 -- 来自<<软技能>>一书的学习方法论
    第八篇、SVN在Mac上使用
    第七篇、Nginx Install On Mac
  • 原文地址:https://www.cnblogs.com/joimages/p/13217727.html
Copyright © 2011-2022 走看看