十大排序算法总结

排序算法不管是在面试中,还是在平时的开发中,都经常用到,本文对十种排序算法进行总结,比较它们的优劣!!!

1. 时间、空间复杂度对比

中文名称英文名称平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
选择排序Selectionn2n2n21不稳
冒泡排序Bubblen2n2n1
插入排序Insertionn2n2n1
堆排序Heapnlog2nnlog2nnlog2n1不稳
希尔排序Shelln1.3n2n1不稳
归并排序Mergenlog2nnlog2nnlog2nn
快速排序Quicknlog2nn2nlog2nlog2n不稳
桶排序Bucketn+kn2nn+k
计数排序Countingn+kn+kn+kn+k
基数排序Radixn*kn*kn*kn+k

2.十大排序总结

2.1 选择排序

选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n - 1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。

选择排序有两步,代码实现步骤比较简单。

  • 比较,找出这一趟最小值处的索引index
  • 交换swap
/**
 * 选择排序
 */
public class SelectionSort {
    public static void main(String[] args) {
        int[] arr = {5, 3, 1, 4, 2};//对该数组排序
        //从数组的第一个元素开始,从前往后,总共需要比较N-1趟
        for (int i = 0; i < arr.length-1; i++) {
            //记录每一趟最小值处的索引
            int minPos = i;
            //这层循环进行比较、交换
            for(int j=i+1; j<arr.length; j++) {
                //比较、更新最小值索引
                minPos = arr[j] < arr[minPos] ? j : minPos;
            }
            //交换
            swap(arr,i,minPos);
        }
    }
    static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2.2 冒泡排序

冒泡排序就是利用循环,将当前最大的元素移到靠后的位置

下面数组arr = {9, 3, 1, 4, 6, 8, 7, 5, 2},通过比较将最大的元素9移到最后
arr1 = {3, 1, 4, 6, 8, 7, 5, 2, 9};
依次类推,加一层外循环,得到排序好的数组

/**
 * 冒泡排序
 */
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {9, 3, 1, 4, 6, 8, 7, 5, 2};
        //第一层循环表示趟数,有arr.length个数,那么仅需要arr.length-1趟
        //仅剩两个数的时候,只需要1趟即可
        for (int i = 0; i < arr.length-1; i++) {
            //比较当前元素与下一个元素的大小
            //若当前元素大于下一个元素,执行交换方法
            for (int j = 0; j < arr.length - i - 1; j++) {
                if (arr[j] > arr[j+1]) swap(arr, j, j+1);
            }
        }
    }
    static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2.3 插入排序

插排的核心思想就是原始数组分成两个子数组,左数组Left是有序的,右数组Right是无序的。

初始:把数组的第一个元素当成Left,其余元素当成Right,Right数组的第一个元素开始向Left当中插入。插入的原则是,将该元素插入到第一个比该元素大的位置的左侧!!!

如何找到合适的位置,循环比较(类似冒泡排序的思想),一直比较、交换找到合适的位置。

/**
 * 插入排序
 */
public class InsertionSort {
    public static void main(String[] args) {
        int[] arr = {6, 2, 5, 4, 8};
        //从Right的第一个元素开始{2, 5, 4, 8}
        for (int i = 1; i < arr.length; i++) {
            //从Left数组的最后一个元素开始,往前进行比较
            //j--表示Left数组从后往前
            for (int j = i; j > 0; j--) {
                //若插入元素小于Left数组的arr[j-1],则交换
                if(arr[j] < arr[j-1]) {
                    swap(arr,j,j-1);
                }
            }
        }
    }
    static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

选择排序、冒泡排序、插入排序三种排序算法比较简单,容易实现,使用场景如下:

  • 冒泡:基本使用的比较少,太慢
  • 选择:基本使用的比较少,不稳
  • 插入:样本少基本有序的时候效率比较高

2.4 堆排序

public class HeapSort {
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
        int len = arr.length;
        buildMaxHeap(arr, len);

        for (int i = len - 1; i > 0; i--) {
            swap(arr, 0, i);
            len--;
            heapify(arr, 0, len);
        }
        return arr;
    }
    private void buildMaxHeap(int[] arr, int len) {
        for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
            heapify(arr, i, len);
        }
    }
    private void heapify(int[] arr, int i, int len) {
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int largest = i;
        if (left < len && arr[left] > arr[largest]) {
            largest = left;
        }
        if (right < len && arr[right] > arr[largest]) {
            largest = right;
        }
        if (largest != i) {
            swap(arr, i, largest);
            heapify(arr, largest, len);
        }
    }
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2.5 希尔排序

选定一个间隔,按照这个间隔进行分组,分组之后按照插入排序排序

/**
 * 希尔排序
 */
public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {9, 6, 11, 3, 5, 12, 8, 7, 10, 15, 14, 4, 1, 13, 2};
        int h = 1;
        while (h <= arr.length / 3) {
            h = h*3 + 1;
        }
        for (int gap = h; gap > 0; gap=(gap-1)/3) {
            for (int i = gap; i <arr.length; i++) {
                for (int j = i; j > gap-1; j-=gap) {
                    if(arr[j] < arr[j-gap]){
                        swap(arr, j, j-gap);
                    }
                }
            }
        }
    static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2.6 归并排序

将原始数组分解为多个子序列,然后对每个子序列进行排序,最后将排好序的子序列合并起来。

  • merge()方法,将排好序的两个子数组进行排序
  • sort()方法,通过递归的思想,分到不可再分之后归并
/**
 * 归并排序
 */
public class MergeSort {
    public static void main(String[] args) {
        int[] arr = {9, 6, 11, 3, 5, 12, 8, 7, 10, 15, 14, 4, 1, 13, 2};
        sort(arr, 0, arr.length-1);
    }
    //递归的思想,大数组分为左、右两个子数组
    static void sort(int[] arr, int left, int right) {
        if (left == right) return;
        //分两成半
        int mid = left + ((right - left) >> 1);
        //左边排序
        sort(arr, left, mid);
        //右边排序
        sort(arr, mid + 1, right);
        merge(arr, left, mid+1, right);
    }
    //merge()方法,默认两个子数组是排好序的,进行合并
    static void merge(int[] arr, int leftPtr, int rightPtr, int rightBound) {
        int i = leftPtr;
        int j = rightPtr;
        int k = 0;
        int[] temp = new int[rightBound-leftPtr+1];
        int mid = rightPtr - 1;
        while (i <= mid && j <= rightBound) {
            temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }
        while (i<=mid) temp[k++] = arr[i++];
        while (j<=rightBound) temp[k++] = arr[j++];

        for (int l = 0; l < temp.length; l++) {
            arr[leftPtr+l] = temp[l];
        }
    }
}

2.7 快速排序

以数组右边界的元素为轴key,作为基准值进行快排。

  • 首先,从左往右找出第一个比key大的元素,索引为left
  • 然后,从右往左找出第一个比可以小的元素,索引为right
  • 交换left与right处的元素
  • 重复这个操作,直到left>right为止
  • 交换基准值与left索引所在的元素,那么现在key左侧都比key小,key右侧都比key大
  • 递归,进行小数组的快排
/**
 * 快速排序
 */
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {7, 3, 2, 8, 1, 9, 5, 4, 6};
        sort(arr, 0, arr.length-1);

        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i] + " ");
        }
    }
    static void sort(int[] arr, int leftBound, int rightBound) {
        if(leftBound >= rightBound) return;
        int mid = partition(arr, leftBound, rightBound);
        sort(arr, leftBound, mid-1);
        sort(arr, mid+1, rightBound);
    }
    static int partition(int[] arr, int leftBound, int rightBound) {
        int key = arr[rightBound];
        int left = leftBound;
        int right = rightBound-1;
        while(left <= right) {
            while(left <= right&&arr[left] <= key) left++;
            while(left <= right&&arr[right] > key) right--;

            if(left < right) swap(arr, left, right);
        }
        swap(arr, left, rightBound);

        return left;
    }
    static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2.8 桶排序

桶排序用的比较少,将原数组按照范围分为几个有限的桶,对每个桶进行桶内排序,排序好之后在合并。

2.9 计数排序

使用的比较少,常用于数量大且范围小的数据排序
比如,0~9之间的数字有几百个(元素大量重复),那么就可以用计数排序。

  • 有一个计数数组,数组的长度是原数组元素的种类数,遍历原数组,统计每种元素0~9出现的次数
  • 有一个结果数组result,遍历计数数组,将元素赋值到result中(while循环条件是关键)
/**
 * 计数排序
 */
public class CountSort {
    public static void main(String[] args) {
        int[] arr = {2, 4, 2, 3, 7, 1, 1, 0, 0, 5, 6, 9, 8, 5, 7, 4, 0, 9};
        int[] result = sort(arr);
    }
    static int[] sort(int[] arr) {
        int[] result = new int[arr.length];
        int[] count = new int[10];
        for (int i = 0; i < arr.length; i++) {
            count[arr[i]]++;
        }
        for (int i=0, j=0; i < count.length; i++) {
            //当前计数位不为0时
            while(count[i]-- > 0) {
                result[j++] = i;
            }
        }
        return result;
    }
}

还可以进一步优化,实现计数排序的稳定性,用的是累加数组。

static int[] sort(int[] arr) {
        int[] result = new int[arr.length];
        int[] count = new int[10];
        for (int i = 0; i < arr.length; i++) {
            count[arr[i]]++;
        }
        //累加数组
        for (int i=1; i < count.length; i++) {
            count[i] = count[i] + count[i-1];
        }
		//倒序遍历原数组,根据累加数组的索引位置,得到result
        for (int i = arr.length-1; i >= 0; i--) {
            result[--count[arr[i]]] = arr[i];
        }
        return result;
    }

2.10 基数排序

桶排序的一种,按照个位、十位、百位进行排序,用到了计数排序

/**
 * 基数排序
 */
public class RadixSort {
    public static void main(String[] args) {

        int[] arr = {421, 240, 115, 532, 305, 430, 124};
        int[] result = sort(arr);
        System.out.println(Arrays.toString(result));
    }
    static int[] sort(int[] arr) {
        int[] result = new int[arr.length];
        int[] count = new int[10];
        //判断数组元素的最高位数,这里用的最高3位
        //比较3趟,分别是个、十、百
        for (int i = 0; i < 3; i++) {
            int division = (int)Math.pow(10, i);
            System.out.println(division);
            for (int j = 0; j < arr.length; j++) {
                int num = arr[j]/division % 10;
                System.out.println(num + " ");
                count[num]++;
            }
            for (int m = 1; m < count.length; m++) {
                count[m] = count[m] + count[m-1];
            }
            for (int n = arr.length-1; n >= 0; n--) {
                int num = arr[n]/division % 10;
                result[--count[num]] = arr[n];
            }
            //将result复制到arr中,重复这个操作
            System.arraycopy(result, 0, arr, 0, arr.length);
            //数组元素没有高位,那么补0
            Arrays.fill(count, 0);
        }
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值