【数据结构】用Java实现七大排序算法

这篇具有很好参考价值的文章主要介绍了【数据结构】用Java实现七大排序算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

🌷1. 排序的概念及引用

1.1 排序的概念

1.2 衡量指标

1.2 十个排序算法

 1.3 十个排序性能对比

🌷2. 冒泡排序

2.1 算法描述

2.2 动图

⭐️代码优化

🌷3. 选择排序

3.1 算法描述

3.2 动图

 3.3 代码

🌷4. 插入排序

4.1 算法描述

4.2 动图

 4.3 代码

🌷5 希尔排序

5.1 描述

5.2 动图

 5.3 代码

🌷6. 归并排序

6.1 描述

6.2 动图

 6.3 代码

⭐️优化代码(优化部分)

🌷7.堆排序

7.1 算法描述

7.2 动图

 7.3 代码

🌷8. 快速排序(挖坑法)

8.1 描述

8.2 动图

8.3 代码

(1)递归版

(2)递归版优化

(3)非递归版

🌷9. 快速排序(二路快排)

代码 

🌷10. 三路快排

🌷11. 测试排序后的数组是否正确排序?

🌷12. 全部代码

(1)生成数组(渐进有序,乱序)

(2)排序代码

(3)测试结果 


🌷1. 排序的概念及引用

1.1 排序的概念

排序:所谓排序,就是对一组任意数据元素(或记录)经过指定关键字排序操作后,就可以把它们变成一组按照关键字排序的有序序列。通常排序的目的是快速查找。

1.2 衡量指标

对于一个排序算法来说,一般从以下3个方面来衡量算法的优劣:

  • 排序算法的执行效率(时间复杂度)
  • 排序算法的内存消耗(空间复杂度)
  • 排序算法的稳定性
⭐️ 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

【数据结构】用Java实现七大排序算法

1.2 十个排序算法

内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
【数据结构】用Java实现七大排序算法

 1.3 十个排序性能对比

【数据结构】用Java实现七大排序算法

【注意】以下排序算法全部为升序排序

为了避免代码太多的重复,这里提供一个swap方法(交换两个值),后面多个排序方法会用到:

    //    交换两个值
    private static void swap(int[] arr,int j, int i) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

🌷2. 冒泡排序

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。 

2.1 算法描述

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。

2.2 动图

【数据结构】用Java实现七大排序算法2.3 代码

public static void bubbleSort(int[] arr){
        if(arr.length < 2){
            return;
        }
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
//                j + 1保证索引不越界,因此,j < arr.length - i - 1
                if(arr[j] > arr[j + 1]){
                    swap(arr,j,j + 1);
                }
            }          
        }
    }

⭐️代码优化

当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。

    //    冒泡排序
    public static void bubbleSort(int[] arr){
        if(arr.length < 2){
            return;
        }
        for (int i = 0; i < arr.length - 1; i++) {
            boolean isSwap = false;
            for (int j = 0; j < arr.length - i - 1; j++) {
//                j + 1保证索引不越界,因此,j < arr.length - i - 1
                if(arr[j] > arr[j + 1]){
                    isSwap = true;
                    swap(arr,j,j + 1);
                }
            }
            if(!isSwap){
                break;
            }
        }
    }

🌷3. 选择排序

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

3.1 算法描述

  • n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
  • 初始状态:无序区为R[1..n],有序区为空;
  • 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  • n-1趟结束,数组有序化了。

3.2 动图

【数据结构】用Java实现七大排序算法

 3.3 代码

public static void selectionSort(int[] arr){
        // 起始状态 : 有序区间[0..i)
        // 无序区间[i....n)
        for (int i = 0; i < arr.length - 1; i++) {
            // min指向的当前无序区间的最小值,min是下标
            int min = i;
            for (int j = i + 1; j < arr.length; j++) {
                if(arr[j] < arr[min]){
                    min = j;
                }
            }
            // 此时min一定指向无序区间最小值下标,换到无序区间的最开始位置
            swap(arr,i,min);
        }
    }

🌷4. 插入排序

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

4.1 算法描述

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 将新元素插入到该位置后;
  • 重复步骤2~5。

4.2 动图

【数据结构】用Java实现七大排序算法

 4.3 代码

    public static void insertionSort(int[] arr){
        // 无序区间[i...n)
        // 有序区间[0...i)
        // i指向当前无序区间的第一个元素
        for (int i = 1; i < arr.length; i++) {
            for (int j = i; j - 1 >= 0 && arr[j] < arr[j - 1]; j--) {
//                简化写法
                swap(arr,j,j - 1);
//                j - 1 要保证不为负数 因此j > 0,而不是 j >= 0
//                if(arr[j] < arr[j - 1]){
//                    swap(arr,j,j - 1);
//                }
            }
        }
    }

🌷5 希尔排序

1959年Shell发明,第一个突破O(n^2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。

希尔(Shell)排序又称缩小增量排序,是对直接插入排序的优化。

5.1 描述

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

  • 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  • 按增量序列个数k,对序列进行k 趟排序;
  • 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

5.2 动图

【数据结构】用Java实现七大排序算法

 5.3 代码

 //    希尔排序(插入排序的优化),不稳定
    public static void shellSort(int[] arr) {
        int gap = arr.length >> 1;
        while (gap > 1) {
            // 先按照gap分组,组内使用插入排序
            insertionSortByGap(arr,gap);
            gap = gap >> 1;
        }
        // 当gap == 1时,整个数组接近于有序,此时来一个插入排序
        insertionSortByGap(arr,1);
    }

    // 按照gap分组,组内的插入排序
    private static void insertionSortByGap(int[] arr, int gap) {
        for (int i = gap; i < arr.length; i++) {
            for (int j = i; j - gap >= 0 && arr[j] < arr[j - gap] ; j -= gap) {
                swap(arr,j,j - gap);
            }
        }
    }

🌷6. 归并排序

归并排序的核心思想还是蛮简单的。如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。归并排序使用的就是分治思想。分治,顾名思义,就是分而治之,将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也就解决了。

6.1 描述

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

6.2 动图

【数据结构】用Java实现七大排序算法

 6.3 代码

public static void mergeSort(int[] arr){
        mergeSortInternal(arr,0,arr.length - 1);
    }

    // 在arr[l...r]进行归并排序
    private static void mergeSortInternal(int[] arr, int l, int r) {
        // base case
//        if(l > r){
//            return;
//        }
        // mid = (l + r) / 2
        int mid = l + ((r - l) >> 1);
        // 先将原数组一分为二,在子数组上先进行归并排序
        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid + 1,r);
        // 此时两个子数组已经有序,将这两个子数组合并为原数组
            merge(arr,l,mid,r);
    }

    private static void merge(int[] arr, int l, int mid, int r) {
//        本身就是插入排序
        // 创建一个大小为r - l + 1的与原数组长度一样的临时数组aux
        int[] aux = new int[r - l + 1];
//        arraycopy:原数组,原数组开始copy的第一个下标,目标数组,目标数组copy的第一个下标,copy的长度
        System.arraycopy(arr,l,aux,0,r - l + 1);
        // 两个子数组的开始索引
        int i = l,j = mid + 1;
        // k表示当前原数组合并到哪个位置
//        时间复杂度O(n)
        for (int k = l; k <= r; k++) {
            if (i > mid) {
                // 子数组1全部拷贝完毕,将子数组2的所有内容写回arr
                arr[k] = aux[j - l];
                j ++;
            }else if (j > r) {
                // 子数组2全部拷贝完毕,将子数组1的剩余内容写回arr
                arr[k] = aux[i - l];
                i ++;
            }else if (aux[i - l] <= aux[j - l]) {
                // 稳定性 <=
                arr[k] = aux[i - l];
                i ++;
            }else {
                arr[k] = aux[j - l];
                j ++;
            }
        }
    }

⭐️优化代码(优化部分)

public static void mergeSort(int[] arr){
        mergeSortInternal(arr,0,arr.length - 1);
    }

    // 在arr[l...r]进行归并排序
    private static void mergeSortInternal(int[] arr, int l, int r) {
        // base case
//        if(l > r){
//            return;
//        }
        // 优化2.小数组(64个元素以内)直接使用插入排序
        if (r - l <= 64) {
            insertionSort(arr,l,r);
            return;
        }
        // mid = (l + r) / 2
        int mid = l + ((r - l) >> 1);
        // 先将原数组一分为二,在子数组上先进行归并排序
        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid + 1,r);
        // 此时两个子数组已经有序,将这两个子数组合并为原数组
//        merge(arr,l,mid,r);
        if (arr[mid] > arr[mid + 1]) {
            // 优化1.只有子数组1和子数组2存在元素的乱序才需要合并
            merge(arr,l,mid,r);
        }
    }
    // 在数组arr[l..r]上进行插入排序
    private static void insertionSort(int[] arr, int l, int r) {
        for (int i = l + 1; i <= r; i++) {
            for (int j = i; j > l && arr[j] < arr[j - 1]; j--) {
                swap(arr,j,j - 1);
            }
        }
    }

🌷7.堆排序

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

7.1 算法描述

  • 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  • 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  • 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

7.2 动图

【数据结构】用Java实现七大排序算法

 7.3 代码

//    原地堆排序   升序最大堆
//    1.堆化,不断将堆顶和无序数组最后一个交换
//    2.再siftDown
//    3.重复12
    public static void heapSort(int[] arr){
//        堆化
        for (int i = (arr.length - 2)/2; i >= 0; i--) {
            siftDown(arr,i, arr.length);
        }
        // 不断的将当前无序数组的最大值(堆顶) 和 最后一个元素进行交换~
        for (int i = arr.length - 1; i >= 0 ; i--) {
            // 将堆顶(最大值) 和 i交换
            swap(arr,0,i);
            // 进行元素的下沉操作
            siftDown(arr,0,i);
        }
    }
//    元素下沉
    private static void siftDown(int[] arr, int i, int length) {
        int j =  2 * i + 1;
        while (j < length){
            if(j + 1 < length && arr[j] < arr[j + 1]){
                j = j + 1;
            }
            if(arr[j] <= arr[i]){
//                下沉结束
                break;
            }else {
                swap(arr,i,j);
                i = j;
                j =  2 * i + 1;//更新孩子节点
            }
        }
    }

🌷8. 快速排序(挖坑法)

快排的思想是这样的:如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。

我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的。

8.1 描述

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

8.2 动图

【数据结构】用Java实现七大排序算法

8.3 代码

(1)递归版

//    挖坑法快速排序 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
//    时间复杂度:O(N*logN)
    public static void quickSortHole(int[] arr){
        quickSortHoleInternal(arr,0, arr.length - 1);
    }

//     默认挖坑法快速排序
    private static void quickSortHoleInternal(int[] arr,int left,int right){
        // base case
        if(left > right){
            return;
        }

        int p = partitionByHole(arr,left,right);
        // 继续在两个子区间上进行快速排序
        quickSortHoleInternal(arr,left,p - 1);
        quickSortHoleInternal(arr,p + 1,right);
    }

//    时间复杂度n
    private static int partitionByHole(int[] arr, int left, int right) {
//        渐进有序(极端情况完全有序)的数组,快速排序性能会退化为O(n*n)
//        原因:每次取最左边的值,假设这个值是最小的,那么j要一直减到left(二叉树会变成单支树)
//        解决:①三数取中法(课本中的方法),②随机数法
        int randomIndex = random.nextInt(left,right);
        swap(arr,left,randomIndex);
        int pivot = arr[left];
        int i = left,j = right;
        while (i < j) {
            // 先让j从后向前扫描碰到第一个 < pivot的元素终止
            while (i < j && arr[j] >= pivot) {
                j --;
            }
            arr[i] = arr[j];
            // 再让i从前向后扫描碰到第一个 > pivot的元素终止,
            while (i < j && arr[i] <= pivot) {
                i ++;
            }
            arr[j] = arr[i];
        }
        // 回填分区点
        arr[j] = pivot;
        return j;
    }

(2)递归版优化

    private static void qiuckSortHoleInternal(int[] arr,int left,int right){
        // base case
//        if(left > right){
//            return;
//        }
        // 优化1.小数组使用插入排序
        if (right - left <= 64) {
            insertionSort(arr,left,right);
            return;
        }
        int p = partitionByHole(arr,left,right);
        // 继续在两个子区间上进行快速排序
        qiuckSortHoleInternal(arr,left,p - 1);
        qiuckSortHoleInternal(arr,p + 1,right);
    }

(3)非递归版

//    非递归挖坑法快速排序
    public static void quickSortNonRecursion(int[] arr){
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(arr.length - 1);
        stack.push(0);
        while (!stack.isEmpty()){
            int l = stack.pop();
            int r = stack.pop();
            if (l >= r) {
                // 当前这个子数组已经处理完毕
                continue;
            }
            int p = partitionByHole(arr,l,r);
            // 先将右半区间压入栈中
            stack.push(r);
            stack.push(p + 1);
            // 继续处理左半区间
            stack.push(p - 1);
            stack.push(l);
        }
    }

🌷9. 快速排序(二路快排)

【数据结构】用Java实现七大排序算法

【数据结构】用Java实现七大排序算法

代码 

    public static void quickSort(int[] arr){
        quickSortInternal(arr,0,arr.length - 1);
    }

    private static void quickSortInternal(int[] arr, int left, int right) {
        if (right - left <= 64) {
            insertionSort(arr,left,right);
            return;
        }
        int p = partition(arr,left,right);
        quickSortInternal(arr,left,p - 1);
        quickSortInternal(arr,p + 1,right);
    }

    private static int partition(int[] arr, int left, int right) {
        int randomIndex = random.nextInt(left,right);
        swap(arr,left,randomIndex);
        int v = arr[left];
        // arr[l + 1..j] < v
        int j = left;
        // arr[j + 1..i) >= v
        for (int i = left + 1; i < right; i++) {
            if(arr[i] < v){
                swap(arr,i,j + 1);
                j ++;
            }
        }
        swap(arr,left,j);
        return j;
    }

🌷10. 三路快排

【数据结构】用Java实现七大排序算法

【数据结构】用Java实现七大排序算法

    public static void quickSort3(int[] arr) {
        quickSortInternal3(arr,0,arr.length - 1);
    }
// 三路快排

    private static void quickSortInternal3(int[] arr, int l, int r) {
        if (r - l <= 64) {
            insertionSort(arr,l,r);
            return;
        }
        int randomIndex = (int) (Math.random() * (r - l + 1)) + l;
        swap(arr,l,randomIndex);
        int v = arr[l];
        // arr[l + 1...lt] < v
        int lt = l;
        // arr[gt...r] > v
        int gt = r + 1;
        // arr[lt + 1..i) == v,i指向当前要处理的元素
        int i = l + 1;
        // 终止条件i和gt重合
        while (i < gt) {
            if (arr[i] < v) {
                swap(arr,lt + 1,i);
                lt ++;
                i ++;
            }else if (arr[i] > v) {
                // 此时不需要i++,因为gt-1这个未处理的元素换到i位置
                swap(arr,gt - 1,i);
                gt --;
            }else {
                // 此处arr[i] == v
                i ++;
            }
        }
        swap(arr,l,lt);
        // 交换后arr[l..lt - 1] < v ; arr[gt..r] >v
        quickSortInternal3(arr,l,lt - 1);
        quickSortInternal3(arr,gt,r);
    }

🌷11. 测试排序后的数组是否正确排序?

//    测试是否排序正确
    public static boolean isSorted(int[] data){
        for (int i = 0; i < data.length - 1; i++) {
            if(data[i] > data[i + 1]){
//                反例
                return false;
            }
        }
        return true;
    }

🌷12. 全部代码

(1)生成数组(渐进有序,乱序)

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ArrayUtil {
    private static ThreadLocalRandom random = ThreadLocalRandom.current();

    // 生成一个大小为n的近乎有序的数组
    // 数组的有序与否取决于元素的交互次数
    public static int[] generateSortedArray(int n,int swapTimes){
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = i + 1;
        }
        for (int i = 0; i < swapTimes; i++) {
            int x = random.nextInt(0,n);
            int y = random.nextInt(0,n);
            swap(arr,x,y);
        }
        return arr;
    }

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

    // 生成一个大小为n的随机数数组
    // 随机数的取值范围为[l..r)
    public static int[] generateRandomArray(int n,int l,int r){
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = random.nextInt(l,r);
        }
        return arr;
    }

    // 深拷贝数组
    public static int[] arrCopy(int[] arr) {
        return Arrays.copyOf(arr,arr.length);
    }

}

(2)排序代码

import util.ArrayUtil;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.concurrent.ThreadLocalRandom;


public class SevenSort {
    private static ThreadLocalRandom random = ThreadLocalRandom.current();
    private static int[] arr6;

    public static void main(String[] args) throws Exception{

        int n = 10_0000;
//        渐进有序的数组
//        int[] arr = ArrayUtil.generateSortedArray(n,10);

//        无序数组
        int[] arr = ArrayUtil.generateRandomArray(n,0,Integer.MAX_VALUE);

        int[] arr1 = ArrayUtil.arrCopy(arr);
        int[] arr2 = ArrayUtil.arrCopy(arr);
        int[] arr3 = ArrayUtil.arrCopy(arr);
        int[] arr4 = ArrayUtil.arrCopy(arr);
        int[] arr5 = ArrayUtil.arrCopy(arr);
        int[] arr6 = ArrayUtil.arrCopy(arr);
        int[] arr7 = ArrayUtil.arrCopy(arr);
        int[] arr8 = ArrayUtil.arrCopy(arr);


        // --------------------------------
        long start = System.nanoTime();
        qiuckSort(arr8);
        long end = System.nanoTime();
        if (isSorted(arr8)) {
            System.out.println("快速排序共耗时 : " + (end - start) / 1000000.0 + "ms");
        }
        //--------------------------------
        start = System.nanoTime();
        qiuckSortNonRecursion(arr7);
        end = System.nanoTime();
        if (isSorted(arr7)) {
            System.out.println("快速排序(挖坑法,非递归法)共耗时 : " + (end - start) / 1000000.0 + "ms");
        }
        //---------------------------------
        start = System.nanoTime();
        qiuckSortHole(arr6);
        end = System.nanoTime();
        if (isSorted(arr6)) {
            System.out.println("快速排序(挖坑法,递归法)共耗时 : " + (end - start) / 1000000.0 + "ms");
        }

        //----------------------------------
        start = System.nanoTime();
        mergeSort(arr5);
        end = System.nanoTime();
        if (isSorted(arr5)) {
            System.out.println("归并排序共耗时 : " + (end - start) / 1000000.0 + "ms");
        }

        //---------------------------------
        start = System.nanoTime();
        shellSort(arr4);
        end = System.nanoTime();
        if (isSorted(arr4)) {
            System.out.println("希尔排序共耗时 : " + (end - start) / 1000000.0 + "ms");
        }

        // ------------------------------
        start = System.nanoTime();
        insertionSort(arr3);
        end = System.nanoTime();
        if (isSorted(arr3)) {
            System.out.println("插入排序共耗时 : " + (end - start) / 1000000.0 + "ms");
        }

        // -------------------------------
        start = System.nanoTime();
        selectionSort(arr2);
        end = System.nanoTime();
        if (isSorted(arr2)) {
            System.out.println("选择排序共耗时 : " + (end - start) / 1000000.0 + "ms");
        }

        // ------------------------------
        start = System.nanoTime();
        bubbleSort(arr1);
        end = System.nanoTime();
        if (isSorted(arr1)) {
            System.out.println("冒泡排序共耗时 : " + (end - start) / 1000000.0 + "ms");
        }

        // ------------------------------
        start = System.nanoTime();
        heapSort(arr);
        end = System.nanoTime();
        if (isSorted(arr)) {
            System.out.println("堆排序共耗时 : " + (end - start) / 1000000.0 + "ms");
        }

    }

//    测试是否排序正确
    public static boolean isSorted(int[] data){
        for (int i = 0; i < data.length - 1; i++) {
            if(data[i] > data[i + 1]){
//                反例
                return false;
            }
        }
        return true;
    }

    //    交换两个值
    private static void swap(int[] arr,int j, int i) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void quickSort3(int[] arr) {
        quickSortInternal3(arr,0,arr.length - 1);
    }
// 三路快排

    private static void quickSortInternal3(int[] arr, int l, int r) {
        if (r - l <= 64) {
            insertionSort(arr,l,r);
            return;
        }
        int randomIndex = (int) (Math.random() * (r - l + 1)) + l;
        swap(arr,l,randomIndex);
        int v = arr[l];
        // arr[l + 1...lt] < v
        int lt = l;
        // arr[gt...r] > v
        int gt = r + 1;
        // arr[lt + 1..i) == v,i指向当前要处理的元素
        int i = l + 1;
        // 终止条件i和gt重合
        while (i < gt) {
            if (arr[i] < v) {
                swap(arr,lt + 1,i);
                lt ++;
                i ++;
            }else if (arr[i] > v) {
                // 此时不需要i++,因为gt-1这个未处理的元素换到i位置
                swap(arr,gt - 1,i);
                gt --;
            }else {
                // 此处arr[i] == v
                i ++;
            }
        }
        swap(arr,l,lt);
        // 交换后arr[l..lt - 1] < v ; arr[gt..r] >v
        quickSortInternal3(arr,l,lt - 1);
        quickSortInternal3(arr,gt,r);
    }

//    算法4的分区快排(二路快排)
    public static void quickSort(int[] arr){
        quickSortInternal(arr,0,arr.length - 1);
    }

    private static void quickSortInternal(int[] arr, int left, int right) {
        if (right - left <= 64) {
            insertionSort(arr,left,right);
            return;
        }
        int p = partition(arr,left,right);
        quickSortInternal(arr,left,p - 1);
        quickSortInternal(arr,p + 1,right);
    }

    private static int partition(int[] arr, int left, int right) {
        int randomIndex = random.nextInt(left,right);
        swap(arr,left,randomIndex);
        int v = arr[left];
        // arr[l + 1..j] < v
        int j = left;
        // arr[j + 1..i) >= v
        for (int i = left + 1; i < right; i++) {
            if(arr[i] < v){
                swap(arr,i,j + 1);
                j ++;
            }
        }
        swap(arr,left,j);
        return j;
    }


    //    挖坑法快速排序 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
//    时间复杂度:O(N*logN)
    public static void quickSortHole(int[] arr){
        quickSortHoleInternal(arr,0, arr.length - 1);
    }

//     默认挖坑法快速排序
    private static void quickSortHoleInternal(int[] arr,int left,int right){
        // base case
//        if(left > right){
//            return;
//        }
        // 优化1.小数组使用插入排序
        if (right - left <= 64) {
            insertionSort(arr,left,right);
            return;
        }
        int p = partitionByHole(arr,left,right);
        // 继续在两个子区间上进行快速排序
        quickSortHoleInternal(arr,left,p - 1);
        quickSortHoleInternal(arr,p + 1,right);
    }
//    非递归挖坑法快速排序
    public static void quickSortNonRecursion(int[] arr){
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(arr.length - 1);
        stack.push(0);
        while (!stack.isEmpty()){
            int l = stack.pop();
            int r = stack.pop();
            if (l >= r) {
                // 当前这个子数组已经处理完毕
                continue;
            }
            int p = partitionByHole(arr,l,r);
            // 先将右半区间压入栈中
            stack.push(r);
            stack.push(p + 1);
            // 继续处理左半区间
            stack.push(p - 1);
            stack.push(l);
        }
    }

//    时间复杂度n
    private static int partitionByHole(int[] arr, int left, int right) {
//        渐进有序(极端情况完全有序)的数组,快速排序性能会退化为O(n*n)
//        原因:每次取最左边的值,假设这个值是最小的,那么j要一直减到left(二叉树会变成单支树)
//        解决:①三数取中法(课本中的方法),②随机数法
        int randomIndex = random.nextInt(left,right);
        swap(arr,left,randomIndex);
        int pivot = arr[left];
        int i = left,j = right;
        while (i < j) {
            // 先让j从后向前扫描碰到第一个 < pivot的元素终止
            while (i < j && arr[j] >= pivot) {
                j --;
            }
            arr[i] = arr[j];
            // 再让i从前向后扫描碰到第一个 > pivot的元素终止,
            while (i < j && arr[i] <= pivot) {
                i ++;
            }
            arr[j] = arr[i];
        }
        // 回填分区点
        arr[j] = pivot;
        return j;
    }


//    归并排序是一种稳定的排序方法。
//    和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,
//    因为始终都是O(n * logn)的时间复杂度。代价是需要额外的内存空间。
//    归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
//    空间复杂度:O(N)
//    稳定性:稳定
    public static void mergeSort(int[] arr){
        mergeSortInternal(arr,0,arr.length - 1);
    }

    // 在arr[l...r]进行归并排序
    private static void mergeSortInternal(int[] arr, int l, int r) {
        // base case
//        if(l > r){
//            return;
//        }
        // 优化2.小数组(64个元素以内)直接使用插入排序
        if (r - l <= 64) {
            insertionSort(arr,l,r);
            return;
        }
        // mid = (l + r) / 2
        int mid = l + ((r - l) >> 1);
        // 先将原数组一分为二,在子数组上先进行归并排序
        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid + 1,r);
        // 此时两个子数组已经有序,将这两个子数组合并为原数组
//        merge(arr,l,mid,r);
        if (arr[mid] > arr[mid + 1]) {
            // 优化1.只有子数组1和子数组2存在元素的乱序才需要合并
            merge(arr,l,mid,r);
        }
    }
    // 在数组arr[l..r]上进行插入排序
    private static void insertionSort(int[] arr, int l, int r) {
        for (int i = l + 1; i <= r; i++) {
            for (int j = i; j > l && arr[j] < arr[j - 1]; j--) {
                swap(arr,j,j - 1);
            }
        }
    }

    private static void merge(int[] arr, int l, int mid, int r) {
//        本身就是插入排序
        // 创建一个大小为r - l + 1的与原数组长度一样的临时数组aux
        int[] aux = new int[r - l + 1];
//        arraycopy:原数组,原数组开始copy的第一个下标,目标数组,目标数组copy的第一个下标,copy的长度
        System.arraycopy(arr,l,aux,0,r - l + 1);
        // 两个子数组的开始索引
        int i = l,j = mid + 1;
        // k表示当前原数组合并到哪个位置
//        时间复杂度O(n)
        for (int k = l; k <= r; k++) {
            if (i > mid) {
                // 子数组1全部拷贝完毕,将子数组2的所有内容写回arr
                arr[k] = aux[j - l];
                j ++;
            }else if (j > r) {
                // 子数组2全部拷贝完毕,将子数组1的剩余内容写回arr
                arr[k] = aux[i - l];
                i ++;
            }else if (aux[i - l] <= aux[j - l]) {
                // 稳定性 <=
                arr[k] = aux[i - l];
                i ++;
            }else {
                arr[k] = aux[j - l];
                j ++;
            }
        }
    }


    //    希尔排序(插入排序的优化),不稳定
    public static void shellSort(int[] arr) {
        int gap = arr.length >> 1;
        while (gap > 1) {
            // 先按照gap分组,组内使用插入排序
            insertionSortByGap(arr,gap);
            gap = gap >> 1;
        }
        // 当gap == 1时,整个数组接近于有序,此时来一个插入排序
        insertionSortByGap(arr,1);
    }

    // 按照gap分组,组内的插入排序
    private static void insertionSortByGap(int[] arr, int gap) {
        for (int i = gap; i < arr.length; i++) {
            for (int j = i; j - gap >= 0 && arr[j] < arr[j - gap] ; j -= gap) {
                swap(arr,j,j - gap);
            }
        }
    }


    //    插入排序,稳定!近乎有序的集合上性能非常好,经常作为其他高阶排序的优化手段
    public static void insertionSort(int[] arr){
        // 无序区间[i...n)
        // 有序区间[0...i)
        // i指向当前无序区间的第一个元素
        for (int i = 1; i < arr.length; i++) {
            for (int j = i; j - 1 >= 0 && arr[j] < arr[j - 1]; j--) {
//                简化写法
                swap(arr,j,j - 1);
//                j - 1 要保证不为负数 因此j > 0,而不是 j >= 0
//                if(arr[j] < arr[j - 1]){
//                    swap(arr,j,j - 1);
//                }
            }
        }
    }


    //    选择排序:对数据不敏感,无论是否有序,都会进行比较
    public static void selectionSort(int[] arr){
        // 起始状态 : 有序区间[0..i)
        // 无序区间[i....n)
        for (int i = 0; i < arr.length - 1; i++) {
            // min指向的当前无序区间的最小值,min是下标
            int min = i;
            for (int j = i + 1; j < arr.length; j++) {
                if(arr[j] < arr[min]){
                    min = j;
                }
            }
            // 此时min一定指向无序区间最小值下标,换到无序区间的最开始位置
            swap(arr,i,min);
        }
    }


    //    冒泡排序
    public static void bubbleSort(int[] arr){
        if(arr.length < 2){
            return;
        }
        for (int i = 0; i < arr.length - 1; i++) {
            boolean isSwap = false;
            for (int j = 0; j < arr.length - i - 1; j++) {
//                j + 1保证索引不越界,因此,j < arr.length - i - 1
                if(arr[j] > arr[j + 1]){
                    isSwap = true;
                    swap(arr,j,j + 1);
                }
            }
            if(!isSwap){
                break;
            }
        }
    }


//    原地堆排序   升序最大堆
//    1.堆化,不断将堆顶和无序数组最后一个交换
//    2.再siftDown
//    3.重复12
    public static void heapSort(int[] arr){
//        堆化
        for (int i = (arr.length - 2)/2; i >= 0; i--) {
            siftDown(arr,i, arr.length);
        }
        // 不断的将当前无序数组的最大值(堆顶) 和 最后一个元素进行交换~
        for (int i = arr.length - 1; i >= 0 ; i--) {
            // 将堆顶(最大值) 和 i交换
            swap(arr,0,i);
            // 进行元素的下沉操作
            siftDown(arr,0,i);
        }
    }
//    元素下沉
    private static void siftDown(int[] arr, int i, int length) {
        int j =  2 * i + 1;
        while (j < length){
            if(j + 1 < length && arr[j] < arr[j + 1]){
                j = j + 1;
            }
            if(arr[j] <= arr[i]){
//                下沉结束
                break;
            }else {
                swap(arr,i,j);
                i = j;
                j =  2 * i + 1;//更新孩子节点
            }
        }
    }

}

(3)测试结果 

【数据结构】用Java实现七大排序算法

 

参考文章:https://www.cnblogs.com/onepixel/p/7674659.html文章来源地址https://www.toymoban.com/news/detail-422937.html

到了这里,关于【数据结构】用Java实现七大排序算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】 七大排序详解(贰)——冒泡排序、快速排序、归并排序

    ==冒泡排序(Bubble Sort)==也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会

    2024年02月09日
    浏览(39)
  • 数据结构——七大排序[源码+动图+性能测试]

    本章代码gitee仓库:排序 我们日常打扑克牌,摸牌,让后将牌按顺序插入好,这其实就是插入排序的过程,打小插入排序的思想就植入我们的脑海 第一张牌不用管,直接拿在手里,之后的牌按照大小再一个一个插入即可 直接插入排序特性: 越接近有序,效率越高(不用那么多

    2024年02月09日
    浏览(32)
  • 【数据结构】 七大排序详解(壹)——直接插入排序、希尔排序、选择排序、堆排序

    排序 :所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 稳定性 :假定在待排序的记录序列中,存在多个具有相同的的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而

    2024年02月09日
    浏览(42)
  • 【数据结构常见七大排序(二)】—选择排序篇【直接选择排序】And【堆排序】

    目录 前言 1.直接选择排序 1.1基本思想 1.2直接选择排序实现过程 1.3动图助解 1.4直接选择排序源码 2.堆排序 2.1堆排序的概念 2.2堆排序源码  选择排序有两种常见的【直接选择排序】、【堆排序】 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起

    2024年02月16日
    浏览(39)
  • 【数据结构常见七大排序(一)】—插入排序篇【直接插入排序】And【希尔排序】

    目录 1.排序的概念及其运用 1.1排序的概念 1.2排序运用 1.3常见的七大排序 2.直接插入排序 2.1基本思想 2.2直接插入排序 2.3动图助解 2.4直接插入排序源码 2.5直接插入排序的特性总结 3.希尔排序( 缩小增量排序 ) 3.1希尔排序概念及思想 3.2希尔排序图解 3.3希尔排序源码 3.4希尔排序

    2024年02月13日
    浏览(33)
  • 【数据结构常见七大排序(三)上】—交换排序篇【冒泡排序】And【快速排序】

    目录 前言 1.冒泡排序 1.1冒泡排序动图 1.2冒泡排序源代码 1.3冒泡排序的特性总结 2.快速排序👑 2.1hoare版本实现思想 排序前 排序中 排序后 2.2hoare版本快排源代码 2.3分析先走 情况1🥇 情况2🥈 交换类排序两个常见的排序算法【冒泡排序】、【快速排序】 交换排序基本思想:

    2024年02月16日
    浏览(36)
  • 数据结构:手撕图解七大排序(含动图演示)

    插入排序分为直接插入排序和希尔排序,其中希尔排序是很值得学习的算法 希尔排序的基础是直接插入排序,先学习直接插入排序 直接插入排序类似于打扑克牌前的整牌的过程,假设我们现在有2 4 5 3四张牌,那么应该怎么整牌? 方法很简单,把3插到2和4中间,这样就完成了

    2024年02月15日
    浏览(28)
  • 算法 数据结构 递归插入排序 java插入排序 递归求解插入排序算法 如何用递归写插入排序 插入排序动图 插入排序优化 数据结构(十)

    1. 插入排序(insertion-sort):                                           是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入     算法稳定性:                  

    2024年02月09日
    浏览(38)
  • 【数据结构与算法】JavaScript实现排序算法

    一、大O表示法 大O表示法: 在计算机中采用 粗略的度量 来描述计算机算法的 效率 ,这种方法被称为 “大O”表示法 在 数据项个数 发生改变时, 算法的效率 也会跟着改变。所以说算法A比算法B快两倍,这样的比较是 没有意义 的。 因此我们通常使用 算法的速度 随着 数据

    2024年02月02日
    浏览(34)
  • 【数据结构与算法】归并排序详解:归并排序算法,归并排序非递归实现

    归并排序是一种经典的排序算法,它使用了分治法的思想。下面是归并排序的算法思想: 递归地将数组划分成较小的子数组,直到每个子数组的长度为1或者0。 将相邻的子数组合并,形成更大的已排序的数组,直到最终得到一个完全排序的数组。 归并排序的过程可以分为三

    2024年01月22日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包