栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

排序算法小总结

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

排序算法小总结

1.直接插入排序算法

与打扑克牌类似,将要排序的牌(元素)不断地加入到已排序的牌(元素)中去。把要排序的数组分为了两个部分,一部分是排序好的元素,另一部分是待插入的元素,如果选择的元素比已排序好的元素小,则交换,直到全部元素都比较过为止。

每一次排序相当于一次反向冒泡

具体代码:

import java.lang.reflect.Array;
import java.util.Arrays;

public class Solution {
    public static void main(String[] args) {
        InsertSort(new int[] { 9 ,20 , 10, 13 , 12});
    }
    public static void InsertSort(int [] arr){
        int value;//待插入元素
        int index;//初始值为待插入元素前一个元素的索引

        for(int i= 1 ; i< arr.length;i++){
            //i从第二个元素开始,默认第一个元素是有序的
            //循环条件是小于数组长度,因为也要将最后一个元素插入到前面的序列
            value = arr[i];
            index = i - 1;//初始为前一个元素
            while(index >=0 && value < arr[index]){
                //需要保证index合法
                //每当前面的元素比待插入元素大,就向后移动
                arr[index + 1] = arr[index];
                //不用怕覆盖,因为value保存着待插入的值
                index--;
            }
            //跳出循环条件:index=-1或value>=arr[index]
            //因此 选择在此时index右边插入元素
            arr[index + 1] = value;
        }

        System.out.println(Arrays.toString(arr));
    }
}

时间复杂度:

最优为O(n),比如原数组已经是有序数组了,此时每个元素只需要跟前面一个元素比较一次,共比较n-1次。最差为O(n²),即第i个元素都要与前面共i-1个元素比较。可以认为直接插入排序时间复杂度为O(n²)。

空间复杂度为O(1)

 2.希尔排序

希尔排序算法是直接插入算法的一种改进,其基本思想为按步长先将数据进行分组,对每个组进行直接插入排序,而后减小步长,重复分组+排序,直到步长为1。

import java.util.Arrays;
public class ShellSort {

    public static void sort(int[] a){

        //确定初始的步长
        int h = 1;
        while(h=1){//h=7,3,1
            //i用于确定分组的起点
            for(int i = h;i=h;j-=h){
                    //步长不同的插入排序
                    if(a[j-h]>a[j]){
                        int temp = a[j-h];
                        a[j-h] = a[j];
                        a[j] = temp;
                    }else{

                        break;
                    }
                }
            }
            //减小步长
            h/=2;
        }

    }

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

 例子来源

时间复杂度:取决于步长,假设以N/2作为起始步长,则最坏情况为O(N²)。

空间复杂度:O(1)

3.归并排序

归并排序是分治思想的体现,其思路是将数组拆分成子数组直到每个数组只有一个元素,而后再将数组两两合并,直到合并成为一个数组。

public static void sort(int[] a,int left,int right){
    if(right>=left)return; //当子数组长度为1时退出
    int mid = (right+left)/2;
    sort(int[] a,left,mid);
    sort(int[] a,mid+1;right);
    merge(int[] a,left,mid,right);
}

public static void merge(int[]a,int left,int mid,int right){
    int i = left;
    int p1 = left;
    int p2 = mid+1;
    int[] temp = new int[a.length];
    //三个指针 两个数组哪个元素小就放入temp数组
    while(p1<=mid&&p2<=right){
       if(a[p1]

空间复杂度: O(NlogN)

时间复杂度: O(N)

具体证明见此处

4.简单选择排序

类似冒泡排序,每次都将子序列中最小的数放在最前面。

import java.util.Arrays;
public class SelectSort {

    public static void sort(int[] a){
        //选择的次数,数组长度减一 次
        for(int i = 0; i
空间复杂度:O(N²)

时间复杂度: O(1)

5.快速排序

快速排序是冒泡排序的一种改进,冒泡排序每次对相邻的两个数比较,比较消耗时间。

思路:运用了迭代和分治的思想,将数组先进行排序,然后再将其划分成小数组,然后重复排序。

排序方式:设左右两个指针和一个基准数(假设第一个元素),右指针从数组末尾开始反向遍历数组,左指针从另一端开始(索引0)。右指针负责寻找比基准数大的数字,找到即停,左指针负责找比基准数小的数字,找到即停,然后交换两指针对应元素的值,当两指针相遇,则该处为基准数要插入的位置。 此时基准数左侧的数组元素全部小于等于基准数,基准数右侧元素全部大于等于基准数。再将数组分为左右两个子数组,重复上述排序。

public class QuickSort {

//arr 需要排序的数组
//low 开始时最左边的索引=0
//high 开始时最右边的索引=arr.length-1
    public static void quickSort(int[] arr,int low,int high){
        int i,j,temp,t;
        if(low>high){
            return;
        }
        i=low;#左边哨兵的索引
        j=high;#右边哨兵的索引
        //temp就是基准位
        temp = arr[low];#以最左边为  基准位
        //里基准数远的那个指针先动 即右烧饼索引
        while (i基准位的数 时
            #继续从右往左找(同时 j 索引-1)
            #找到后会跳出 while循环
            while (temp<=arr[j]&&i=arr[i]&&i 

时间复杂度:O(NlogN)

空间复杂度: O(1) 并未申请额外的空间,而是在原数组上进行操作。

6.冒泡排序

冒泡排序的思想是,多次遍历数组,每次都将最大的数放在最后。

public static void sort(int[] a){
        //需要冒泡的次数
        for(int i = 0 ; ia[j+1]){
                    int temp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = temp;
                }
            }
        }

    }

时间复杂度:O(N)

空间复杂度:O(1)

7.堆排序

堆:堆可以看做一个完全二叉树的数组形式。堆又分为大顶堆和小顶堆。

  • 大顶堆:每个结点的值都大于或等于左右子结点的值。
  • 小顶堆:每个结点的值都小于或等于左右子结点的值。

 堆排序的思路:将待排序列构造成一个大顶堆,然后将堆顶元素和数组最后一个元素(arr.length-1)互换,类似于冒泡排序,最大的元素已经被放在最后,再对[0,arr.length-2]的子序列进行排序,不断重复,每一次的堆顶都会被排到子序列的最后位置。大顶堆得到的是升序的排列,小顶堆得到的是降序的排列。

import java.util.Arrays;
public class HeapSort {

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

    
    public static void heapSort(int[] arr){
        //为什么从arr.length/2-1开始?
        //因为arr.length/2-1是二叉树最后一个非叶子结点的下标

        //第一次排序 从最后一个非叶子结点开始 倒序进行
        for (int i = arr.length/2-1; i >= 0 ; i--) {
            adjustHeap(arr,i,arr.length);
        }
        
        //交换子序列堆顶元素的位置 并对子序列进行排序
        for (int j = arr.length-1; j > 0; j--) {
            int temp = arr[j];
            arr[j] = arr[0];
            arr[0] = temp;
            
            adjustHeap(arr,0,j);

        }
    }

    
    public static void adjustHeap (int[] arr,int i,int length){
        
        int temp = arr[i];

        
        for (int j = i * 2 + 1; j < length ; j = j * 2 + 1) {
            if (j+1 < length && arr[j] < arr[j+1]){ //左子结点小于右子结点
                j++; //j指向右子结点
            }
            if (arr[j] > temp){ //子节点大于父节点
                arr[i] = arr[j]; //把较大的值赋值给父节点
                //arr[j] = temp; 这里没必要换
                i = j; //让i指向与其换位的子结点 因为
            }else{
                
                break;
            }
        }
        arr[i] = temp;
    }
}

其实整个排序主要核心就是堆化过程,堆化过程一般是用父节点和他的孩子节点进行比较,取最大的孩子节点和其进行交换;但是要注意这应该是个逆序的,先排序好子树的顺序,然后再一步步往上,到排序根节点上。然后又相反(因为根节点也可能是很小的)的,从根节点往子树上排序。最后才能把所有元素排序好;
 

时间复杂度:O(NlogN)

空间复杂度:O(1) 因为是就地排序,原地更改数组。

8.计数排序

思想:用辅助数组对数组中出现的数字计数,元素转下标,下标转元素。

public static int[] sort ( int[] arr ) {
        if (arr.length < 2) {
            return arr;
        }
        int min = arr[0], max = arr[0];
        //找出最小值和最大值
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] < min) {
                min = arr[i];
            }
            if (arr[i] > max) {
                max = arr[i];
            }
        }

        //创建辅助数组,数组大小为原数组的最大值
        int[] helper = new int[max - min + 1];
        for (int e : arr) {
            //将原数组的元素作为辅助数组的下标,出现一次次数+1
            //e - min计算偏差
            helper[e - min]++;
        }
        //数据回填的位置
        int current = 0;
        //遍历辅助数组
        for (int i = 0; i < helper.length; i++) {
            //当前元素出现次数大于1,依次赋给原数组
            while (helper[i] > 0) {
                //每次赋值后,指针右移
                //i + min回填数据计算偏差
                arr[current++] = i + min;
                helper[i]--;
            }
        }
        return arr;
    }

空间复杂度:O(N+M) 其中 M 是原数组的最大值 

时间复杂度:O(N+M) 其中 M 是原数组的最大值(即辅助数组的空间大小)。

由此可见计数排序适用于最大值较小的情况。

9.桶排序

桶排序是计数排序的一种进阶版本。

public static void bucketSort(int[] arr){
    
    // 计算最大值与最小值
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for(int i = 0; i < arr.length; i++){
        max = Math.max(max, arr[i]);
        min = Math.min(min, arr[i]);
    }
    
    // 计算桶的数量
    int bucketNum = (max - min) / arr.length + 1;
    ArrayList> bucketArr = new ArrayList<>(bucketNum);
    for(int i = 0; i < bucketNum; i++){
        bucketArr.add(new ArrayList());
    }
    
    // 将每个元素放入桶
    for(int i = 0; i < arr.length; i++){
        int num = (arr[i] - min) / (arr.length);
        bucketArr.get(num).add(arr[i]);
    }
    
    // 对每个桶进行排序,任意排序方法都可以
    for(int i = 0; i < bucketArr.size(); i++){
        Collections.sort(bucketArr.get(i));
    }
    
    // 将桶中的元素赋值到原序列
	int index = 0;
	for(int i = 0; i < bucketArr.size(); i++){
		for(int j = 0; j < bucketArr.get(i).size(); j++){
			arr[index++] = bucketArr.get(i).get(j);
		}
	}  
}

四、复杂度分析
1. 时间复杂度:O(N + C)
对于待排序序列大小为 N,共分为 M 个桶,主要步骤有:

N 次循环,将每个元素装入对应的桶中
M 次循环,对每个桶中的数据进行排序(平均每个桶有 N/M 个元素)
一般使用较为快速的排序算法,时间复杂度为 O ( N l o g N ) O(NlogN)O(NlogN),实际的桶排序过程是以链表形式插入的。

整个桶排序的时间复杂度为:

O ( N ) + O ( M ∗ ( N / M ∗ l o g ( N / M ) ) ) = O ( N ∗ ( l o g ( N / M ) + 1 ) ) O(N)+O(M*(N/M*log(N/M)))=O(N*(log(N/M)+1))O(N)+O(M∗(N/M∗log(N/M)))=O(N∗(log(N/M)+1))

当 N = M 时,复杂度为 O ( N ) O(N)O(N)

2. 额外空间复杂度:O(N + M)
 

桶排序适用场景

桶排序适合外部排序,外部排序就是数据在内存之外,比如磁盘上,数据量比较大,无法 一次性读入内存。

举个例子,假如老板给你一份 10 GB 大小的文件,是订单的交易明细数 据,要求你按订单金额从大到小排序,而你的内存内有 4GB,实际可用内存只有 2 GB, 那么此时就是桶排序发挥作用的时候了。

1. 将文件逐行读入内存(几乎每个编程语言都可 以),扫描并记录最小值,最大值,假如最小值为 1 元,最大值为 10 万元,且都为整数, 不是整数也没关系,可以先乘以 100 换成整数,排序后再除以 100 还原。
 

10.基数排序

基数排序(Radix Sort)是将待排序序列的每个元素统一为同样位数长度的元素,位数较短的通过补0达到长度一致,然后从最低位或从最高位开始,依次进行稳定的计数排序,最终形成有序的序列

基数排序主要是针对整数的排序,由于整数也可以表示字符串或和特定格式的浮点数,因此能用整数表达的其他数据类型也能用基数排序

基数排序既可以从高位优先进行排序(简称MSD),也可以从低位优先进行排序(简称LSD)

public static int[] sort(int[] array) {
        if (array.length < 2) return array;
        //根据最大值算出位数
        int max = array[0];
        for (int temp : array) {
            if (temp > max) {
                max = temp;
            }
        }
        //算出位数digit 比如最大值为120 maxDigit就为2
        int maxDigit = 0;
        while (max != 0) {
            max /= 10;
            maxDigit++;
        }
        //创建桶并初始化
        ArrayList> bucket = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            bucket.add(new ArrayList<>());
        }
        //按照从右往左的顺序,依次将每一位都当做一次关键字,然后按照该关键字对数组排序,每一轮排序都基于上轮排序后的结果
        int mold = 10;//取模运算
        int div = 1;//获取对应位数的值
        for (int i = 0; i < maxDigit; i++, mold *= 10, div *= 10) {
            for (int j = 0; j < array.length; j++) {
                //获取个位/十位/百位......
                int num = (array[j] % mold) / div;
                //把数据放入到对应的桶里
                bucket.get(num).add(array[j]);
            }
            //把桶中的数据重新写回去,并把桶的元素清空,开始第二轮排序
            int index = 0;
            for (int k = 0; k < bucket.size(); k++) {
                //桶中对应的数据
                ArrayList list = bucket.get(k);
                for (int m = 0; m < list.size(); m++) {
                    array[index++] = list.get(m);
                }
                //清除桶
                bucket.get(k).clear();
            }
        }
        return array;
    }

时间复杂度:O(k*n),最外层时间复杂度是o(k),k是序列最大数的位数,在里层的出桶时间复杂度是数组的长度O(n),再拼接成数组的时间复杂度平均是数组的长度O(n),总的来说,基数排序的时间复杂度是o(k*(n+n)),即O(k*n)

空间复杂度:O(K+N)

适用于位数不多,每一位范围不大的序列。

附总结:


 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/992072.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号