算法-排序

0、复杂度及稳定性

|------|------------------------------------------|------------------------|-----------------|
| | 时间复杂度 | 空间复杂度 | 稳定性(相等元素相对顺序不变) |
| 冒泡排序 | 时间复杂度为O(n^2) 最坏/平均:O(n^2) 最好:O(n),序列有序 | O(1) | 稳定 |
| 插入排序 | 时间复杂度为O(n^2) 最坏/平均:O(n^2) 最好:O(n),序列有序 | O(1) | 稳定 |
| 选择排序 | O(n^2) | O(1) | 不稳定 |
| 希尔排序 | O(nlogn) <O( )<O(n^2) | O(1) | 不稳定 |
| 快速排序 | O(nlogn) 最坏:O(n^2),逆序 | O(logn) | 不稳定 |
| 归并排序 | O(nlogn) | O(n) | 稳定 |
| 堆排序 | O(nlogn) | O(1) | 不稳定 |
| 桶排序 | 最好:O(n) 最坏/平均,与数据分布有关 | O(n+k) k是桶的数量 | 稳定 |
| 计数排序 | O(n+k) | O(k) k是待排序序列中最大值与最小值的差 | 稳定 |
| 基数排序 | O(d(n+k)) d为位数,n为数据总数 k为最大数的位数 | O(n+k) k为最大数的位数 | 稳定 |

一、冒泡排序

比较相邻的元素,若顺序错误则交换

java 复制代码
public void bubbleSort(int[] array) {
    for (int i = 0; i < arr.length - 1; i++) {  
        for (int j = 0; j < arr.length - i - 1; j++) {  
            if (array[j] > array[j+1]) {  
                // 交换 array[j] 和 array[j+1]  
                int temp = array[j];  
                array[j] = array[j+1];  
                array[j+1] = temp;  
            }  
        }  
    }  
}  

二、插入排序

将一个待排序的元素按其大小插入到已排序的序列中的适当位置,直到全部插入完

java 复制代码
public void insertionSort(int[] array) {  
    int n = array.length;  
    for (int i = 1; i < n; ++i) {  
        int key = array[i];  
        int j = i - 1;  
        while (j >= 0 && array[j] > key) {  
            array[j + 1] = array[j];  
            j = j - 1;  
        }  
        array[j + 1] = key;  
    }  
}  

三、选择排序

在未排序序列中找到最小元素,存放到排序序列的起始位置。再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

java 复制代码
public void selectionSort(int[] array) {  
    for (int i = 0; i < array.length - 1; i++) {  
        // 找到最小元素的索引  
        int minIndex = i;  
        for (int j = i + 1; j < array.length; j++) {  
            if (array[j] < array[minIndex]) {  
                minIndex = j;  
            }  
        }  
        // 将找到的最小元素交换到已排序序列的末尾  
        int temp = array[minIndex];  
        array[minIndex] = array[i];  
        array[i] = temp;  
    }  
}  

四、希尔排序

插入排序的一种更高效的改进版

java 复制代码
public void shellSort(int[] array) {  
    int n = array.length;  
    int gap, i, j, temp;  
    // Start with a big gap, then reduce the gap  
    for (gap = n / 2; gap > 0; gap /= 2) {  
        for (i = gap; i < n; i += 1) {  
            temp = array[i];  
            for (j = i; j >= gap && array[j - gap] > temp; j -= gap) {  
                array[j] = array[j - gap];  
            }  
            array[j] = temp;  
        }  
    }  
}  

五、快速排序

采用分治法。选择一个"基准"元素,通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比基准元素小,另一部分的所有数据都比基准元素大。以此类推,达到有序

java 复制代码
public void quickSort(int[] array, int low, int high) {  
    if (low < high) {  
        // 找到基准元素的正确位置  
        int pi = partition(array, low, high);  
        // 分别对基准元素两侧的子序列进行递归排序  
        quickSort(array, low, pi - 1);  
        quickSort(array, pi + 1, high);  
    }  
}  

/* 基准元素分割 */  
public int partition(int[] array, int low, int high) {  
    // 选择最右侧的元素作为基准元素  
    int pivot = array[high];  
    int i = low - 1; // 指向比基准元素小的元素  
    for (int j = low; j < high; j++) {  
        // 如果当前元素小于或等于基准元素  
        if (array[j] <= pivot) {  
            i++;  
            // 交换两个元素  
            int temp = array[i];  
            array[i] = array[j];  
            array[j] = temp;  
        }  
    }  
    // 将基准元素放到正确的位置  
    int temp = array[i + 1];  
    array[i + 1] = array[high];  
    array[high] = temp;  
    return i + 1;  
}  

六、归并排序

先递归分解数组,再将已有序的子序列合并,得到完全有序的序列

java 复制代码
 public void mergeSort(int[] array, int left, int right) {  
    if (left < right) {  
        // 找到中间位置  
        int mid = (left + right) / 2;  
        // 对左半部分进行归并排序  
        mergeSort(array, left, mid);  
        // 对右半部分进行归并排序  
        mergeSort(array, mid + 1, right);  
        // 合并左右两部分  
        merge(array, left, mid, right);  
    }  
}  

public void merge(int[] array, int left, int mid, int right) {  
    // 创建一个临时数组来辅助归并操作  
    int[] temp = new int[right - left + 1];  
    // 左指针和右指针分别指向左半部分和右半部分的起始位置  
    int i = left;  
    int j = mid + 1;  
    int k = 0;  
    // 合并两个有序数组到临时数组  
    while (i <= mid && j <= right) {  
        if (array[i] <= array[j]) {  
            temp[k++] = array[i++];  
        } else {  
            temp[k++] = array[j++];  
        }  
    }  
    // 将左半部分剩余的元素复制到临时数组  
    while (i <= mid) {  
         temp[k++] = array[i++];  
    }  
    // 将右半部分剩余的元素复制到临时数组  
    while (j <= right) {  
        temp[k++] = array[j++];  
    }  
    // 将临时数组中的元素复制回原数组  
    for (i = 0; i < temp.length; i++) {  
        array[left + i] = temp[i];  
    }  
}  

七、堆排序

利用堆进行排序

java 复制代码
public class Heap <T extends Comparable<T>>{
    //定义一个数组来存储堆中的元素
    private T[] items;
    //记录堆中元素个数
    private int Num;

    public Heap(int capacity){
        //因为T继承了Comparable接口,所以这里是new Comparable类型数组
        this.items = (T []) new Comparable[capacity+1]; 
        this.Num = 0;
    }

    //判断堆中索引i处的元素是否小于索引j处的元素
    private boolean less(int i, int j){
        return items[i].compareTo(items[j]);
    }

    //交换堆中i索引和j索引处的值
    private void exchange(int i, int j){
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }

    //往堆中插入一个元素
    public void insert(T t) {
        //因为数组数量Num初始化为0,而这里堆中第一个元素item[0]是不存放任何数值的,
        //所以使用++Num跳过了第一个items【0】
        items[++Num] = t;
        rise(Num);
    }

    //上浮算法,使索引k处的元素能在堆中处于一个正确的位置
    private void rise(int k){
        //通过循环不断比较当前结点的值和其父结点的值,如果当前结点大于父结点就交换两者位置
        while(k>1){
            //比较当前结点和其父结点
            if(less(k/2,k)){
                exchange(k/2,k);
                k = k/2;
            }else{
                break;
            }
        }

    }

    //删除堆中最大的元素,并返回这个最大元素
    public T delMax(){
        T max = items[1];

        //交换索引1处元素和最大索引处的元素,让完全二叉树最右侧的元素变为临时根结点
        exchange(1,Num);

        //删除交换操作后的最大索引处的元素
        items[Num] = null;

        //元素个数减1
        Num--;

        //通过下沉算法,重新排列堆
        sink(1);

        return max;
    }

    //下沉算法,使索引k处的元素能在堆中处于一个正确的位置
    private void sink(int k){
        //通过循环不断比较当前k结点和其左子结点2*k以及右子结点2*k+1处的较大值的元素大小
        //当前结点小,则交换与子节点中最大值的位置
        while(2*k<=Num){
            //获取当前结点的子结点的最大结点
            int max;
            if(2*k+1<=Num){ //判断当前结点是否有右子结点
                if(2*k<2*k+1){
                    max = 2*k+1;
                }else{
                    max = 2*k;
                }
            }else{
                max = 2*k;
            }

            //比较当前结点和较大结点的值
            if(!less(k,max)){
                break;
            }

            //交换k索引的值和max索引处的值
            exchange(k,max);

            //交换k的值
            k = max;
        }
    }
}

八、桶排序

1、数据分桶,桶编号 = (数组元素 - 最小值) * (桶个数 - 1) / (最大值 - 最小值)

2、每个桶排序

3、遍历

java 复制代码
/**
 * 桶排序
 *
 */
public static void bucketsort(int[] arr, int bucketSize) {
    // 初始化最大最小值
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    // 找出最小值和最大值
    for (int num : arr) {
        max = Math.max(max, num);
        min = Math.min(min, num);
    }

    // 创建bucketSize个桶
    List<List<Integer>> bucketList = new ArrayList<>();// 声明五个桶
    for (int i = 0; i < bucketSize; i++) {
        bucketList.add(new ArrayList<>());// 确定桶的格式为ArrayList
    }

    // 将数据放入桶中
    for (int num : arr) {
        // 确定元素存放的桶号
        int bucketIndex = (num - min) * (bucketSize - 1) / (max - min);
        // 将元素存入对应的桶中
        List<Integer> list = bucketList.get(bucketIndex);
        list.add(num);

    }
    // 遍历每一个桶
    for (int i = 0, arrIndex = 0; i < bucketList.size(); i++) {
        List<Integer> list = bucketList.get(i);
        // 对每一个桶排序
        list.sort(null);
        for (int value : list) {
            arr[arrIndex++] = value;
        }
    }
}

九、计数排序

java 复制代码
/**
 * 计数排序
 *
 */
public static void countingSort(int[] arr) {
    if (arr.length == 0) {
        return;
    }
    // 原数组拷贝一份
    int[] copyArray = Arrays.copyOf(arr, arr.length);
    // 初始化最大最小值
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    // 找出最小值和最大值
    for (int num : copyArray) {
        max = Math.max(max, num);
        min = Math.min(min, num);
    }

    // 新开辟一个数组用于统计每个元素的个数(范围是:最大数-最小数+1)
    int[] countArray = new int[max - min + 1];
    // 增强for循环遍历
    for (int num : copyArray) {
        // 加上最小偏差是为了让最小值索引从0开始,同时可有节省空间,每出现一次数据就加1
        // 真实值+偏差=索引值
        countArray[num - min]++;
    }

    // 获取数组的长度
    int length = countArray.length;
    // 计数数组变形,新元素的值是前面元素累加之和的值
    for (int i = 1; i < length; i++) {
        countArray[i] = countArray[i] + countArray[i - 1];
    }

    // 遍历拷贝数组中的元素,填充到原数组中去,从后往前遍历
    for (int j = copyArray.length - 1; j >= 0; j--) {
        // 数据对应计数数组的索引
        int countIndex = copyArray[j] - min;
        // 数组的索引获取
        //(计数数组的值n就是表示当前数据前有n-1个数据,数组从0开始,故当前元素的索引就是n-1)
        int index = countArray[countIndex] - 1;
        // 数组中的值直接赋值给原数组
        arr[index] = copyArray[j];
        // 计数数组中,对应的统计值减1
        countArray[countIndex]--;
    }
}

十、基数排序

java 复制代码
/**
 * 基数排序
 *
 */
public static void radixSort(int[] arr) {
    // 初始化最大值
    int max = Integer.MIN_VALUE;
    // 找出最大值
    for (int num : arr) {
        max = Math.max(max, num);
    }

    // 我们这里是数字,所以初始化10个空间,采用LinkedList
    LinkedList<Integer>[] list = new LinkedList[10];
    for (int i = 0; i < 10; i++) {
        list[i] = new LinkedList<>();// 确定桶的格式为ArrayList
    }

    // 个位数:123 / 1 % 10 = 3
    // 十位数:123 / 10 % 10 = 2
    // 百位数: 123 / 100 % 10 = 1
    for (int divider = 1; divider <= max; divider *= 10) {
        // 分类过程(比如个位、十位、百位等)
        for (int num : arr) {
            int no = num / divider % 10;
            list[no].offer(num);
        }
        int index = 0; // 遍历arr原数组
        // 收集的过程
        for (LinkedList<Integer> linkedList : list) {
            while (!linkedList.isEmpty()) {
                arr[index++] = linkedList.poll();
            }
        }
    }
}
  • 计数排序:每个桶只存储单一键值
  • 桶排序:每个桶存储一定范围的数值
  • 基数排序:根据键值的每位数字来分配桶
相关推荐
ABin-阿斌1 分钟前
SpringBoot 整合 Easy_Trans 实现翻译的具体介绍
java·spring boot·后端
边疆.1 分钟前
数据结构:内部排序
c语言·开发语言·数据结构·算法·排序算法
菜鸟求带飞_3 分钟前
算法打卡:第十一章 图论part03
java·数据结构·算法·深度优先·图论
圆头圆脑圆JAVA4 分钟前
简单了解微服务--黑马(在更)
java·spring boot·微服务
木子欢儿11 分钟前
在 Debian 12 上安装 Java 21
java·运维·开发语言·debian
一二小选手14 分钟前
【高级编程】XML DOM4J解析XML文件(含案例)
xml·java
终末圆15 分钟前
MyBatis XML映射文件编写【后端 18】
xml·java·开发语言·后端·算法·spring·mybatis
就这个java爽!16 分钟前
超详细的XML介绍【附带dom4j操作XML】
xml·java·开发语言·数据库·青少年编程·eclipse
kunkun10118 分钟前
Mybatis的XML实现方法
xml·java·mybatis
libai24 分钟前
STM32 USB HOST CDC 驱动CH340
java·前端·stm32