5、排序算法

69 阅读4分钟

1、冒泡排序

冒泡排序思想就是犹如冒泡一样,一级一级向上进行比较  
1. 从开头开始相邻两个数比较,后一个比前一个小的话进行交换,直到结尾,此时结尾为最大值  
2. 第二轮也从开始进行,继续进行比较,到结尾-1处  
3. 直到剩下最后一个元素
4. 通过一个flag判断,当在某一轮中没有进行元素的交换,则此时循环退出

image.png

public static <T extends Comparable> void bubblingSort(T[] arrays) {  
    boolean flag = false;  
    for (int i = 0; i < arrays.length - 1; i++) {  
        for (int j = 0; j < arrays.length - 1 - i; j++) {  
            if (arrays[j].compareTo(arrays[j + 1]) == 1) {  
                swap(arrays, j, j + 1);  
                flag = true;  
            }  
        }  
        if (!flag) {  
            break;  
        }  
    }  
}

2、选择排序

1. 选择排序会先选择一个数作为最小值,然后根后面的数据进行比较,选取到最小值,然后和最初的基准值进行交换
2. 循环进行,每一轮选取该轮的i元素作为基准,及第1轮为第一个元素作为基准,第二轮第二个元素作为基准
3. 比较的时候,从基准元素的下一个元素开始比较
4. 进行循环时,每一轮都会找到最小值放在前面

image.png

public static <T extends Comparable> void selectSort(T[] arrays) {  
    for (int i = 0; i < arrays.length - 1; i++) {  
        int minIndex = i;  
        for (int j = i + 1; j < arrays.length; j++) {  
            if (arrays[j].compareTo(arrays[minIndex]) == -1) {  
                minIndex = j;  
            }  
        }  
        if (minIndex != i) {  
            swap(arrays, i, minIndex);  
        }  
    }  
}

3、插入排序

插入排序是先预设一个有序数列,然后把后一个元素向数列中进行插入,是其到达有序的效果
1. 预设第一个元素为有序数列
2. 定义一个变量记录每次要向有序数列中插入的元素,及循环的次数,从i=1开始,及第一次要插入的元素为坐标i的元素
3. 待插入的元素和前面元素比较,每比较依次,元素向后移动依次,直到遇到小于待插入元素或者标记变量小于1的时候
4. 将该标记坐标赋值为待插入的原素
5. 重复循环

image.png

public static <T extends Comparable> void insertSort(T[] arrays) {  
    for (int i = 1; i < arrays.length; i++) {  
        int index = i;  
        T indexValue = arrays[i];  
        //从后向前  
        while (index > 0 && arrays[index].compareTo(arrays[index - 1]) == -1) {  
            swap(arrays, index, index - 1);  
            index--;  
        }  
        arrays[index] = indexValue;  
    }  
}

4、希尔排序

希尔排序是对插入排序的改进,插入排序当最小值一直在最后的时候需要一直对数组进行移动赋值,它又被称为缩小增量排序,它把数组进行增量分组,对每一组进行插入排序,当增量为1时,算法终止。它的好处是每一次插入排序后,在进行下一次增量排序的时候,不用频繁移动元素
1. 设值增量,length/2,对元素数列进行分组
2. 每一个元素的步长为length/2,替换原先的插入排序的步长1
3. 对每组进行插入排序
4. 继续缩小增量,循环进行,直到增量为1

image.png

public static <T extends Comparable> void shellSort(T[] arrays) {  
    int step = arrays.length / 2;  
    while (step > 0) {  
        for (int i = step; i < arrays.length; i++) {  
            int index = i;  
            T indexValue = arrays[index];  
            while (index > step - 1 && arrays[index].compareTo(arrays[index - step]) == -1) {  
                swap(arrays, index, index - step);  
                index -= step;  
            }  
            if (index != i) {  
                arrays[index] = indexValue;  
            }  
        }  
        step /= 2;  
    }  
}

5、快速排序

快速排序是用双指针法进行
1. 先取一个标准,通常取第一个元素  
2. 设置两个头尾指针,分别移动并和标准元素进行比较,当头指针遇到比标准元素大的,尾指针遇到比标准元素小的进行交换。  
3. 当头尾指针相遇时,本次循环结束,并把标准元素和相遇点元素互换  
4. 继续选取标准元素,进行递归操作

image.png

public static <T extends Comparable> void quickSort(T[] arrays,int begin,int end){  
    if(begin > end || begin < 0 || end >= arrays.length){  
        return;  
    }  
    T standard = arrays[begin];  
    int i = begin;  
    int j = end;  
    while (i != j){  
        while (arrays[j].compareTo(standard) != -1 && j > i) {  
            j--;  
        }  
        while (arrays[i].compareTo(standard) != 1  && j > i) {  
            i++;  
        }  
        if (j > i) {  
            swap(arrays,j,i);  
        }  
    }  
    arrays[begin] = arrays[i];  
    arrays[i] = standard;  
    quickSort(arrays,begin,i-1);  
    quickSort(arrays,i+1,end);  
}

6、归并排序

归并排序主要算法思想就是分治算法:先把大的问题分成一个个小的问题,然后把小的问题的答案结合在一起,归并排序先把数组分成一个个的小单元,然后每个小单元进行结合,组成单元,然后各个单元在进行结合排序最终组合成一个。
1. 先进性分,递归进行
2. 治:当递归到最后一次的时候进行合并
3. 借助辅助数组,双指针进行比较,小的加入辅助数组,并移动指针。直到其中一个移动到mid元素,把剩余元素加入到辅助数组
4. 然后在进行数组拷贝,把辅助数组中的数据移动到原数组中相应的位置
5. 递归进行治操作,直到最后一次拷贝全部数组

image.png

public static <T extends Comparable> void mergeSort(T[] arrays,int left,int right,T[] result){  
    if (left < right){  
        int mid = (right + left) / 2;  
        mergeSort(arrays,left,mid,result);  
        mergeSort(arrays,mid + 1,right,result);  
  
  
        //合并  
        int i = left;  
        int j = mid + 1;  
        int t = 0;  
        while (i <= mid && j <= right){  
            if (arrays[i].compareTo(arrays[j]) != 1) {  
                result[t++] = arrays[i++];  
            }else {  
                result[t++] = arrays[j++];  
            }  
        }  
  
        while (i <= mid){  
            result[t++] = arrays[i++];  
        }  
        while (j <= right){  
            result[t++] = arrays[j++];  
        }  
  
        t = 0;  
        int index = left;  
        while (index <= right) {  
            arrays[index++] = result[t++];  
        }  
    }  
  
}

7、基数排序

基数排序是基于桶排序进行的,他是采用分配式排序,采用空间换时间的方式进行。让所有数值补充为同样的长度,数位较短的补充零。从最低位开始,依次进行一次排序,从最低位到最高位直到排序完成
1. 定一个长度为10的二维数组,在定义一个长度为10的一维数组,记录每个桶保存的长度
2. 对排序的数字进行获取个位的操作,并根据个位数字的大小分别放入二维数组中,及各位数字为1放入arr[1]的桶中,并把记录长度的值加1
3. 根据记录长度一维数组的值从桶中获取值,并赋值给原数组,继续进行循环操作,大于当前数组的最大值的大小

image.png

public static void radixSort(int[] arrays){  
    int[][] bucket = new int[10][arrays.length];  
    int[] bucketMark = new int[10];  
    Integer max = arrays[0];  
    for (int i = 1; i < arrays.length; i++) {  
        if (arrays[i] > max){  
            max = arrays[i];  
        }  
    }  
    int maxLength = max.toString().length();  
    for (int i = 0, n = 1; i < maxLength; i++,n *= 10) {  
        for (int j = 0; j < arrays.length; j++) {  
            int index = arrays[j] / n % 10;  
            bucket[index][bucketMark[index]] = arrays[j];  
            bucketMark[index]++;  
        }  
        int index = 0;  
        for (int m = 0; m < bucketMark.length; m++) {  
            if (bucketMark[m] != 0) {  
                for (int k = 0; k < bucketMark[m]; k++) {  
                    arrays[index++] = bucket[m][k];  
                }  
                bucketMark[m] = 0;  
            }  
        }  
    }  
}

总结

排序时间复杂度稳定性优缺点
冒泡排序O(n2)稳定冒泡排序当有序的时候,最优,不用进行全部遍历,每次进行一轮都会获取到最大
或者最小元素
选择排序O(n2)不稳定选择排序是根据每次循环获取最小值最大值进行替换
插入排序O(n2)稳定插入排序是想预设有序数列,根据后续数值想有序数列中进行插入
,缺点是当小的数值越靠后,进行移动赋值的数据越多
希尔排序O(nlogn)不稳定对插入排序的改进,每进行一轮,数值的序列就无限接近于有序数列,到最后一轮移动的元素就越少
快速排序O(nlogn)不稳定利用双指针法进行性移动交换,并进行递归
归并排序O(nlogn)稳定分治算法
基数排序O(n * k)稳定只能运用于数值的排序,而且负数小数的时候要进行特殊处理