Java-排序算法

这里抽象出了一个排序基类:

public abstract class BaseSort {

        public abstract int[] sort(int[] array);

        public void swap(int[] arr, int a, int b) {
            int temp;
            temp = arr[a];
            arr[a] = arr[b];
            arr[b] = temp;
        }
    }

下面我会用7个实现类分别实现7种排序算法。

1.插入排序

数据结构 数组

最差时间复杂度 O(n^2)

最优时间复杂度 O(n)

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

最差空间复杂度 总共O(n) ,需要辅助空间O(1)

1.1.动图展示

1.2.算法描述

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

1. 从第一个元素开始,该元素可以认为已经被排序

2. 取出下一个元素,在已经排序的元素序列中从后向前扫描

3. 如果该元素(已排序)大于新元素,将该元素移到下一位置

4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

5. 将新元素插入到该位置后

6. 重复步骤2~5

如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找插入排序。

1.3.示例代码

public static class InsertSort extends BaseSort {

        @Override
        public int[] sort(int[] array) {
            for (int i = 1; i < array.length; i++) {
                int temp = array[i];
                int j;
                for (j = i - 1; j >= 0 && temp < array[j]; j--) {
                    array[j + 1] = array[j];
                }
                array[j + 1] = temp;
            }
            return array;
        }
    }

2.选择排序

数据结构 数组

最差时间复杂度 О(n2)

最优时间复杂度 О(n2)

平均时间复杂度 О(n2)

最差空间复杂度 О(n) total, O(1) auxiliary

2.1.动图展示

2.2.算法描述

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

2.3.示例代码

public class SelectSort extends BaseSort {

        @Override
        public int[] sort(int[] array) {
            for (int i = 0; i < array.length; i++) {
                int miniPost = i;
                for (int m = i + 1; m < array.length; m++) {
                    if (array[m] < array[miniPost]) {
                        miniPost = m;
                    }
                }

                if (array[i] > array[miniPost]) {
                    int temp;
                    temp = array[i];
                    array[i] = array[miniPost];
                    array[miniPost] = temp;
                }
            }
            return array;
        }
    }

3.冒泡排序

数据结构 数组

最差时间复杂度 O(n^2)

最优时间复杂度 O(n)

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

最差空间复杂度 总共O(n),需要辅助空间O(1)

3.1.动图展示

3.2.算法描述

冒泡排序算法的运作如下:

1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

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

3. 针对所有的元素重复以上的步骤,除了最后一个。

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

3.3.示例代码

public class BubbleSort extends BaseSort {

        @Override
        public int[] sort(int[] array) {
            int temp;
            // 第一层循环:表明比较的次数, 比如 length 个元素,比较次数为 length-1 次(肯定不需和自己比)
            for (int i = 0; i < array.length - 1; i++) {
                for (int j = array.length - 1; j > i; j--) {
                    if (array[j] < array[j - 1]) {
                        swap(array, j, j - 1);
                    }
                }
            }
            return array;
        }
    }

4.快速排序

数据结构 不定

最差时间复杂度 О(n^2)

最优时间复杂度 О(n log n)

平均时间复杂度 О(n log n)

最差空间复杂度 根据实现的方式不同而不同

4.1动图展示:

4.2.算法描述:

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。

步骤为:

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

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

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

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

4.3.示例代码:

public class QuickSort extends BaseSort {

        @Override
        public int[] sort(int[] array) {
            return sortSub(array, 0, array.length - 1);
        }

        //排序子数组,采用分治思想,不断递归迭代,当每个子数组都排好了,源数组也就排好了
        private int[] sortSub(int[] arr, int low, int heigh) {
            if (low < heigh) {
                int division = partition(arr, low, heigh);
                sortSub(arr, low, division - 1);
                sortSub(arr, division + 1, heigh);
            }
            return arr;
        }

        // 分水岭,基位,左边的都比这个位置小,右边的都大
        private int partition(int[] arr, int low, int heigh) {
            int base = arr[low]; //用子表的第一个记录做枢轴(分水岭)记录
            while (low < heigh) { //从表的两端交替向中间扫描
                while (low < heigh && arr[heigh] >= base) {
                    heigh--;
                }
                // base 赋值给 当前 heigh 位,base 挪到(互换)到了这里,heigh位右边的都比base大
                swap(arr, heigh, low);
                while (low < heigh && arr[low] <= base) {
                    low++;
                }
                // 遇到左边比base值大的了,换位置
                swap(arr, heigh, low);
            }
            // now low = heigh;
            return low;
        }

    }

5.合并排序

数据结构 数组

最差时间复杂度 O(n log n)

最优时间复杂度 O(n)

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

最差空间复杂度 O(n)

5.1.动图展示

5.2.算法描述

归并操作(merge),也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操作。归并排序算法依赖归并操作。

迭代法:

1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置

3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

4. 重复步骤3直到某一指针到达序列尾

5. 将另一序列剩下的所有元素直接复制到合并序列尾

递归法:

原理如下(假设序列共有n个元素):

1. 将序列每相邻两个数字进行归并操作,形成floor(n/2)个序列,排序后每个序列包含两个元素

2. 将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素

3. 重复步骤2,直到所有元素排序完毕

5.3.示例代码

public class MergeSort extends BaseSort {

        @Override
        public int[] sort(int[] array) {
            return sortSub(array, 0, array.length - 1);
        }

        private int[] sortSub(int[] nums, int low, int high) {
            int mid = (low + high) / 2;
            if (low < high) {
                // 左边
                sortSub(nums, low, mid);
                // 右边
                sortSub(nums, mid + 1, high);
                // 左右归并
                merge(nums, low, mid, high);
            }
            return nums;
        }

        private void merge(int[] nums, int low, int mid, int high) {
            int[] temp = new int[high - low + 1];
            int i = low;// 左指针
            int j = mid + 1;// 右指针
            int k = 0;
            // 把较小的数先移到新数组中
            while (i <= mid && j <= high) {
                if (nums[i] < nums[j]) {
                    temp[k++] = nums[i++];
                } else {
                    temp[k++] = nums[j++];
                }
            }
            // 把左边剩余的数移入数组
            while (i <= mid) {
                temp[k++] = nums[i++];
            }
            // 把右边边剩余的数移入数组
            while (j <= high) {
                temp[k++] = nums[j++];
            }
            // 把新数组中的数覆盖nums数组
            for (int k2 = 0; k2 < temp.length; k2++) {
                nums[k2 + low] = temp[k2];
            }
        }
    }

6.希尔排序

数据结构 数组

最差时间复杂度 根据步长序列的不同而不同。已知最好的:O(n log^2 n)

最优时间复杂度 O(n)

平均时间复杂度 根据步长序列的不同而不同。

最差空间复杂度 O(n)

6.1.动图展示

以23, 10, 4, 1的步长序列进行希尔排序

6.2.算法描述

希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。

假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n2)的排序(冒泡排序或插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。

一个更好理解的希尔排序实现:将数组列在一个表中并对列排序(用插入排序)。重复这过程,不过每次用更长的列来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身仅仅对原数组进行排序(通过增加索引的步长,例如是用i += step_size而不是i++)。

6.3.示例代码

public class ShellSort extends BaseSort {

        @Override
        public int[] sort(int[] array) {
            // 取增量
            int step = array.length / 2;
            while (step >= 1) {
                for (int i = step; i < array.length; i++) {
                    int temp = array[i];
                    int j = 0;
                    // 跟插入排序的区别就在这里
                    for (j = i - step; j >= 0 && temp < array[j]; j -= step) {
                        array[j + step] = array[j];
                    }
                    array[j + step] = temp;
                }
                step /= 2;
            }
            return array;
        }
    }

7.堆排序

7.1.动图展示

堆排序算法的演示。首先,将元素进行重排,以匹配堆的条件。图中排序过程之前简单的绘出了堆树的结构。

7.2.算法描述

堆节点的访问:

通常堆是通过一维数组来实现的。在数组起始位置为0的情形中:

1. 父节点i的左子节点在位置(2*i+1);

2. 父节点i的右子节点在位置(2*i+2);

3. 子节点i的父节点在位置floor((i-1)/2);

堆的操作:

在堆的数据结构中,堆中的最大值总是位于根节点。堆中定义以下几种操作:

1. 最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点

2. 创建最大堆(Build_Max_Heap):将堆所有数据重新排序

3. 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算

7.3.示例代码

public class HeapSort extends BaseSort {

        @Override
        public int[] sort(int[] array) {
            buildHeap(array);// 构建堆
            int n = array.length;
            int i = 0;
            for (i = n - 1; i >= 1; i--) {
                swap(array, 0, i);
                heapify(array, 0, i);
            }

            return array;
        }

        private void buildHeap(int[] array) {
            int n = array.length;// 数组中元素的个数
            for (int i = n / 2 - 1; i >= 0; i--)
                heapify(array, i, n);
        }

        private void heapify(int[] A, int idx, int max) {
            int left = 2 * idx + 1;// 左孩子的下标(如果存在的话)
            int right = 2 * idx + 2;// 左孩子的下标(如果存在的话)
            int largest = 0;// 寻找3个节点中最大值节点的下标
            if (left < max && A[left] > A[idx])
                largest = left;
            else
                largest = idx;
            if (right < max && A[right] > A[largest])
                largest = right;
            if (largest != idx) {
                swap(A, largest, idx);
                heapify(A, largest, max);
            }
        }

        // 建堆函数,认为【s,m】中只有 s
        // 对应的关键字未满足大顶堆定义,通过调整使【s,m】成为大顶堆
        public void heapAdjust(int[] array, int s, int m) {
            // 用0下标元素作为暂存单元
            array[0] = array[s];
            // 沿孩子较大的结点向下筛选
            for (int j = 2 * s; j <= m; j *= 2) {
                // 保证j为较大孩子结点的下标,j < m 保证 j+1 <= m ,不越界
                if (j < m && array[j] < array[j + 1]) {
                    j++;
                }
                if (!(array[0] < array[j])) {
                    break;
                }
                // 若S位较小,应将较大孩子上移
                array[s] = array[j];
                // 较大孩子的值变成S位的较小值,可能引起顶堆的不平衡,故对其所在的堆进行筛选
                s = j;
            }
            // 若S位较大,则值不变;否则,S位向下移动至2*s、4*s、。。。
            array[s] = array[0];
        }
    }


参考目录:

1. 维基百科

2. java常用的7大排序算法汇总

时间: 2024-07-29 14:29:28

Java-排序算法的相关文章

Java排序算法(一)

Java排序算法(一) 一.排序的基本概念和分类 1.1排序的定义 在<大话数据结构>中,排序定义为,假设含有n个记录的序列为{r1,r2,...,rn},其相应的关键字{k1,k2,...,kn},需确定1,2...n的一种排列p1,p2...pn,是其相应的关键字满足Kp1<=Kp2<=...<=Kpn(非递减或非递增)关键,即使得序列称为一个按关键字有序的序列{rp1,rp2...rp3},这样的操作称为排序. 1.2排序的稳定性 假设ki=kj(1<=i<

java排序算法(六):直接插入排序

java排序算法(六):直接插入排序 直接插入排序的基本操作就是将待的数据元素按其关键字的大小插入到前面的有序序列中 直接插入排序时间效率并不高,如果在最坏的情况下,所有元素的比较次数的总和为(0+1..n-1)= o(n^2).其他情况下也要考虑移动元素的次数.故时间复杂度是o(n^2) 直接插入空间效率很好,只需要一个缓存数据单元,也就是说空间复杂度是o(1) 直接插入排序是稳定的 直接插入排序在数据以有一定顺序的情况下,效率较好.但如果数据无规则,则需要移动大量的数据.其效率就和冒泡排序和

Java排序算法-Java入门|Java基础课程

Java 排序算法 1. 课程目标 排序是任何语言都会使用到的功能之一,然成果排序的算法有很多,对空间的要求及其时间效率也不尽相同. 本文章以Java语言示例,通过对空间要求.时间效率要求,来对比各种排序算法的使用场景 2.适用对象 Java语言初学者 Java算法爱好者 3.相关概念 3.1 排序概念 排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作. 3.2 排序算法 排序算法,就是如何使得记录按照要求排列的方法. 排序算法在很多领域得到相当地重视,尤其是在

Java排序算法 - 堆排序的代码

把内容过程中比较重要的一些内容片段做个备份,如下的资料是关于Java排序算法 - 堆排序的内容. import java.util.Arrays; public class HeapSort { int a[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,56,17,18,23,34,15,35,25,53,51}; public HeapSort(){ heapSort(a); } public void heapSort(int[

Java排序算法(四):希尔排序

[基本思想] 将原本有大量记录数的记录进行分组,分割成若干个子序列,此时每个子序列待排序的记录个数就比较少了,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时,再对全体记录进行一次直接插入排序. 所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,像{2, 1, 3, 6, 4, 7, 5, 8, 9}这样可以称为基本有序了. [java实现] public class ShellSort { public static void main(String

java排序算法

1.插入排序 插入排序是最简单的排序算法之一,由N-1趟排序组成,必须得保证位置0到p处的元素都是已排序状态. 具体实现的代码如下: package k; import java.util.Scanner; public class test2 { public static void main(String[] args) { Scanner in=new Scanner(System.in); //获取输入的字符串,并且把中间的空格全部去掉 String [] str = in.nextLi

java排序算法之希尔排序

希尔排序是冲破二次时间屏障的第一批算法之一. 它是通过比较相距一定间隔的元素来工作,各趟比较所用的距离随着算法的进行而减小,直到最后一趟(比较相邻元素)为止.因此希尔排序也叫缩减增量排序. 希尔排序使用一个序列h1,h2,h3...hk来排序. 具体的意思是 第一趟排序比较的是相隔为hk的元素,也就是比较a[i]与a[i+hk],保证a[i]<=a[i+hk]. 第二次比较的是相隔为hk-1的元素,也就是比较a[i]与a[i+hk-1],保证a[i]<=a[i+hk-1]. 直到最后比较的是相

(转)java 排序算法

排序算法汇总(java实现,附源代码) 整理系统的时候发现了原来写的各种算法的总结,看了一下,大吃一惊,那时候的我还如此用心,具体的算法,有的已经模糊甚至忘记了,看的时候就把内容整理出来,顺便在熟悉一下,以后需要的时候就可以直接过来摘抄了.下面是总结的几个常用的排序算法: 插入排序 快速排序 冒泡排序 堆排序 计数排序 桶排序 可能大家对插入排序,快速排序,冒泡排序比较常用,在满足需求的时候也简单一些,下面逐一说一下每个算法的实现方式,不保证是写的最有效率的,但是能保证的是,各种算法的中心思想是

Java排序算法总结

稳定排序:* 泡沫排序(bubble sort) — O(n²)* 插入排序 (insertion sort)— O(n²)* 桶排序 (bucket sort)— O(n); 需要 O(k) 额外空间* 计数排序 (counting sort) — O(n+k); 需要 O(n+k) 额外空间* 合并排序 (merge sort)— O(n log n); 需要 O(n) 额外空间* 二叉排序树排序 (Binary tree sort) — O(n log n)期望时间; O(n²)最坏时间;

Java排序算法(四):Shell排序

[基本的想法] 将原本有大量记录数的记录进行分组.切割成若干个子序列,此时每一个子序列待排序的记录个数就比較少了,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时.再对全体记录进行一次直接插入排序. 所谓的基本有序,就是小的keyword基本在前面,大的基本在后面,不大不小的基本在中间.像{2, 1, 3, 6, 4, 7, 5, 8, 9}这样能够称为基本有序了. [java实现] public class ShellSort { public static void main(