选择、冒泡、插入和计数排序

说到排序,生活中我们有很多排序的方法,不同的排序方法有不同的优点和缺点,各有各的好处。这次我们来说一下四个排序,分别是选择排序、冒泡排序、插入排序和计数排序。下面就具体来看看吧!

  • 选择排序

从算法逻辑上来说,选择排序是一种简单直观的排序算法。它的原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零,这就是选择排序的具体流程。下面我们通过一个例子来具体看看。

class Test02{
    public static void main(String[] args){
        //选择排序 时间复杂度O(n^2)
        selectSort();
    }
    //选择排序函数
    public static void selectSort(){
        int[] arr={8,5,9,2,7,4,6,1,3};
        for(int i=0;i<arr.length-1;i++){//-1是因为没有必要进行最后一个数字的比较
            for(int j=i+1;j<arr.length;j++){
                if(arr[i]>arr[j]){
                    swap(arr,i,j);//即用-即释放
                }
            }
        }
        show(arr);
    }
    //交换函数
    public static void swap(int[] arr,int i,int j){
        //1.借助三方变量进行交换 
        //适用于所有的数据类型 比较通用
        /*
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
        */
        //2.借助加减法运算进行交换
        //只适用于数字相关的数据类型
        arr[i]=arr[i]+arr[j];
        arr[j]=arr[i]-arr[j];
        arr[i]=arr[i]-arr[j];

        //3.借助位运算进行交换
        //只适用于整数相关的数据类型
        /*
        int a=3;
        int b=7;
        a=a^b;
            0011
            0111
            0100
        b=a^b;
            0100
            0111
            0011 ->3
        a=a^b;
            0100
            0011
            0111 ->7
        */
    }
    //输出函数
    public static void show(int[] arr){
        //[1,2,3,4,5,6,7,8,9]
        String s="[";
        for(int i=0;i<arr.length;i++){
            if(i==arr.length-1){
                s+=arr[i]+"]";
            }else{
                s+=arr[i]+",";
            }
        }
        System.out.println(s);
    }
}

上述代码中,我们在main函数中直接调用选择排序函数 ,相当于把其他过程都写进选择排序函数里面。然后在选择排序函数里面,我们又调用了两个函数,swap()函数和show()函数。swap函数的功能是交换两个数字的位置,而show函数的功能则是对整个数组进行遍历,最终输出。swap()函数交换两个数字的位置的方法有三种,都写在代码里面了。

综上所述,选择排序的思想就是比较+交换。具体操作方法:

第一趟:从n个记录中找出关键码最小的记录和第一个记录交换;

第二趟:从第二个记录开始的n-1个记录中再选出关键码最小的记录与第二个记录交换

以此类推......

第i趟,则从第i个记录开始的n-i+1个记录中选出关键码最小的记录与第i个记录交换,直到整个序列按关键码有序。 

  • 冒泡排序

冒泡排序是一种比较稳定的排序算法,它的原理其实也很简单,大概来说一下:

1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。

2.对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

3.除了最后一个元素以外对其他所有元素重复以上的步骤。

4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

这就是冒泡排序的流程图,很直观明了,我们再来看一个例子。

class Test02{
    public static void main(String[] args){
        //冒泡排序 时间复杂度O(n^2)
        bubbleSort();
    }
    //冒泡排序函数
    public static void bubbleSort(){
        int[] arr={8,5,9,2,7,4,6,1,3};
        for(int i=0;i<arr.length-1;i++){//-1是少一轮比较
            for(int j=0;j<arr.length-1-i;j++){//-1避免重复比较和角标越界
                if(arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                }
            }
        }
        show(arr);
    }
    //交换函数
    public static void swap(int[] arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
    //输出函数
    public static void show(int[] arr){
        //[1,2,3,4,5,6,7,8,9]
        String s="[";
        for(int i=0;i<arr.length;i++){
            if(i==arr.length-1){
                s+=arr[i]+"]";
            }else{
                s+=arr[i]+",";
            }
        }
        System.out.println(s);
    }
}

关于这个swap()函数和show()函数,前面 我们都已经解释过了,这里就不再多做说明了,我就直接用了。

关于冒泡排序的思想我在这里简单说一下:它就是让数组当中相邻的两个数进行比较,数组当中比较小的数值向下沉,数值比较大的向上浮。n个数字要排序完成,总共进行n-1趟排序,每i趟的排序次数为(n-i)次,外层for循环控制循环次数,内层for循环控制相邻的两个元素进行比较。

冒泡排序的优点:每进行一趟排序,就会少比较一次。因为每进行一趟排序都会找出一个较大值。第一趟比较之后,排在最后的一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,同样也能找出一个最大的数排在参与第二趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,以此类推........也就是说,我们每进行一趟比较,每一趟就少比较一次,这在一定程度上减少了算法量。

  • 插入排序

插入排序就是:假设第一个数已经是有序的,从第二个数开始,拿出第二个数进行向前插入排序,一直到最后一个数向前做插入排序,直到序列排完为止。来看一个例子

class Test02{
    public static void main(String[] args){
        //插入排序 时间复杂度O(n^2) 
        insertSort();	
    }
    //插入排序函数
    public static void insertSort(){
        int[] arr={8,5,9,2,7,4,6,1,3};
        int e;
        int j;
        for(int i=1;i<arr.length;i++){
            e=arr[i];
            for(j=i;j>0&&arr[j-1]>e;j--){
                arr[j]=arr[j-1];
            }
            arr[j]=e;
        }
        /*
        for(int i=1;i<arr.length;i++){
            for(int j=i;j>0&&arr[j-1]>arr[j];j--){
                swap(arr,j,j-1);
            }
        }
        */
        show(arr);
    }
    /*
    //交换函数
    public static void swap(int[] arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
    */
    //输出函数
    public static void show(int[] arr){
        //[1,2,3,4,5,6,7,8,9]
        String s="[";
        for(int i=0;i<arr.length;i++){
            if(i==arr.length-1){
                s+=arr[i]+"]";
            }else{
                s+=arr[i]+",";
            }
        }
        System.out.println(s);
    }
}

上述插入排序函数中,注释掉的那一块是最先写的,然后进行了下优化,比较的次数能少了点。这个排序算法简单来说就是,如果当前数字的左边有数字并且左边的数字大于当前数字,则进行交换,接着进行下一个数字,直到排序完成为止。

  • 计数排序

​​​​​​​计数的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),它快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,但是当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(如归并排序,堆排序)。来看个例子:

class Test02{
    public static void main(String[] args){
        //计数排序 时间复杂度O(n+m)
	countSort();
    }
    //计数排序函数
    public static void countSort(){
        int[] arr={8,5,9,2,7,4,6,1,3,10,-3,-2,-10};
        int min=arr[0];
        int max=arr[0];
        for(int i=0;i<arr.length;i++){//O(n)
            if(arr[i]>max){
                max=arr[i];
            }
            if(arr[i]<min){
                min=arr[i];
            }
        }
        int[] nums=new int[max-min+1];
        int offset=min;
        for(int i=0;i<arr.length;i++){//O(n)
            nums[arr[i]-offset]++;
        }
        int index=0;
        for(int i=0;i<nums.length;i++){//O(m)
            if(nums[i]!=0){
                for(int j=0;j<nums[i];j++){
                    arr[index++]=i+offset;
                }
            }
        }
        show(arr);
    }
    //输出函数
    public static void show(int[] arr){
        //[1,2,3,4,5,6,7,8,9]
        String s="[";
        for(int i=0;i<arr.length;i++){
            if(i==arr.length-1){
                s+=arr[i]+"]";
            }else{
                s+=arr[i]+",";
            }
        }
        System.out.println(s);
    }
}

它的具体流程: 

第一步:找出原数组中元素值最大的,记为max

第二步:创建一个新数组nums,其长度是max加1,其元素默认值都为0。

第三步:遍历原数组中的元素,以原数组中的元素作为nums数组的索引,以原数组中的元素出现次数作为nums数组的元素值。

第四步:arr[index++]用来表示最后的结果数组,起始索引index

第五步:遍历nums数组,找出其中元素值大于0的元素,将其对应的索引作为元素值填充到arr[index++]数组中去,每处理一次,nums中的该元素值减1,直到该元素值不大于0,依次处理nums中剩下的元素。

第六步:返回结果数组arr[index++]。

上面这个算法流程可以用,但是它可能存在空间浪费的问题,所以我对这个算法进行了一点改进:那就是将数组长度定为max-min+1,即不仅要找出最大值,还要找出最小值,根据两者的差值来确定数组的长度。底下这是一个草图,还有大致步骤。

总之,所有的排序算法都有互通之处,理解了一两种,其他的自然也就通了,另外,还是要多做题目,多多练习,才能孰能生巧。

发布了18 篇原创文章 · 获赞 25 · 访问量 3861

猜你喜欢

转载自blog.csdn.net/Agonyq/article/details/104346945
今日推荐