适合小规模排序的算法-冒泡排序,插入排序,选择排序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/c_he_n/article/details/83929800

1.如何选择排序

在面对不同的应用场景时,如何选择一个合适的算法也很重要,可以从下面三点入手。

  • 排序算法执行效率:

1.最好情况,最坏情况和平均时间复杂度
2.时间复杂度的系数,常数,低阶
3.比较次数和交换次数

  • 算法的内存消耗

指空间复杂度,有个新的概念:原地排序(Sorted in place)就是特指空间复杂度是 O(1) 的排序算法。

  • 算法稳定性

有个要排序的数组2,9,3(1),4,8,3(2),有两个元素一样,在排序后相同元素的相对位置有没有变化,若排序后为2,3(1),3(2),4,8,9,则为稳定排序,若为2,3(2),3(1),4,8,9,则为不稳定排序。

2.冒泡排序(Bubble Sort)

冒泡排序只会操作相邻的数据,每次冒泡操作只比较相邻的数据,是否满足大小关系,若满足则交换,每次冒泡完至少有个元素放到应该放的位置,n次后完成改排序。可以分两步走:
1,确定冒泡排序的总次数
2,满足大小关系数据进行交换
在这里插入图片描述

如何写这个算法呢:
冒泡排序,是不是要从第一个开始进行冒泡,直到最后一个冒泡完,这就是外层循环。内层循环:每次冒泡都是相邻的数据进行冒泡,大的往上冒,小了就不冒泡了。

算法如下:

 private static int[] BubbleSort(int[] array) {
        if (array.length <= 1) {
            return array;
        }
        boolean swipeFlag = false;
        for (int i = 0, total = array.length; i < total; i++) {//冒泡的次数
            for (int j = 0; j < total - i - 1; j++) {//判断是否满足大小关系,进行交换数据。
                if (array[j] > array[j + 1]) {
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                    swipeFlag = true;
                }
            }
            if (!swipeFlag) {
                break;
            }
        }
        return array;
    }
}

结合冒泡排序,我们可以从上面是三点入手来分析这个排序算法的效率:
a.冒泡排序算法的效率

最坏的情况下就是逆序,需要n次冒泡和n次交换,时间复复杂度O(N*N);
最好的情况下就是已经排好顺序了,不需要交换,需要一次冒泡,时间复杂度为O(N)

b.冒泡排序的内存消耗

因为只需要交换即可,所以属于原地排序。

c.是否为稳定排序

在比较大小的时候并没有等于号,所以属于稳定排序。结合应用场景

根据应用场景来选择相应的算法。

3.插入排序

什么是插入排序呢,不妨看个例子,假如现在有个已经拍好的数组,现在需要插入一个数据,插入后仍然有序,那是不是需要从头开始遍历,找个到对应的排序位置后,先将找个位置后面的数据后移,然后将找个数据插入到该位置。
插入排序也是利用这一原理来实现的,将待排序的数组分为两个区域,一个是已排序的,另外一个是未排序的,每次都从未排序的区域选择一个数组,插入到已排序的区域,直到未排序区域没有数据。
排序算法的区域划分
为了更好的理解这个算法,我们举个例子:
排序数组:[2,9,3(1),4,8,3(2)]

第一次:[2],[9,3(1),4,8,3(2)]
第二次:[2,9],[3(1),4,8,3(2)]
第三次:[2,3(1),9],[4,8,3(2)]
第三次:[2,3(1),4,9],[8,3(2)]
第三次:[2,3(1),4,8,9],[3(2)]
第三次:[2,3(1),3(2),4,8,9] ,[]

总体来看,两个区域的变化,每次插入步骤具体见下图:
在这里插入图片描述

如何写这个算法呢?
既然将一个数组划分未排序和已排序,那么在代码层面上也是如此。外层循环为未排序的,内层循环为以排好序的。从未排序中取个数据,然后和已排序的数据从后往前进行比较,满足关系的就往后移动一位,直到不满足,将暂存的数据放到该位即可。

1.外层循环为未排序
2.内层循环为已排序
3.从未排序中取个数据和已排序的数组进行比较。比较大小关系,满足的话,已排序的数据往后移动一位,直到不满足条件,然后将暂存数据放在该位即可。

private static int[] insertSort(int[] array) {
        for (int i = 1; i < array.length; i++) {//未排序数组
            int j = i - 1;//i的前一个位置进行比较,并且是从后到前的比较。
            int temp = array[i];//从未排序数组中拿一个数和已排序的数组进行比较。
            for (; j >= 0; --j) {//已排序数组,比较的方式是从后往前比较,并配合移动数据。
                if (temp > array[j]) {
                    array[j + 1] = array[j];//判断是否满足大小关系,若是满足大小关系,需要向后移动一位,将j位置的数据覆盖到j+1上。
                } else {
                    break;//不满足大小的关系,就必须跳出循环,因为前面肯定也不满足大小关系。
                }
            }
            array[j + 1] = temp;//将待存储的数据放到被覆盖的位置上。
        }
        return array;
    }

从上面三点来分析这个算法:
空时间复杂度为O(1),属于原地排序,
时间复杂度:最好的情况下,是有序的,在首次比较就能确定位置,所以是O(n),最坏的情况下毫无疑问是 O(n^2),平均复杂度为 O(n^2) 。为稳定排序。

4.选择排序

选择排序和插入排序有点类似,也分已排序和未排序,不同的是选择排序从未排序中选择最小的数据和已排序的进行交换。
假设现在数组中已经是部分有序了,我们只需要从未排序中找到最小的数据放在未排序的首位就可以了,这样已排序数据增大,未排序的数据减小。详见代码吧,很多注释。

private static int[] selectSort(int[] array) {
        if (array.length <= 1) {
            return array;
        }
        for (int i = 0; i < array.length - 1; i++) {
            int minValues = array[i];//最小的值
            int minIndex = i;//最小值的下标
            for (int j = i + 1; j < array.length; j++) {
                if (minValues > array[j]) {//找到未排序中最小的值和下标。
                    minIndex = j;
                    minValues = array[j];
                }
            }
            if (i != minIndex) {//判断找到的最小和首次赋值的下标是否一样,若是不一样,说明找到了,若是一样,说明首次赋值的就是最小值,不需要做交换了。
                int temp = array[i];
                array[i] = minValues;
                array[minIndex] = temp;
            }
        }
        return array;
    }

属于原地排序
不稳定排序排序
最好的情况下 O(n^2) ,最坏的情况下 O(n^2) ,平均情况下 O(n^2)

是否处于原地排序 是否为稳定排序 最好 最坏 平均
冒泡排序 O(n) O(n^2) O(n^2)
插入排序 O(n) O(n^2) O(n^2)
选择排序 O(n^2) O(n^2) O(n^2)

鉴于他们的复杂度,这些算法适合小规模排序,针对大规模的排序,我们需要了解其他的排序算法。

参考

数据结构与算法之美

猜你喜欢

转载自blog.csdn.net/c_he_n/article/details/83929800