java 排序算法

目录

排序算法

冒泡排序

 选择排序

 插入排序

 希尔排序

 归并排序

 快速排序

 堆排序

 计数排序

 桶排序

 基数排序


排序算法

排序算法可分为内部排序算法和外部排序算法。

内部排序算法:数据记录在内存中排序。

外部排序算法:因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要方位内存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用以下两张图概括:

稳定即在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相 对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的。反之,不稳定。

冒泡排序

比较两个相邻的元素,如果升序的话,前面的比后面的大,就交换,这样一轮下来,就会找到这组数据中最大的元素,然后抛开这个元素继续重复上述步骤,知道排完为止。

算法步骤:

升序情况下,比较相邻的元素,如果前面元素比后面元素大,即交换两个元素的位置。

对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

对所有元素重复以上步骤,除去每次排序后的最后一个元素。

持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

动图效果演示:

 代码实现:

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

    }

    /**
     * 冒泡排序
     * @param arr 需要排序的数组
     */
    public static void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            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;
                }
            }
        }
    }

 选择排序

每次都遍历所有未排序的序列,找到最小值,放在已排序序列末尾。即第一次未排序序列为所有,找到最小值放在第一位;第二次未排序序列为除去第一个元素,找到最小值追加在第一次找到的最小值之后;第三次未排序序列为除去前两个元素,找到最小值追加在第二次找到的最小值之后,以此类推,直到最后一个元素。

 算法步骤:

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

重复第二步,直到所有元素均排序完毕。

 动图效果演示:

 代码实现:

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

    }

    /**
     * 选择排序
     *
     * @param arr 需要排序的数组
     */
    public static void sort(int[] arr) {

        for (int i = 0; i < arr.length - 1; i++) {
            //每次将i位置看做最小值得索引
            int min = i;
            for (int j = i + 1; j < arr.length; j++) {
                //如果存在比第一个元素还小的值,将索引重新赋值
                if (arr[j] < arr[min]) {
                    min = j;
                }
            }
            // 将找到的最小值和i位置所在的值进行交换
            if (arr[min] != arr[i]) {
                int temp = arr[i];
                arr[i] = arr[min];
                arr[min] = temp;
            }
        }
    }

 插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

 算法步骤:

将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

如:{3,1,2},从第二个元素开始,第一个元素为有序序列,第一次排序即比较3和1的大小,3比1大交换元素,变为{1,3,2},有序序列变为1、3;第二次排序比较2和3的大小,以及2和1的大小,如果当前元素比前一个元素大,就将他追加在有序序列,退出循环;如果比前一个元素小就交换位置继续比较,直到比较完前面所有元素。

 动图效果演示:

 代码实现:

    /**
     * 插入排序
     *
     * @param arr 需要排序的数组
     */
    public static void sort(int[] arr) {

        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = i + 1; j > 0; j--) {
                if (arr[j] < arr[j - 1]) {
                    int temp = arr[j - 1];
                    arr[j - 1] = arr[j];
                    arr[j] = temp;
                } else
                    break;

            }
        }
    }

 希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本思想是:其实是一种分组插入方法。希尔排序是把数据按下标的一定增量分组,增量相当于分的组数,而数组长度/增量≈每组中的元素。对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的数据越来越多,当增量减至1时,所有数据恰被分成一组,算法便终止。

 算法步骤:

如原始数组

{2, 0, 4, 9, 3, 6, 8, 7, 1, 5}

第一次排序增量一般为数组长度/2,此处即为5,数组即分为第一组从下标为0,和下标为0+5的元素{2,6},第二组下标1,和下标1+5的元素{0,8},以此类推得{4,7}、{9、1}、{3、5}五组,然后对五组进行插入排序得到{2、6}、{0、8}、{4、7}、{1、9}、{3、5},然后在根据对应的下标得到数据为{2,0,4,1,3,6,8,7,9,5}。

第二次排序

原始数组变为{2,0,4,1,3,6,8,7,9,5}

增量变为第一次排序增量/2,即为2,数组即分为第一组由下标为0和为0+2,0+2+2,0+2+2+2,0+2+2+2+2五个元素组成{2、4、3、8、9};第二组由下标1,1+2,1+2+2,1+2+2+2,1+2+2+2+2五个元素组成{0、1、6、7、5};对两组进行插入排序得到

{2、3、4、8、9}和{0、1、5、6、7},然后在根据对应的下标得到数据为{然后在根据对应的下标得到数据为{2,0,3,1,4,5,8,6,9,7}。

第三次排序

原始数组变为{2,0,3,1,4,5,8,6,9,7}

增量变为第二次排序增量/2,即为1,即对{2,0,3,1,4,5,8,6,9,7}进行插入排序得到最终结果{0,1,2,3,4,5,6,7,8,9}。

 动图效果演示:

 代码实现:

/**
     * 希尔排序
     *
     * @param arr 需要排序的数组
     */
    public static void sort(int[] arr) {

        int length = arr.length;
        int temp;
        for (int step = length / 2; step >= 1; step /= 2) {
            for (int i = step; i < length; i++) {
                temp = arr[i];
                int j = i - step;
                while (j >= 0 && arr[j] > temp) {
                    arr[j + step] = arr[j];
                    j -= step;
                }
                arr[j + step] = temp;
            }
        }
    }

 归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

 算法步骤:

将待排序的元素序列分为两个长度相等的子序列,对每个子序列进行排序,然后将他们合并成一个序列,合并两个子序列的过程称为二路归并。

说明:数组{49,38,65,97,76,13,27},第一次排序即长度相等的子序列{49,38}{65,97} {76,13}{27},对每个子序列排序得到{38,49}{65,97}{13,76}{27}即{38,49,65,97,13,76,27};第二次排序长度相等子序列{38,49,65,97}{13,76,27},排序得到{38,49,65,97}{13,27,76}即{38,49,65,97,13,27,76},第三次排序相等子序列{38,49,65,97,13,27,76},排序等到最终结果。

 动图效果演示:

 代码实现:

 /**
     * 归并排序
     *
     * @param sourceArray 需要排序的数组
     */
    public static int[] sort(int[] sourceArray) {

        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        if (arr.length < 2) {
            return arr;
        }
        int middle = (int) Math.floor(arr.length / 2);

        int[] left = Arrays.copyOfRange(arr, 0, middle);
        int[] right = Arrays.copyOfRange(arr, middle, arr.length);

        return merge(sort(left), sort(right));
    }
    protected static int[] merge(int[] left, int[] right) {
        int[] result = new int[left.length + right.length];
        int i = 0;
        while (left.length > 0 && right.length > 0) {
            if (left[0] <= right[0]) {
                result[i++] = left[0];
                left = Arrays.copyOfRange(left, 1, left.length);
            } else {
                result[i++] = right[0];
                right = Arrays.copyOfRange(right, 1, right.length);
            }
        }

        while (left.length > 0) {
            result[i++] = left[0];
            left = Arrays.copyOfRange(left, 1, left.length);
        }

        while (right.length > 0) {
            result[i++] = right[0];
            right = Arrays.copyOfRange(right, 1, right.length);
        }

        return result;
    }

 快速排序

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。

 算法步骤:

  1. 从数列中挑出一个元素,称为 "基准"(pivot);

  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

 算法的演示

 代码实现:


    /**
     * 快速排序
     *
     * @param sourceArray 需要排序的数组
     */
    public static int[] sort(int[] sourceArray) {

        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        return quickSort(arr, 0, arr.length - 1);

    }
    private static int[] quickSort(int[] arr, int left, int right) {
        if (left < right) {
            int partitionIndex = partition(arr, left, right);
            quickSort(arr, left, partitionIndex - 1);
            quickSort(arr, partitionIndex + 1, right);
        }
        return arr;
    }
    private static int partition(int[] arr, int left, int right) {
        // 设定基准值(pivot)
        int pivot = left;
        int index = pivot + 1;
        for (int i = index; i <= right; i++) {
            if (arr[i] < arr[pivot]) {
                swap(arr, i, index);
                index++;
            }
        }
        swap(arr, pivot, index - 1);
        return index - 1;
    }

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

 堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 Ο(nlogn)。

 算法步骤:

  • 创建一个堆
  • 把堆顶元素(最大值)和堆尾元素互换
  • 把堆的尺寸缩小1,并调用heapify(A, 0)从新的堆顶元素开始进行堆调整
  • 重复步骤2,直到堆的尺寸为1

 动图效果演示:

 代码实现:

/**
     * 堆排序
     *
     * @param sourceArray 需要排序的数组
     */
    public static int[] sort(int[] sourceArray) {

        // 对 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 static void buildMaxHeap(int[] arr, int len) {
        for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
            heapify(arr, i, len);
        }
    }
    private static 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 static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

 计数排序

找到待排序列中最大最小的元素,然后以此确定临时空间大小,在临时空间中,以待排序列组中元素大小为下标,该元素出现的次数为该下标对应的元素,根据临时空间的统计结构,重新对元素进行回收。

 算法步骤:

  • (1)找出待排序的数组中最大和最小的元素
  • (2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
  • (3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  • (4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

 动图效果演示:

 代码实现:


    /**
     * 计数排序
     *
     * @param sourceArray 需要排序的数组
     */
    public  int[] sort(int[] sourceArray) {

        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        int maxValue = getMaxValue(arr);

        return countingSort(arr, maxValue);
    }

    private int[] countingSort(int[] arr, int maxValue) {
        int bucketLen = maxValue + 1;
        int[] bucket = new int[bucketLen];

        for (int value : arr) {
            bucket[value]++;
        }

        int sortedIndex = 0;
        for (int j = 0; j < bucketLen; j++) {
            while (bucket[j] > 0) {
                arr[sortedIndex++] = j;
                bucket[j]--;
            }
        }
        return arr;
    }

    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }

 桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:

  1. 在额外空间充足的情况下,尽量增大桶的数量
  2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

 动图效果演示:

元素分布在桶中:

然后,元素在每个桶中排序: 

 代码实现:

/**
     * 桶排序
     *
     * @param sourceArray 需要排序的数组
     */
    public  int[] sort(int[] sourceArray) throws Exception {

        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        return bucketSort(arr, 5);
    }

    private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
        if (arr.length == 0) {
            return arr;
        }

        int minValue = arr[0];
        int maxValue = arr[0];
        for (int value : arr) {
            if (value < minValue) {
                minValue = value;
            } else if (value > maxValue) {
                maxValue = value;
            }
        }

        int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
        int[][] buckets = new int[bucketCount][0];

        // 利用映射函数将数据分配到各个桶中
        for (int i = 0; i < arr.length; i++) {
            int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
            buckets[index] = arrAppend(buckets[index], arr[i]);
        }

        int arrIndex = 0;
        for (int[] bucket : buckets) {
            if (bucket.length <= 0) {
                continue;
            }
            // 对每个桶进行排序,这里使用了插入排序
            bucket = insertSort(bucket);
            for (int value : bucket) {
                arr[arrIndex++] = value;
            }
        }

        return arr;
    }

    /**
     * 自动扩容,并保存数据
     *
     * @param arr
     * @param value
     */
    private int[] arrAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }
 private int[] insertSort(int[] sourceArray) {

        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
        for (int i = 1; i < arr.length; i++) {

            // 记录要插入的数据
            int tmp = arr[i];

            // 从已经排序的序列最右边的开始比较,找到比其小的数
            int j = i;
            while (j > 0 && tmp < arr[j - 1]) {
                arr[j] = arr[j - 1];
                j--;
            }

            // 存在比其小的数,插入
            if (j != i) {
                arr[j] = tmp;
            }

        }
        return arr;
    }

 基数排序

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

基数排序和基数排序、桶排序比较:

基数排序有两种方法:

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

 动图效果演示:

 代码实现:

/**
     * 基数排序
     *
     * @param sourceArray 需要排序的数组
     */
    public  int[] sort(int[] sourceArray) throws Exception {

        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        int maxDigit = getMaxDigit(arr);
        return radixSort(arr, maxDigit);
    }

    /**
     * 获取最高位数
     */
    private int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);
        return getNumLenght(maxValue);
    }

    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }

    protected int getNumLenght(long num) {
        if (num == 0) {
            return 1;
        }
        int lenght = 0;
        for (long temp = num; temp != 0; temp /= 10) {
            lenght++;
        }
        return lenght;
    }

    private int[] radixSort(int[] arr, int maxDigit) {
        int mod = 10;
        int dev = 1;

        for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
            // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
            int[][] counter = new int[mod * 2][0];

            for (int j = 0; j < arr.length; j++) {
                int bucket = ((arr[j] % mod) / dev) + mod;
                counter[bucket] = arrayAppend(counter[bucket], arr[j]);
            }

            int pos = 0;
            for (int[] bucket : counter) {
                for (int value : bucket) {
                    arr[pos++] = value;
                }
            }
        }

        return arr;
    }

    /**
     * 自动扩容,并保存数据
     *
     * @param arr
     * @param value
     */
    private int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值