数据结构-交换排序(冒泡算法,快速排序)

目录

1,冒泡排序思想

1.1,冒泡排序过程分析与描述

2,冒泡排序时间复杂度分析

2.1,冒泡排序算法的实现

2.2,算法改进版本一

2.3,算法改进版本二

2.4,优化小结

3,快速排序

3.1,快速排序的思想

3.2,快排算法的时间复杂度

3.3,快速排序的代码实现


1,冒泡排序思想

冒泡算法是一种基于交换思想的排序算法,是一种简单的排序算法。它适合小规模数据的排序,算法每次都和相邻的两个元素比较,把比较结果比较大的哪一个元素逐渐的交换到最后的位置,经过n-1趟比较后,整个序列有序。可以看到,冒泡算法的每一趟排序都有一个元素被放到最终的位置,下面我们通过一个例子看看冒泡排序的过程。

1.1,冒泡排序过程分析与描述

  • 算法描述
    • 比较相邻的两个数,如果第一个数比第二个数大,则两数交换。
    • 对之后的相邻元素进行同样的工作,从开始到最后一对,这样进行一次排序后,数据的最后一位会是最大值 ,第一次循环进行的次数为 arr.length-1。
    • 之后对所有的元素重复以上的步骤,且以后每次循环的次数为arr.length-1-i (i为循环第几次 ,i 从零开始)。
    • 重复上述步骤,直到排序完成。
  • 一趟算法的排序过程

首先看我们的原始数据,每次都把前面的一个元素和后面的一个元素比较,如果前面元素小于后面元素,不发生交换,如果前面元素大于后面元素,就发生交换,直到遍历完所有的元素,此时我们的第一趟排序完成,从结果来看,我们最大的元素已经放到了他自己的位置,也就是说对于冒泡排序来说,每一趟排序完成后,都有一个元素被放到合适的位置。(默认是从小到大排序)。

  • 每一趟的排序结果

基于上面每一趟比较的过程,可以得到冒泡排序每一趟排序后的序列结果,可以看出,每一趟排序后,都有一个元素被放到最终的位置。因为冒泡排序是基于交换的,所以是一个稳定的排序算法。从排序的过程可以看到,对于长度为n的待排序数组,最多需要进行n-1趟遍历,每趟遍历最多进行n-1次比较。

2,冒泡排序时间复杂度分析

算法最好时间最坏时间平均时间空间复杂度稳定性
冒泡O(N)O(N^{2})O(N^{2})O(1)稳定
  • 关于稳定性:因为在比较的过程中,当两个相同大小的元素相邻,只比较大或者小,所以相等的时候是不会交换位置的。而当两个相等元素离着比较远的时候,也只是会把他们交换到相邻的位置。他们的位置前后关系不会发生任何变化,所以算法是稳定的。
  • 关于最优时间复杂度为什么是O(n),当待排序的序列已经基本有序的时候,算法发现一趟排序下来没有发生数据元素交换的情况下,说明序列基本有序,会结束排序。

2.1,冒泡排序算法的实现

冒泡排序算法的实现,可以每一趟把最小的元素放到序列最前端或者每一趟都把最大的元素放到数组的末尾两种。冒泡算法的普通版本,时间复杂度是O(N^{2})

public class Hello {
    public static void main(String[] args) {
//        Non-static method 'BubbleSort(int[], int)' cannot be referenced from a static context
//        解决办法:把函数添加static进行修饰
//        new一个对象,使用对象调用函数
        Hello hello=new Hello();
        int []array={5,6,7,8,1,2,3,4,9,10};
        int []a=hello.BubbleSortPlus(array,10);
    }

    /**
     *  冒泡排序:每次把最小的元素放到数组前面
     * @param arr 待排序的数组
     * @param n 数组中元素的个数
     * @return 排好序的数组
     */
    public  int [] BubbleSortSmallEleToFront(int []arr,int n){
        int temp=0;
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
//                注意,这里做法是每次找到最小的元素,放到前面
//                也可以每次找到最大的元素放到后面
                if(arr[i]>arr[j]){
                    temp=arr[i];
                    arr[i]=arr[j];
                    arr[j]=temp;
                }
            }
            System.out.println("第"+(i+1)+"趟排序:");
            System.out.println(Arrays.toString(arr));
        }
        return arr;
    }

    /**
     * 冒泡排序,每一次把最大的元素放到数组的末尾。
     * @param arr 待排序数组
     * @param n 数组中元素的个数
     * @return 排好序的数组
     */
    public  int [] BubbleSortBigEleToRear(int []arr,int n){
        int temp=0;
        for(int i=0;i<n-1;i++){
            for(int j=0;j<n-i-1;j++){
//每次找到最大的元素,放到数组末尾
                if(arr[j]>arr[j+1]){
                    temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;    
                }
            }
            System.out.println("第"+(i+1)+"趟排序:");
            System.out.println(Arrays.toString(arr)); 
        }
        return arr;
    }
}

2.2,算法改进版本一

看下面序列的排序过程,其实在第5趟的时候已经完成了排序,后面的遍历和比较是多余的。于是,我们来逐步对算法进行优化。可以看到,其实从第5趟开始,各个元素之间已经没有发生过位置(值)交换了。那么我们可以引入一个布尔型标志flag,如果检测到某一趟没有发生元素交换,就可以知道数组已经提前排序好了,那么整个遍历操作就可以提前退出。最好情况下排序的时间复杂度是O(n)

  • 改进算法代码:
public class Hello {
    public static void main(String[] args) {
//        Non-static method 'BubbleSort(int[], int)' cannot be referenced from a static context
//        解决办法:把函数添加static进行修饰
//        new一个对象,使用对象调用函数
        Hello hello=new Hello();
        int []array={5,6,7,8,1,2,3,4,9,10};
        int []a=hello.BubbleSortPlus(array,10);
    }

    /**
     *  冒泡排序:每次把最小的元素放到数组前面
     * @param arr 待排序的数组
     * @param n 数组中元素的个数
     * @return 排好序的数组
     */
    public  int [] BubbleSortSmallEleToFront(int []arr,int n){
        int temp=0;
        boolean flag=false;
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
//                注意,这里做法是每次找到最小的元素,放到前面
//                也可以每次找到最大的元素放到后面
                if(arr[i]>arr[j]){
                    temp=arr[i];
                    arr[i]=arr[j];
                    arr[j]=temp;
                    flag=true;
                }
            }
            System.out.println("第"+(i+1)+"趟排序:");
            System.out.println(Arrays.toString(arr));
            if(!flag){
                break;
            }else {
                flag=false;
            }
        }
        return arr;
    }

    /**
     * 冒泡排序,每一次把最大的元素放到数组的末尾。
     * @param arr 待排序数组
     * @param n 数组中元素的个数
     * @return 排好序的数组
     */
    public  int [] BubbleSortBigEleToRear(int []arr,int n){
        int temp=0;
        boolean flag=false;
        for(int i=0;i<n-1;i++){
            for(int j=0;j<n-i-1;j++){
//每次找到最大的元素,放到数组末尾
                if(arr[j]>arr[j+1]){
                    temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                    flag=true;
                }
            }
            System.out.println("第"+(i+1)+"趟排序:");
            System.out.println(Arrays.toString(arr));
            if(!flag){
                break;
            }else {
                flag=false;
            }
        }
        return arr;
    }
}

2.3,算法改进版本二

引入标志位flag后,可以提前结束暴力遍历,减少遍历次数,但算法还有改进空间。我们可以看到,在进行第n趟排序时(n≥1),倒数n位的元素已经就位排好序了,那么这些值其实在下一趟遍历时,就无需参与比较了。因此,我们可以对冒泡算法再次进行改进,减少每一趟的比较次数。

从上面的过程来看,在每一趟比较之后,序列的后面元素已经有序,例如在第四趟比较之后,后面的四个元素:6,7,8,9已经有序,后续的每一趟比较不需要和这几个元素再次比较,因此可以设置一个标志位,每次都记录最后一次发生比较的位置,下一趟比较直接比较到这个标志位即可,不必和标志位后面的元素比较。

  • 改进代码:
//    改进思路,每一次记住最后的一次元素比较并且交换位置的地方,下一次比较比较到这个位置即可
    public static int [] BubbleSortPlus(int []arr,int n){
        int temp=0;
        //记录最后一次发生交换的位置
        int lastChange=0;
        int indexNum=n-1;
        int num=n-1;
        boolean flag=false;
        while(num>0){
            for(int i=0;i<indexNum;i++){
                if(arr[i]>arr[i+1]) {
                    temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                    lastChange = i;
                    flag= true;
                }
            }
            System.out.println(Arrays.toString(arr));
            indexNum=lastChange;
            num--;
            if(!flag){
                break;
            }else {
                flag=false;
            }
        }
        return arr;
    }

2.4,优化小结

  1. 设置标志位判断是否提前完成排序。
  2. 每趟减少参与比较的元素个数。

3,快速排序

3.1,快速排序的思想

快速排序(Quick Sort)使用分治法策略。它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

  • 快速排序的基本流程
    • 从数列中挑出一个基准值。
    •  将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置。
    • 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序。
  • 快速排序的过程

3.2,快排算法的时间复杂度

  • 快速排序的稳定性
    • 快速排序是不稳定的算法,它不满足稳定算法的定义。
    • 算法稳定性 -- 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!
  • 快速排序的时间复杂度
    • 快速排序的时间复杂度在最坏情况下是O(n^{2}),平均的时间复杂度是O(N*lgN)。
      • 这句话很好理解:假设被排序的数列中有N个数。遍历一次的时间复杂度是O(N),需要遍历多少次呢?至少lg(N+1)次,最多N次。
        • (01) 为什么最少是lg(N+1)次?快速排序是采用的分治法进行遍历的,我们将它看作一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是lg(N+1)。因此,快速排序的遍历次数最少是lg(N+1)次。
        • (02) 为什么最多是N次?这个应该非常简单,还是将快速排序看作一棵二叉树,它的深度最大是N。因此,快读排序的遍历次数最多是N次。
    • 当待排序数组已经基本有序或者是处于逆序情况的时候,在最坏的情况下,待排序的序列为正序或者逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,所以快速排序最坏情况下是o(n^{2})。是因为我们画出的二叉树是一棵单支树。
    • 初始数组序列对快速排序有影响。

3.3,快速排序的代码实现

public class QuickSortedDemo {
    public static void main(String[] args) {
        int array[] = {4, 11, 9, 0, 13, 7, 5, 6, 8,1,2,3};
        quickSorted(array,0,array.length-1);
    }

    /**
     * 快速排序代码实现
     * @param arr 待排序数组
     * @param left 最左边索引
     * @param right 最右边索引
     */
    public static void quickSorted(int [] arr,int left,int right){
        int l=left;
        int r=right;
        int pivot=arr[(r+l)/2];
        int temp=-1;
        while (l<r){
//            默认从小到大排序,先从左边找出一个大于pivot的数字
            while (arr[l]<pivot){
                l++;
            }
//            从右边找到一个小于prvot的数值
            while (arr[r]>pivot){
                r--;
            }
            temp=arr[l];
            arr[l]=arr[r];
            arr[r]=temp;
//           l>r说明最小值r是在pivot的左边找到,最大值l是在pivot的右边找到
//            也就是说明pivot左右两边的数据已经有序,不需要在比较排序
            if(l>=r){
                break;
            }

            if(arr[l] == pivot)
                r-=1;
            if(arr[r]== pivot)
                l+=1;
            System.out.println(Arrays.toString(arr));
        }
        if(r == l){
            r-=1;
            l+=1;
        }
        if(left<r){
            quickSorted(arr,left,r);
        }
        if(l<right){
            quickSorted(arr,l,right);
        }
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值