数据结构和算法之五:排序算法二

数据结构基础之排序算法二

学习算法,排序算法当然是不能少的,这次我们来学习一下基础的选择排序,冒泡排序,以及大名鼎鼎的快速排序。

选择排序

选择排序,非常好理解,就是找最小的数放到第一位,然后从第二个数开始找最小的数,放到第二个位置,然后从第三个数开始找最小的数,放到第三个位置… 。每次找最小的数,就是选择最小的数,这就是选择的含义。

看个图就明白了

在这里插入图片描述

第一次,找到最小的数字1,放到第一个位置上(当然原来在第一个位置的元素就交换到原来数字1的位置上了);第二次,从第二个位置也就是6开始,找到最小的数字2,放到第二个位置…

选择排序的逻辑非常简单,那么代码肯定也不复杂了

/**
 * 选择排序
 *
 * 思路
 *  每一次找一个最小的值,交换放到最左边的位置上。
 *  重复这样找下去即可。
 *
 *  时间复杂度 O(N^2)
 *  空间复杂度 O(n)
 *  稳定性    不稳定
 */
public class SelectSortDemo {

    public static void selectSort(int[] data){
        if(null == data || data.length == 0 || data.length == 1){
            return ;
        }
        for (int i = 0; i < data.length; i++) { //
            int minIndex = i;           //当前这一轮中的最小值下标
            for (int j = i+1; j < data.length; j++) {  //内层循环,找最小的数
                if(data[minIndex] > data[j]){
                    minIndex = j;
                }
            }
            if(minIndex != i){ //最小值,不是初始值,说明在后面有更小的值,那么进行交换
                //这儿用一个加减法技巧来完成两个数的交换,省下一个临时交换变量。
                data[i] = data[i] + data[minIndex];
                data[minIndex] = data[i] - data[minIndex];
                data[i] = data[i] - data[minIndex];
            }
        }
    }

    public static void main(String[] args) {
        int[] data = {2,6,3,5,4,1,0,8,7,9,2};
        selectSort(data);
        System.out.println(Arrays.toString(data));
    }
}

注意代码中,交换两个位置上的值,有一个小技巧就是不使用辅助变量也可以完成的,你可以get一下。

选择的排序的时间复杂度显然是n^2,和插入排序一样,胜在简单,但是因为其不稳定的特性,实际使用的较少。

冒泡排序

冒泡排序,一次循环会找出一个最大数放到最后,就像水泡从水底往上冒出来一样,这也是冒泡排序的名称由来。它的基本逻辑是,挨着的两个元素比较,大的就就交换到后面,依次不断的往后比较,一轮下来,最大的那个数就会跑到最后面去。

同样,看图说话

在这里插入图片描述

代码实现也比较容易:

/**
 * 冒泡排序
 *
 *  思路:
 *  从前到后,依次两两比较,如果前面大于后面的元素,将两个元素进行交换,完成一轮比较之后,最大的值就会放到最后
 *  重复这个过程,直到剩下最左边最后一个元素。
 *  就像气泡从水底往上冒出来一样,这也是冒泡排序的名称由来。
 *
 * 时间复杂度 O(N^2)
 * 空间复杂度 O(n)
 * 稳定性    稳定(实现中在与有序段比较时,只有小于才发生交换)
 *
 */
public class BubbleSortDemo {

    public static void bubbleSort(int[] data){
        if(null == data || data.length == 0 || data.length == 1){
            return ;
        }
        for (int i = data.length; i > 0; i--) {
            for (int j = 0; j < i-1; j++) {
                if(data[j+1] < data[j] ){ // 后一个元素小于前一个元素,发生交换
                    int temp = data[j];
                    data[j] = data[j+1];
                    data[j+1] = temp;
                }
            }
        }
    }

    public static void main(String[] args) {
        int[] data = {2,6,3,5,4,1,7,0,9};
        bubbleSort(data);
        System.out.println(Arrays.toString(data));
    }
}

时间复杂度显然是n^2,也是比较简单。

快速排序

快速排序,听名字就知道它很快,那么我们就来分析分析,它有多快,是如何快起来的?

快速排序同样使用了分治的思想,但和归并不一样的是,它在拆分时,并不是二分,而是选定一个基准值,将小于这个基准值的拆到一段,大于基准值的拆到一段;然后小的这一段再重新选择基准值,小于新基准值的再拆分一段,大于新基准值的拆为一段;重复这样下去,直到不用再拆为止。因为每次拆分之前,小于基准和大于基准的就分开了,那么最后拆出来的就已经是有序的了,因此它只有递归的递的过程,没有归的过程,不就是个尾递归嘛。

不说楞个多,上图才是正事

在这里插入图片描述

快排的核心思想相信你应该有所了解了,其中讲到选定一个基准值,将小于这个基准值的拆到一段,大于基准值的拆到一段,这个操作有很多种实现方式,上图就提供一种思路:

  1. 从基准值后面的元素中找出第一个比他小的元素,和基准值交换
  2. 从前面找出第一个比它大的元素,和基准值交换
  3. 重复1和2,直到遍历完这一段元素。

上一盘代码,你说香不香

/**
 * 快速排序
 *
 *  思路: 拆分,递归
 *  选择一个基准值,并通过比较和交换,将所有小于基准值的放到它的左边,将所有大于基准值的放到它的右边。
 *  (具体实现方式,可以先从基准值的右边找比他小的,和它交换; 然后从左边找比它大的,再和它交换;
 *  然后再右边,再左边,做完一轮之后,就能实现基准值的左边都是小于它的,右边都是大于它的)。
 *  现在就可以将数组拆分为3个部分(如果基准值最大/最小,那会是2个部分),左边的子数组,基准值,右边的子数组,
 *  接下来迭代对左、右子数组进行一样的操作(选基准值,比较交换),直到拆分到不能再拆分为止(小于等于2个元素即可)
 *
 *  时间复杂度 O(nlogn)
 *  空间复杂度 O(n)
 *  稳定性    不稳定(因为以基准值进行交换,会打乱原本相同值的前后顺序)
 */
public class QuickSortDemo {

    public static void quickSort(int[] data){
        if(null == data || data.length == 0 || data.length == 1){
            return ;
        }

        recursionSort(data, 0, data.length-1);
    }

    public static void recursionSort(int[] data,  int left, int right){

        //递归终止条件
        if(left>=right){
            return;
        }

        int base = left; //选定基准值,这儿简单起见,选左边第一个
        int ll = left;  //遍历左边时使用的指针
        int rr = right; //遍历右边时使用的指针
        while(ll < rr){
            //从右边往左边找,找到一个比base小的值时,交换
            while(ll < rr) {
                if (data[rr] < data[base]) {
                    int temp = data[rr];
                    data[rr] = data[base];
                    data[base] = temp;
                    base = rr;
                    rr--;
                    break;
                }
                rr--;
            }
            //从左边往右边找,找到一个比base大的值时,交换
            while(ll < rr) {
                if (data[ll] > data[base]) {
                    int temp = data[ll];
                    data[ll] = data[base];
                    data[base] = temp;
                    base = ll;
                    ll++;
                    break;
                }
                ll++;
            }
        }

        recursionSort(data, left, base-1); //左半边递归
        recursionSort(data, base+1, right); //右半边递归

    }


    public static void main(String[] args) {
        int[] data = {2,6,3,5,4,1,0,8,7,9,2};
        quickSort(data);
        System.out.println(Arrays.toString(data));
    }
}

应该可以发现,快排中,基准值的选择还是比较重要的,选得好,那么拆分就比较均匀,树的深度也就越小,当然效率也就越高了,所以优化快排的思路就在基准值的选取上,你可以暂开。

到此呢,我们学习了6种排序算法了,来综合比较下他们:

排序名称时间复杂度是否稳定额外空间开销
插入排序O(n^2)稳定O(1)
冒泡排序O(n^2)稳定O(1)
选择排序O(n^2)不稳定O(1)
希尔排序O(n^2)不稳定O(1)
归并排序O(nlogn)稳定O(n)
快速排序O(nlogn)不稳定O(1)

这么多种排序算法我们究竟应该怎么选择呢?

1.分析场景:稳定还是不稳定

2.数据量:数据量小的时候选什么?比如就50个数,优先选插入。

综上所述,没有一个固定的排序算法,都是要根据情况分析的。当然,你不想去做过多选择,那么就上归并或者快排吧,至少在数据量大的时候,系统不会崩盘。

最后今天的几种排序算法,你可以在一个堪称神器的网站上观察排序算法运行的过程。

可视化动态排序演示:https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html

学了排序算法,来个思考题,对200W学生的成绩排序,成绩从0到999.99(两位小数),要求效率要高的。

(也许根本不是用排序来做哦,还记得年龄问题吗?扩展一下就可以变成计数排序了)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值