图解十大排序算法(附java源码)

1.冒泡排序

排序原理不在多说,直接上例子,通过例子能够一点点的认识到冒泡排序的原理及过程,大家看懂后可以自己总结各个排序的原理发到评论区,如有写错的地方,希望读者们不吝指正。另外,各个小节中的举例都以升序排序;


无序数组(长度为6):9   2   0  1   5   7   

第一趟冒泡:将最大的数字冒到数组末尾

按照图中:从左至右两两比较,发现左边大于右边就交换两数的位置的方式,一直重复直至比较(遍历)完全部元素,我们便完成了第一趟排序,成功的将数组中最大的数字放到了数组末尾:

第一趟共比较了5次,发生了5次交换

 第二趟排序:对剩余的左边五个元素进行冒泡,将第二大的数字冒到倒数第二个位置:

第二趟排序将第二大的数字7冒到了9的左边, 此时数组中元素变为: 

 第二趟共比较了4次,发生了2次交换

  第三趟排序:对剩余的左边四个元素进行冒泡,将第三大的数字冒到倒数第三个位置:

 

第三趟排序将第三大的数字5冒到了7的左边, 此时数组中元素变为: 

 

  第三趟共比较了3次,发生了0次交换

 第四趟排序:对剩余的左边三个元素进行冒泡,将第四大的数字冒到倒数第四个位置:

 

 第四趟排序将第四大的数字5冒到了5的左边, 此时数组中元素变为: 

 第四趟共比较了2次,发生了0次交换

 第五趟排序:对剩余的左边两个个元素进行冒泡,将第五大的数字冒到倒数第五个位置:

  第五趟共比较了1次,发生了0次交换

到这里我们一共进行了5次冒泡,将最大的五个泡泡冒到了右边,此时还剩下一个泡泡:0,但数组中一共6个泡泡,已经将最大的5个放到了最后,那么剩下的1个一定是最小的,所以到这里我们就完成了数组的排序。

编码思路:

在上面我们已经看完了冒泡排序算法排序的全过程,我们发现数组中一共 6(n)个数字,我们需要进行 5(n-1)趟冒泡,另外我们还发现,第 趟冒泡的比较次数为5(n-1)次,第 2比较次数为4(n- 2 )次,第 3 趟比较次数为4(n- 3 )次..........

以此类推我们可以知道:第 比较次数为(n- k )次

结论:

  • 待排序数组大小为 n, 则需要 n-1 趟冒泡,其中第 k 趟冒泡的比较次数 n-k 次。
  • 那么总的比较次数为(交换次数这里不讨论):N=(n-1)+(n-2)+(n-3)+...+2+1=\frac{(1+(n-1))*n}{2}=\frac{n^{2}}{2}
  • 所以我们的冒泡排序时间复杂度为O(n^{2})

代码:

   public static void bubbleSort(int[] array){
        for(int i = array.length-1; i > 0; i--){//外循环控制冒泡的趟数
            for(int j = 0; j < i; j++){//内循环控制比较次数
                if( array[j] > array[j+1] ){//比较过程中,发现左边大于右边则交换
                    swap(array,j,j+1);
                }
            }
        }
        
    }

    //交换元素的方法
    public static void swap(int[] array,int i,int j){
        array[i]=array[i]^array[j];
        array[j]=array[i]^array[j];
        array[i]=array[i]^array[j];
    }

看到这里,恭喜你已经学会冒泡排序了,但还有个好消息是冒泡排序还可以改进一下,现在我们回头看一看上面的冒泡排序过程,你会发现其实我们完成第二趟排序之后,数组中的元素已经有序了:

所以我们后面的3趟冒泡完全是在做无用功,所以,我们是否可以程序中加上一个判断条件,在每一趟排序后,来检验我们数组是否已经有序了,以避免上面那种做无用功的情况,答案当然是yes,怎么加判断条件呢?每一趟冒泡后遍历检查一下前面的元素是否有序?这显然不行,为啥不行,小伙伴们可以自己思考一下,有思路的可以分享到评论区共勉,下面我说一下我的思路:我们回头去看一下,当数组有序后,剩下的几趟冒泡中有什么共同特点:交换次数都为 0 !!!不难理解,我们是希望将大小顺序不符合预期的数字进行交换,既然数组已经有序了,那么相邻两个元素必然是左小右大,所以不会发生交换,按照这个理论,我们便可以对程序进行优化,话不多说,怎么优化,直接看代码。

public static void bubbleSort1(int[] array){
        for(int i = array.length-1; i > 0; i--){
            boolean swaped=false; //加一个判断,默认为此次遍历未发生交换
            for(int j = 0; j < i; j++){
                if( array[j] > array[j+1] ){
                    swap(array,j,j+1);
                    swaped=true; //一旦发生了交换,则将其改为true
                }
            }
            if(!swaped) break; //当某次遍历结束后,未发生交换,说明数组已经有序,则直接退出
        }
        
    }
//swap()与上相同

但实际上这种优化的意义不大,因为我们遇到的数组中,有的数组(如:9 8 7 6 5 4 3 2 1)是必须要进行N趟冒泡才能使得整个数组有序的,遇到这样的数组,我们不但不能提前结束循环,还得在每一趟冒泡后进行一次判断以及对 swaped 赋值,增加了工作量,另外还浪费了更多的空间(一个字节也算浪费)

2.选择排序


还是直接先上例子,算法原理和过程留给大家总结:

待排序数组:

次遍历:找出数组中最大的元素: 9,9  对应下标为  0 ,0 不等于 3,说明最大的元素没有放在数组末尾,所以交换 0 和 3 中的元素:

次遍历:找出数组中剩余的3个数中最大的元素: 2,2  对应下标为  1 ,1 不等于 2,说明最大的元素没有放在数组末尾,所以交换 1 和 2 中的元素:

 

次遍历:找出数组中剩余的2个数中最大的元素: 1,1  对应下标为  0 ,0 不等于 1,说明最大的元素没有放在数组末尾,所以交换 0 和 1 中的元素:

 

 三次选择后,我们前三大的元素都放在了相应位置,剩下最后一个元素,必然是有序的,所以长度为4的数组,我们一共要选择3次。

编码思路:

我们回看上面的例子,不难发现,选择排序实际上就是两个步骤:

  1. 找出剩余元素中最大的数的下标
  2. 如果找到的下标不是剩余元素中的最大下标,则交换元素

结论:

  • 长度为n的数组,需要找出前(n-1)大的元素,并将找到的元素放到剩余元素的最右边
  • 寻找 第 k 大的数时,需要比较 n-k次
  • 每次找到最大数下标后,交换次数最大为1次

总的比较次数为(总的交换次数最大为 n-1 次):N=(n-1)+(n-2)+(n-3)+...+2+1=\frac{(1+(n-1))*n}{2}=\frac{n^{2}}{2}

代码:

   public static void selectionSort(int[] array){
        for(int i=0;i<array.length-1;i++){
            int minIndex=i;
            for(int j=i+1;j< array.length;j++){
                minIndex = array[minIndex] <= array[j] ? minIndex : j;
            }
            if (minIndex != i) swap(array,minIndex,i);
        }
    }

   public static void swap(int[] array,int i,int j){
        array[i]=array[i]^array[j];
        array[j]=array[i]^array[j];
        array[i]=array[i]^array[j];
    }

3.插入排序


插入排序可以想象为打牌,我们在摸牌的过程中,每摸起一张牌时都会主动的将其放在一个中间位置(中间位置:比如你手里有两张牌:J 和 K,而你摸到的牌为Q,那你会主动将Q这张牌放在J和K之间,这个位置就是中间位置),另外当我们摸到第一张牌时,是不需要进行中间位置插入的,所以插入排序,一般都以数组中第二个元素开始进行插入。

待排序数组:5  2 1 3 4

插排图示:

 编码思路:

从数组中的第2个元素开始,将该元素作为待插入元素,依次向左比较,找到中间位置后进行插入,每执行一次插入后,选取下一个待插入元素以相同方式进行插入,直到完成最后一个元素的插入,整个数组就变得有序了。

结论:

  • 待排序数组长度为 ,则需要进行 n-1 次插入
  • k 次插入的比较次数最大为:k
  • 所以最坏的情况下,总的比较次数为N=1+2+3+4+...+(n-2)+(n-1)=\frac{(1+(n-1))*n}{2}=\frac{n^{2}}{2}
  • 所以我们的冒泡排序时间复杂度为O(n^{2})

另外比较次数还有一个下限为 n-1,为什么?这里留给大家作为一个小作业,有思路的可以放在评论区。

代码:

    public static void insertSort(int[] array){
        for(int i=1;i<array.length;i++){//拿取第i张牌
            for(int j=i; j > 0;j--){//通过交换的方式进行插入
                if(array[j] >= array[j-1]) break;
                swap(array,j,j-1);
            }
        }
    }

    public static void swap(int[] array,int i,int j){
        array[i]=array[i]^array[j];
        array[j]=array[i]^array[j];
        array[i]=array[i]^array[j];
    }

以上三种排序的时间复杂的均为n^{2},下一篇文章更新时间复杂度为O(nlogn)的排序算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值