十大排序-OTW为序

以O时间复杂度,T思考,W写代码为序介绍十大排序。

本文实现语言为:JAVA(世界上最好的语言(*^▽^*))

冒泡排序 - 选择排序 - 插入排序 - 快速排序 - 希尔排序

- 归并排序 - 堆排序 - 基数排序 - 桶排序 - 计数排序

冒泡排序

思想:每次都把最大的数交换到当前最后面,然后该位置再不去修改。

        外层每层排序用一个布尔值判断是否已满足大小序列,满足则直接break。

        正因为若原位置序列就满足大小关系,则判断中不会变动相对位置,则冒泡排序稳定

核心思想:不断比较并交换相邻元素,使较大元素逐渐向后移

时间复杂度:O(n^2)

空间复杂度:O(1)

平均时间复杂度:O(n^2)

// ASC 结果为递增
public void bubbleSort(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        boolean flag = false;  // 用于标记是否交换过
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag = true;
            }
        }
        // 本轮都未判断过,说明已满足大小序列
        if (!flag) {
            break;
        }
    }
}

事例:

[4, 2, -3, 66, 5]
[2, -3, 4, 5, 66]
[-3, 2, 4, 5, 66] 
break

选择排序

思想:每次把后面最小的数的索引位记录下来替换当前位置,确保当前位是后面值最小的,然后

        依次往后排。可能看起来跟冒泡很类似,但不一样的是选择排序只把该交换的那一位记录,

        最后直接交换,而冒泡是一判断成功就交换,显然选择排序节省了大多交换的时间。不过

        选择排序不稳定

核心思想:未排序序列中找到最小元素,将其放到排序序列的始位,再从未排序的序列中寻找最小元素,放至已排序序列的末尾。

时间复杂度:O(n^2)

空间复杂度:O(1)

平均时间复杂度:O(n^2)

// ASC
public void selectSort(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        // 标记当前最小索引
        int minIndex = i;
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[minIndex] > arr[j]) {
                minIndex = j;
            }
        }
        
        if (minIndex > i) {
            int temp = arr[minIndex];
            arr[minIndex] = arr[i];
            arr[i] = temp; 
        }
    }
}
 

事例:

[4, 2, -3, 66, 5]
[-3, 4, 2, 66, 5]
[-3, 2, 4, 66, 5]
[-3, 2, 4, 66, 5]   // 无交换
[-3, 2, 4, 5, 66]
end

插入排序

思想:每次循环记录当前值、索引,然后根据大小后移到该放入的位置(此时是遍历区间的正确大小位置),放入。会发现并没有交换,而是直接赋值,所以需要记录当前值。结果稳定

核心思想:未排序元素逐个插入到已排序序列中的合适位置

时间复杂度:O(n^2)

空间复杂度:O(1)

平均时间复杂度:O(n^2)

// ASC
public void insertSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int insertIndex = i;
        int insertVal = arr[i];
        while (insertIndex > 0 && arr[insertIndex - 1] > insertVal) {
            arr[insertIndex] = arr[insertIndex - 1];
            insertIndex--;
        }
        arr[insertIndex] = insertVal;
    }
}

事例:

[4, 2, -3, 66, 5]
[2, 4, -3, 66, 5]
[-3, 2, 4, 66, 5]
[-3, 2, 4, 66, 5]   // 无交换
[-3, 2, 4, 5, 66]
end

快速排序

思想:每次排序使要排序数据分成两边,一边的数据完全小于另一边的数据,再递归做快排。结果不稳定。(按基准元素递归)

核心思想:分治

最坏时间复杂度:O(n^2 ) 【就数组已经排好序的情况】

空间复杂度:O(log(n))

平均时间复杂度:O(n log(n))

public void quickSort(int[] arr, int left, int right) {
    int l = left;
    int r = right;
    int mid = (l + r) >> 1;
    while (l < r) {
        while (arr[l] < arr[mid]) l++;
        while (arr[r] > arr[mid]) r--;
        if (l >= r) break;
        
        int temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;

        while (arr[l] == arr[mid]) l++;
        while (arr[r] == arr[mid]) r--;
    }
    // 当前元素恰中立的情况且无重中心元
    if (l == r) {
        l++;
        r--;
    }
    if (left < r) quickSort(arr, left, r);
    if (l < right) quickSort(arr, l, right);
}

事例:

[4, 2, -3, 66, 5]
[-3, 2, 4, 66, 5]  // -3和4交换
[-3, 2, 4, 66, 5] 与 [-3, 2, 4, 5, 66]  // 5与66交换
end

希尔排序

思想:相当于不断平均拆分,然后按拆分间隔做插入排序,称按增量的插入排序,不稳定

核心思想:分组插入排序,逐渐缩小间隔

时间复杂度:O(n log^2(n))

空间复杂度:O(1)

平均时间复杂度:O(n log(n))

希尔排序有位移法和交换法,这里写的是速度更快的位移法。

public static void shellSort(int[] arr) {
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < arr.length; i++) {
            // 这里面就是一个按间隔的插入排序
            int j = i;
            int val = arr[i];
            while (j - gap >= 0 && arr[j - gap] > val) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = val;
        }
    }
}

事例:

[4, 2, -3, 66, 5]
[-3, 2, 4, 66, 5]  // 4和-3比较 
[-3, 2, 4, 66, 5]  // 66和2比较
[-3, 2, 4, 66, 5]  // 5和4比较
[-3, 2, 4, 66, 5]  // 这里快速就相当于一个插入排序了,2和-3比较
[-3, 2, 4, 66, 5]  // 4和2比较
[-3, 2, 4, 66, 5]  // 66和4比较
[-3, 2, 4, 5, 66]  // 5和66且5和4比较
end

归并排序

思想:归并是使用分治的策略,不断二分拆分,然后从低置顶排序,同时利用一个辅助数组存值,稳定

核心思想:空间换时间,分治,使有序子序列依次合并

时间复杂度:O(n log(n))

空间复杂度:O(n)

平均时间复杂度:O(n log(n))

public static void mergeSort(int[] arr, int left, int right, int[] temp) {
    if (left < right) {
        int mid = (left + right) / 2;
        mergeSort(arr, left, mid, temp);
        mergeSort(arr, mid + 1, right, temp);
        
        // 利用temp排序
        int i = left;
        int j = mid + 1;
        int t = 0;
        while (i <= mid && j <= right) {
            temp[t++] = (arr[i] < arr[j]) ? arr[i++] : arr[j++];
        }
        while (i <= mid) temp[t++] = arr[i++];
        while (j <= right) temp[t++] = arr[j++];
        // 根据传入范围赋值回arr
        t = 0;
        while (left + t <= right) arr[left + t] = temp[t];
    }
}

事例:

[4, 2, -3, 66, 5]
[2, 4, -3, 66, 5]  // 2和4比较
[-3, 2, 4, 66, 5]  // -3,2,4比较
[-3, 2, 4, 5, 66]  // 5和66比较
[-3, 2, 4, 5, 66]  
end

堆排序

思想:把数组当完全二叉树,首元素为根节点,使数组成大顶堆,然后依次把根节点和数组边缘叶子节点交换,再成顶堆,再交换,最后排序成功,结果不稳定

核心思想:构建堆,依次交换把最大值放尾节点

时间复杂度:O(n log(n))

空间复杂度:O(1)

平均时间复杂度:O(n log(n))

public void heapSort(int[] arr) {
    // 从第一个非叶子节点遍历每个非叶子节点
    for (int i = arr.length / 2 - 1; i >= 0; i--) {
        becomeHeap(arr, i, arr.length);
    }

    // 叶子最边缘与根节点值交换,此时的堆层都是有序的,所有只需对根节点做成顶堆操作
    for (int i = arr.length - 1; i > 0; i--) {
        int temp = arr[i];
        arr[i] = arr[0];
        arr[0] = temp;
        becomeHeap(arr, 0, i);
    }
}

/**
 * @param arr 待调整的数组                        
 * @param i 非叶子节点在数组中的索引(从这个i,说明是局部的)       
 * @param len 截止元素长度      
 */
private void becomeHeap(int[] arr, int i, int len) {
    int val = arr[i];  // 记录
    for (int k = 2 * i + 1; k < len; k = 2 * k + 1) {
        if (k + 1 < len && arr[k] < arr[k + 1]) {
            k++;  // 右子大则当前转换为右子
        }
        if (arr[k] > val) {
            arr[i] = arr[k];  // 上移  
            i = k;            // i此时记录的是下移状态
        } else {
            break;
        }
    }
    arr[i] = val;
}

事例:

[4, 2, -3, 66, 5]
// 成大顶堆
[4, 66, -3, 2, 5]
[66, 4, -3, 2, 5]
[66, 5, -3, 2, 4]
// 与最后一个调换
[4, 5, -3, 2, 66]   
// 成大顶堆
[5, 4, -3, 2, 66]
// 与最后一个调换
[2, 4, -3, 5, 66]
// 成大顶堆
[4, 2, -3, 5, 66]
// 与最后一个调换
[-3, 2, 4, 5, 66]
// 成大顶堆
[2, -3, 4, 5, 66]
// 与最后一个调换
[-3, 2, 4, 5, 66]
end

基数排序

思想:很多文章也把它叫做桶排序、卡片排序,但是有一个区分点在于,基数排序是非比较整数的方式去排序的,而是按位数切割成不同的数字,然后按每次每个位数去放入数组,甚至在表层可以认为是没有比较的。显然稳定。是一种非比较型整数排序算法。按每个位数分别比较。由于整数也可以表达字符和浮点数等数据类型,因此基数排序也不限于整数。

核心思想:按位分配,逐级收集整理。

时间复杂度:O(n × k)

空间复杂度:O(n + k)

平均时间复杂度:O(n × k)

public static void RaSort(int[] arr) {
    int n = arr.length;
    // 首先创建桶
    int[][] bucket = new int[10][n];
    int[] bucketNumber = new int[10];
    // 其次获取最大值得到遍历终止条件
    int maxV = arr[0];
    for (int i = 1; i < n; i++) {
        maxV = Math.max(maxV, arr[i]);
    }
    int maxLength = (maxV + "").length();

    // 遍历到最大位数
    for (int i = 0, num = 1; i < maxLength; i++, num *= 10) {
        // 把数组的数都放入对应数位桶中
        for (int j = 0; j < n; j++) {
            int k = arr[j] / num % 10;  // 位数
            bucket[k][bucketNumber[k]++] = arr[j];
        }
        int index = 0;  // 给arr用
        // 把所有桶中的数,按桶的位数大小取出
        for (int j = 0; j < 10; j++) {
            // 对桶中有数的桶依次取出(显然不改变原顺序-稳定)
            if (bucketNumber[j] > 0) {
                for (int k = 0; k < bucketNumber[j]; k++) {
                    arr[index++] = bucket[j][k];
                }
                // 清空留给下一次外遍历使用
                bucketNumber[j] = 0;  
            }
        }
    }
}

事例:

[4, 1, 151, 192, 29]
# 第一位
[1, 151, 192, 4, 29]
# 第二位
[1, 4, 29, 151, 192]
# 第三位
[1, 4, 29, 151, 192]
end

桶排序

思想:获取数组的最大小值和设置的桶容量来确定桶数目,然后按大小放入每个桶中,然后对每个桶排序,最后再合为一体,稳定

核心思想:分桶,桶内排序,按顺序合并

时间复杂度:O(n^2)

空间复杂度:O(n + k)

平均时间复杂度:O(n + k)

public void backetSort(int[] arr, int bucketSize) {
    // 获取数组最大、最小值    
    int min = arr[0];
    int max = arr[0];    
    for (int i = 1; i < arr.length; i++) {
        if (arr[i] < min) {
            min = arr[i];
        } else if (arr[i] > max) {
            max = arr[i];
        }
    }

    // 桶数目,并初始化这些桶
    int bucketCount = (max - min) / bucketSize + 1;  
    List<List<Integer>> buckets = new ArrayList<>(bucketCount);
    for (int i = 0; i < bucketCount; i++) {
        buckets.add(new ArrayList<>());
    }    

    // 元素入桶
    for (int i = 0; i < arr.length; i++) {
        buckets.get((arr[] - min) / bucketSize).add(arr[i]);
    }

    // 每个桶的元素进行排序,并合并为一个有序数组
    int currentIndex = 0;
    for (int i = 0; i < bucketCount; i++) {
        List<Integer> bucket = buckets.get(i);
        Collections.sort(bucket);
        for (int j = 0; j < bucket.size(); j++) {
            arr[currentIndex++] = bucket.get(j);
        }
    }
}

事例:

[4, 2, -3, 66, 5]  // 假设令桶大小为30
[4, 2, -3, 5]与[66]
[-3, 2, 4, 5]与[66]
[-3, 2, 4, 5, 66]
end

计数排序

思想:计数排序,是一种非比较型整数排序算法。它的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数,利用这个信息将元素x直接放在输出序列中的正确位置上。显然稳定。

核心思想:统计元素频数,构建有序序列。

时间复杂度:O(n + k)

空间复杂度:O(k)

平均时间复杂度:O(n + k)

(k是整数的范围(数组最大值-数组最小值),在是一种用空间换时间的操作,虽然速度上要明显优于其他排序算法,但在k越大时,对空间的占用越大,并不适于大范围操作)

    public static void count(int[] arr) {
        int n = arr.length;

        // 先找最大最小值
        int minV = arr[0];
        int maxV = arr[0];
        for (int i = 1; i < n; i++) {
            minV = Math.min(minV, arr[i]);
            maxV = Math.max(maxV, arr[i]);
        }

        // 初始计数数组
        int[] countArr = new int[maxV - minV + 1];
        // 把arr中数放入计数数组中
        for (int value : arr) {
            countArr[value - minV]++;
        }

        // 更新计数数组,使得每个数字都表示其前面有多少个数字
        for (int i = 1; i < countArr.length; i++) {
            countArr[i] += countArr[i - 1];
        }

        // 一个数组记录结果
        int[] result = new int[n];
        for (int value : arr) {
            result[countArr[value - minV] - 1] = value;
            countArr[value - minV]--;  // 避免数组有相同的值
        }

        // 拷贝数组
        System.arraycopy(result, 0, arr, 0, n);
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值