十大排序算法及其扩展

一、冒泡排序

基本介绍:

冒泡排序是比较基础的排序算法之一,其思想是相邻的元素两两比较,较大的数下沉,较小的数冒起来,这样一趟比较下来,最大(小)值就会排列在一端。整个过程如同气泡冒起,因此被称作冒泡排序。

步骤:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 每趟从第一对相邻元素开始,对每一对相邻元素作同样的工作,直到最后一对。
  3. 针对所有的元素重复以上的步骤,除了已排序过的元素(每趟排序后的最后一个元素),直到没有任何一对数字需要比较。

在这里插入图片描述
优化:

因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。

public class BubbleSort {
    public static void main(String[] args) {
//        int[] arr = {3, 9, -1, 10, -2};
//        System.out.println("排序前");
//        System.out.println(Arrays.toString(arr));

        // 测试冒泡排序速度O(n^2),给80000个数据,测试
        int[] arr = new int[80000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 8000000); // 生成一个[0,8000000)数
        }
        long start = System.currentTimeMillis();
        // 测试冒泡排序
        bubbleSort(arr);
        long end = System.currentTimeMillis();
        System.out.println("排序的时间是=" + (end - start));
    }
    
    public static void bubbleSort(int[] arr) {
        // 冒泡排序的时间复杂度O(n^2)
        int temp = 0; // 临时变量
        boolean flag = false; // 标识变量,表示是否进行过交换
        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]) {
                    flag = true; // 交换过
                    temp = arr[j]; 
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            if (!flag) { // 说明在一趟排序中,一次交换都没有发生
                break;
            } else {
                flag = false; // 重置flag,进行下次判断
            }
        }
    }
}

二、选择排序

基本介绍:

选择排序是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,继续放在起始位置直到未排序元素个数为0。

步骤:

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到未排序序列的起始位置。
  3. 重复第二步,直到所有元素均排序完毕。

在这里插入图片描述

public class SelectSort {
    public static void main(String[] args) {
//        int[] arr = {101, 34, 119, 1};
//        System.out.println("排序前");
//        System.out.println(Arrays.toString(arr));
        
        int[] arr = new int[80000];
        for (int i=0;i<arr.length;i++){
            arr[i]= (int) (Math.random()*8000000); // 生成一个[0,8000000)数
        }
		long start = System.currentTimeMillis();
        // 测试选择排序
        selectSort(arr);
        long end = System.currentTimeMillis();
        System.out.println("排序的时间是=" + (end - start));
    }

    // 选择排序
    public static void selectSort(int[] arr) {
        for (int i=0;i<arr.length-1;i++){
            int minIndex=i; // 记录最小值索引
            int min=arr[i]; // 记录最小值
            // 每次循环少一个:因为每循环一次就有一个已经排序好了
            for (int j=i+1;j<arr.length;j++){
                if (min>arr[j]){ // 如果最小值比当前值大则更新
                    min=arr[j];
                    minIndex=j;
                }
            }
            // 避免重复 小优化 可判断也可不判断
            if (minIndex!=i){ 
                arr[minIndex]=arr[i];
                arr[i]=min;
            }
        }
    }
}

三、插入排序

基本介绍:

插入排序也是一种常见的排序算法,插入排序的思想是:将初始数据分为有序部分和无序部分,每一步将一个无序部分的数据插入到前面已经排好序的有序部分中,直到插完所有元素为止。

步骤:

每次从无序部分中取出一个元素,与有序部分中的元素从后向前依次进行比较,并找到合适的位置,将该元素插到有序组当中。
在这里插入图片描述

public class InsertSort {
    public static void main(String[] args) {
        // int[] arr = {101, 34, 119, 1};
        int[] arr = new int[80000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 800000);
        }
        
		long start = System.currentTimeMillis();
        // 测试插入排序
        insertSort(arr);
        long end = System.currentTimeMillis();
        System.out.println("排序的时间是=" + (end - start));
    }

    // 插入排序
    public static void insertSort(int[] arr) {
        int insertVal=0; // 待插入的值
        int insertIndex=0; // 应该插入的索引位置
        for (int i = 1; i < arr.length; i++) {
            // 定义待插入的数
            insertVal = arr[i];
            insertIndex = i - 1;
            // 给insertVal找到插入的位置
            // 说明
            // 1.insertIndex>=0保证在给insertVal找出入位置,不越界
            // 2.insertVal<arr[insertIndex] 待插入的数,还没有找到插入位置
            // 3.就需要将arr[insertIndex] 后移
            while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
                arr[insertIndex + 1] = arr[insertIndex];
                insertIndex--;
            }
            // 当退出while循环时,说明插入的位置找到,insertIndex+1
            // 判断是否需要重置
            if (insertIndex + 1 != i) {
                arr[insertIndex + 1] = insertVal;
            }
        }
    }
}

四、希尔排序

基本介绍:

希尔排序又称为缩小增量排序,是对之前介绍的插入排序的一种优化版本,优化的地方在于:不用每次插入一个元素时,就和序列中有序部分中的所有元素进行比较。该方法的基本思想是:设待排序元素序列有n个元素,首先取一个整数increment(小于序列元素总数)作为间隔,所有距离为increment的元素放在同一个逻辑数组中,在每一个逻辑数组中分别实行直接插入排序。然后缩小间隔increment,重复上述逻辑数组划分和排序工作。直到最后取increment=1,将所有元素放在同一个数组中排序为止。

步骤:

  1. 选increment,划分逻辑分组,组内进行直接插入排序。
  2. 不断缩小increment,继续组内进行插入排序。
  3. 直到increment=1,在包含所有元素的序列内进行直接插入排序。

在这里插入图片描述
在这里插入图片描述

public class ShellSort {
    public static void main(String[] args) {
//        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        int[] arr = new int[80000000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 800000);
        }
        long start = System.currentTimeMillis();
//        shellSort(arr); // 交换式
        shellSort2(arr); // 移位式
        long end = System.currentTimeMillis();
        System.out.println("排序的时间是=" + (end - start));
    }

    // 交换法
    public static void shellSort(int[] arr) {
        int temp = 0;
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            // 遍历各组中所有的元素(共gap组,每组有..个元素),步长gap
            for (int i = gap; i < arr.length; i++) {
            	// 同一组进行交换
                for (int j = i - gap; j >= 0; j -= gap) {
                    if (arr[j] > arr[j + gap]) {
                        temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
        }
    }
    
    // 对交换式的希尔排序进行优化->移位法(组内插入排序)
    public static void shellSort2(int[] arr) {
        // 增量gap,并逐步缩小增量
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            // 从gap个元素开始,逐个对其所在的组进行直接插入排序
            for (int i = gap; i < arr.length; i++) {
                int j = i; // 要插入位置的索引
                int temp = arr[j]; // 待插入的值
                if (arr[j] < arr[j - gap]) {
                    while (j - gap >= 0 && temp < arr[j - gap]) {
                        // 移动
                        arr[j] = arr[j - gap];
                        j -= gap;
                    }
                    // 当退出while后,就给temp找到插入的位置
                    arr[j] = temp;
                }
            }
        }
    }
}

五、快速排序

基本介绍:

快速排序也是一种较为基础的排序算法,其效率比冒泡排序算法有大幅提升。因为使用冒泡排序时,一趟只能选出一个最值,有n个元素最多就要执行n - 1趟比较。而使用快速排序时,一次可以将所有元素按大小分成两堆,也就是平均情况下需要logn轮就可以完成排序。 快速排序的思想是:每趟排序时选出一个基准值,然后将所有元素与该基准值比较,并按大小分成左右两堆,然后递归执行该过程,直到所有元素都完成排序。

步骤:

  1. 先从数列中取出一个数作为基准数。
  2. 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
  3. 再对左右区间重复第二步,直到各区间只有一个数。

二路快排图解:

1、场景:对 6 1 2 7 9 3 4 5 10 8 这 10 个数进行排序
2、思路:
先找一个基准数(一个用来参照的数),为了方便,我们选最左边的 6,希望将 >6 的放到 6 的右边,<6 的放到 6 左边。
如:3 1 2 5 4 6 9 7 10 8
先假设需要将 6 挪到的位置为 k,k 左边的数 <6,右边的数 >6
(1)我们先从初始数列“6 1 2 7 9 3 4 5 10 8 ”的两端开始“探测 ”,先从右边往左找一个 <6 的数,再从左往右找一个 >6 的数,然后交换。我们用变量 i 和变量 j 指向序列的最左边和最右边。刚开始时最左边 i=0 指向 6,最右边 j=9 指向 8 ;
在这里插入图片描述
(2)现在设置的基准数是最左边的数,所以序列先右往左移动(j–),当找到一个 <6 的数(5)就停下来。接着序列从左往右移动(i++),直到找到一个 >6 的数又停下来(7);
(3)两者交换,结果:6 1 2 5 9 3 4 7 10 8;
在这里插入图片描述
(4)j 的位置继续向左移动(友情提示:每次都必须先从 j 的位置出发),发现 4 满足要求,接着 i++ 发现 9 满足要求,交换后的结果:6 1 2 5 4 3 9 7 10 8;
在这里插入图片描述
(5)目前 j 指向的值为 9,i 指向的值为 4,j-- 发现 3 符合要求,接着 i++ 发现 i=j,说明这一轮移动结束啦。现在将基准数 6 和 3 进行交换,结果:3 1 2 5 4 6 9 7 10 8;现在 6 左边的数都是 <6 的,而右边的数都是 >6 的,但游戏还没结束
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(6)我们将 6 左边的数拿出来先:3 1 2 5 4,这次以 3 为基准数进行调整,使得 3 左边的数小于3,右边的数大于3,根据之前的模拟,这次的结果:2 1 3 5 4
(7)再将 2 1 抠出来重新整理,得到的结果: 1 2
(8)剩下右边的序列:9 7 10 8 也是这样来搞,最终的结果: 1 2 3 4 5 6 7 8 9 10 (具体看下图)
在这里插入图片描述

public class QuickSort {
    public static void main(String[] args) {
        int[] arr = new int[10000000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 10000);
        }
        long start = System.currentTimeMillis();
        quickSort(arr, 0, arr.length - 1);
        long end = System.currentTimeMillis();
        System.out.println((end - start) + "ms");
//        System.out.println(Arrays.toString(arr));
    }

    /**
     * @param arr 要排序的数组
     * @param p   左指针0
     * @param r   右指针arr.length
     */
    public static void quickSort(int[] arr, int p, int r) {
        if (p <= r) {
            // 10000000万个数据测试
//            int q = Partition1(arr, p, r); // 7112ms
//            int q = Partition2(arr, p, r); // 2426ms
//            quickSort(arr, p, q - 1); // 对左边排序
//            quickSort(arr, q + 1, r); // 对右边排序
            quick3(arr, p, r); // 578ms
//            quick(arr, p, r); // 551ms
        }
    }

    /**
     * 单路快排
     *
     * @param arr 要排序的数组
     * @param p   左指针0
     * @param r   右指针arr.length
     */
    public static int Partition1(int[] arr, int p, int r) {
        int pivot = arr[p]; // 组元
        int left = p + 1; // 左指针
        int right = r; // 右指针
        while (left <= right) {
            if (arr[left] < pivot) {
                left++;
            } else {
                swap(arr, left, right); // 交换
                right--;
            }
        }
        swap(arr, p, right);
        return right;
    }

    /**
     * 双路快排
     *
     * @param arr 要排序的数组
     * @param p   左指针0
     * @param r   右指针arr.length
     * @return
     */
    public static int Partition2(int[] arr, int p, int r) {
        int pivot = arr[p]; // 组元
        int left = p + 1; // 左指针
        int right = r; // 右指针
//        while (true) {
//            while (left <= right && arr[left] <= pivot)
//                left++; // 左指针右移寻找
//            while (right >= left && arr[right] > pivot)
//                right--; // 右指针左移寻找
//            if (left > right) // 临界条件
//                break;
//            swap(arr, left, right); // 交换
//            left++;
//            right--;
//        }
        while (left <= right) {
            while (left <= right && arr[left] <= pivot)
                left++; // 左指针右移寻找
            while (right >= left && arr[right] > pivot)
                right--; // 右指针左移寻找
            if (left < right) { // 临界条件
                swap(arr, left, right); // 交换
                left++;
                right--;
            }
        }
        swap(arr, p, right);
        return right;
    }

    /**
     * 三路快排
     *
     * @param arr 待排序的数组
     * @param p   左指针0
     * @param r   右指针arr.length
     */
    public static void quick3(int[] arr, int p, int r) {
        if (p > r) {
            return;
        }
        int pivot = arr[p];
        int left = p + 1; // 左指针
        int e = p; // 相等区域的第一个元素
        int right = r; // 右指针
        while (left <= right) {
            if (arr[left] < pivot) {
                swap(arr, e++, left++);
            } else if (arr[left] > pivot) {
                swap(arr, left, right--);
            } else {
                left++;
            }
        }
        quick3(arr, p, e - 1);
        quick3(arr, right + 1, r);
    }

    //交换数组元素
    public static void swap(int[] arr, int left, int right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }
}

扩展:双指针遍历,分治,递归思想

奇左偶右

调整数组的顺序,使奇数在左,偶数在右
要求时间复杂度为O(n)

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

    public static void eachLeftRight(int[] arr) {
        int left = 0;//左指针
        int right = arr.length - 1;//右指针
        while (left <= right) {
            while (left <= right && arr[left] % 2 == 1) {//左到右寻找偶数
                left++;
            }
            while (left <= right && arr[right] % 2 == 0) {//右到左寻找奇数
                right--;
            }
            if (left < right) {//找到后交换
                swap(arr, left, right);
                left++;
                right--;
            }
        }
    }

    public static void swap(int[] arr, int left, int right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }
}

最长递增子序列

(1,9,2,5,7,3,4,6,8,0)中最长的递增子序列为(3,4,6,8)
思路:利用两个指针,扫描记录
需要什么变量?
双指针遍历left,right
记录最长长度length
记录最长长度的开头索引下标index

public class 最长递增子序列 {
    public static void main(String[] args) {
        int[] arr = new int[]{1, 9, 2, 5, 7, 3, 4, 6, 8, 0};
        int[] arr1 = new int[]{1, 9, 2, 5, 7, 3, 4, 6, 8, 9, 10, 12, 3, 4, 5};
        theMaxLong(arr);
        System.out.println();
        theMaxLong(arr1);
    }

    public static void theMaxLong(int[] arr) {
        int left = 0; // 定指针
        int right = 1; // 动指针
        int length = 0; // 记录长度
        int index = 0; // 记录索引
        while (right < arr.length) { // right遍历数组
            while (right < arr.length && arr[right] > arr[right - 1]) { // 后一个数比前一个数大则右移
                right++;
            }
            if (right - left > length) { // 刷新最长长度
                length = right - left;
                index = left; // 记录开始索引值
            }
            // 寻找下一个子序列
            left = right;
            right++;
        }
        // 遍历输出
        for (int i = index; i < index + length; i++) {
            System.out.print(arr[i] + " ");
        }
    }
}

第k个元素

以尽量高的效率求出一个乱序数组中按数值顺序的第k个元素值
法一:先排序,直接输出
法二:快排分区

public class theK {
    public static void main(String[] args) {
        int[] arr = {1, 3, 2, 4, 5, 67, 3, 7, 8, 9, 2, 10};
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入k:");
        int k = sc.nextInt();
        int num = findK(arr, k, 0, arr.length - 1);
        System.out.println(num);
    }

    public static int findK(int[] arr, int k, int p, int r) {
        if (p <= r) {
            int q = partition(arr, p, r);
            int qK = q - p + 1;//主元是第几个元素
            if (k > qK) {
                return findK(arr, k-qK, q + 1, r);//要减去前半段
            } else if (k < qK) {
                return findK(arr, k, p, q - 1);
            } else {
                return arr[q];
            }
        }
        return -1;
    }

    public static int partition(int[] arr, int p, int r) {
        //三点中值法
        int midIndex = 0;
        if (arr[p] >= arr[r / 2] && arr[p] >= arr[r]) {
            midIndex = p;
        } else if (arr[r / 2] >= arr[p] && arr[r / 2] >= arr[r]) {
            midIndex = r / 2;
        } else if (arr[r] >= arr[p] && arr[r] >= arr[r / 2]) {
            midIndex = r;
        }
        swap(arr, p, midIndex);
        int pivot = arr[p];
        int left = p + 1;//左
        int right = r;//右
        while (left <= right) {
            while (left <= right && arr[left] <= pivot) {
                left++;
            }
            while (left <= right && arr[right] > pivot) {
                right--;
            }
            if (left < right) {
                swap(arr, left, right);
            }
        }
        swap(arr, p, right);
        return right;
    }

    public static void swap(int[] arr, int left, int right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }
}

六、归并排序

基本介绍:

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

步骤:

  1. 归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。
  2. 将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
  3. 将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class MergeSort {
    public static void main(String[] args) {
        int[] arr = new int[10000000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 10000);
        }
        int[] temp = new int[arr.length];
        long start = System.currentTimeMillis();
        mergeSort(arr, 0, arr.length - 1, temp);
        long end = System.currentTimeMillis();
        System.out.println((end-start)+"ms");
//        System.out.println(Arrays.toString(arr));
    }

    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = left + ((right - left) >> 1);
            // 向左递归
            mergeSort(arr, left, mid, temp);
            // 向右递归
            mergeSort(arr, mid + 1, right, temp);
            // 合并
            merge(arr, left, right, mid, temp);
        }
    }

    public static void merge(int[] arr, int left, int right, int mid, int[] temp) {
        int i = left; // 左指针
        int j = mid + 1; // 右指针
        int t = 0; // 辅助指针
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[t++] = arr[i++];
            } else {
                temp[t++] = arr[j++];
            }
        }
        // 剩余的 左边还没有放完
        while (i <= mid) {
            temp[t++] = arr[i++];
        }
        // 右边还没有放完
        while (j <= right) {
            temp[t++] = arr[j++];
        }
        // 拷贝
        t = 0;
        int tempLeft = left;
        while (tempLeft <= right) {
            arr[tempLeft++] = temp[t++];
        }
    }
}

扩展

合并有序数组

给定两个排序后的数组A和B,其中A的末端有足够的缓冲空间容纳B。编写一个方法,将B合并入A并排序。

public class MergeArr {
    public static void main(String[] args) {
        int[] A = new int[10];
        int m=6;
        for (int i = 0; i < m; i++) {
            A[i] = i;
        }
        int[] B = {1, 3, 4, 5};
        merge(A, B,m);
        System.out.println(Arrays.toString(A));
    }

    public static void merge(int[] A, int[] B,int m) {
        int cur = m + B.length - 1; // A数组容纳B数组后指向最后的元素
        int left = m - 1; // 指向A数组最后的元素
        int right = B.length - 1; // 指向B数组最后的元素
        while (left >= 0 && right >= 0) {
            if (A[left] < B[right]) {
                A[cur--] = B[right--];
            } else {
                A[cur--] = A[left--];
            }
        }
        while (left >= 0) {
            A[cur--] = A[left--];
        }
        while (right >= 0) {
            A[cur--] = B[right--];
        }
    }
}

逆序对个数

一个数列,如果左边的数大,右边的数小,则称这两个数位一个逆序对。求出一个数列中有多少个逆序对。

public class ReverseNum {
    public static void main(String[] args) {
        int[] arr = {2, 1, 3, 5, 1, 1};
        int[] temp = new int[arr.length];
        mergerSort(arr, 0, arr.length - 1, temp);
        System.out.println(niXuCount);
        System.out.println(Arrays.toString(arr));
//        niXuDui(arr);
    }

    static int niXuCount = 0;

    public static void mergerSort(int[] A, int left, int right, int[] temp) { // temp为临时数组
        if (left < right) {
            int midIndex = left + ((right - left) >> 1); // 中间索引
            // 分
            mergerSort(A, left, midIndex, temp);
            mergerSort(A, midIndex + 1, right, temp);
            // 合
            merge(A, left, right, midIndex, temp);
        }
    }

    // 暴力法
    public static void niXuDui(int[] A) {
        int count = 0; // 计数变量
        for (int i = 0; i < A.length; i++) {
            for (int j = i + 1; j < A.length; j++) {
                if (A[i] > A[j]) {
                    count++;
                }
            }
        }
        System.out.println(count);
    }

    public static void merge(int[] A, int left, int right, int midIndex, int[] temp) {
        int i = left; // 左指针
        int j = midIndex + 1; // 右指针
        int t = 0; // 指向临时变量
        while (i <= midIndex && j <= right) { // 其中一边遍历完则退出
            if (A[i] <= A[j]) {
                temp[t++] = A[i++];
            } else { // 右边小
                temp[t++] = A[j++];
                niXuCount += midIndex - i + 1; // 归并排序中加一行代码
            }
        }
        // 左边未遍历完
        while (i <= midIndex) {
            temp[t++] = A[i++];
        }
        // 右边未遍历完
        while (j <= right) {
            temp[t++] = A[j++];
        }
        // 将temp复制回A中
        t = 0;
        int tempLeft = left;
        while (tempLeft <= right) {
            A[tempLeft++] = temp[t++];
        }
    }
}

七、堆排序

基本介绍:

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

步骤:

  1. 构造堆;
  2. 得到堆顶元素,这个值就是最大值;
  3. 交换堆顶元素和数组中的最后一个元素,此时所有元素中的最大元素已经放到合适的位置;
  4. 对堆进行调整,重新让除了最后一个元素的剩余元素中的最大值放到堆顶;
  5. 重复2~4这个步骤,直到堆中剩一个元素为止。

在这里插入图片描述

// 该代码是构造小顶推从大到小排序
public class HeapSort {
    public static void main(String[] args) {
        int[] arr = {1, 5, 2, 5, 3, 6, 7, 9, 4, 5};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void heapSort(int[] arr) {
        // 构造小顶堆
        minHeap(arr);
        for (int i = arr.length - 1; i >= 0; i--) {
            // 交换顶部元素和最后一个元素
            swap(arr, 0, i);
            // 减少数据堆化
            minHeapFixDown(arr, 0, i - 1);
        }
    }

    // 第一次构造小顶堆
    public static void minHeap(int[] arr) {
        int n = arr.length;
        for (int i = n / 2 - 1; i >= 0; i--) {
            minHeapFixDown(arr, i, n);
        }
    }

    // 堆化
    public static void minHeapFixDown(int[] arr, int i, int n) {
        int left = 2 * i + 1; // 左子结点
        int right = 2 * i + 2; // 右子结点
        // 左子结点已经越界
        if (left >= n) {
            return;
        }
        int min = left;
        // 右子结点越界
        if (right >= n) {
            min = left;
        } else { // 没有越界
            if (arr[right] < arr[left]) {
                min = right;
            }
        }
        // 根结点比子结点都要小,结束
        if (arr[i] <= arr[min]) {
            return;
        }
        // 交换
        swap(arr, i, min);
        // 小孩子那个位置的值发生了变化,i变更为小孩子那个位置,递归调整
        minHeapFixDown(arr, min, n);
    }

    public static void swap(int[] arr, int left, int right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }
}

扩展

小顶堆与topK问题

求海量数据(正整数)按逆序排列的前k个数(topK),因为数据量太大,不能全部
存储在内存中,只能一个一个地从磁盘或者网络上读取数据,请设计一个高效的算法
来解决这个问题。
第一行:用户输入k,代表要求topK
随后的n(不限制)行,每一行是一个整数代表用户输入的数据
请输出topK,从小到大,空格分割

public class 小顶堆与topK问题 {

    static int[] heap;
    static int index = 0;
    static int k;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        k = sc.nextInt();
        heap = new int[k];
        int x = sc.nextInt();
        while (x != -1) {
            deal(x);//处理x
            x = sc.nextInt();
        }
        printRs();
    }

    /**
     * 如果数据量小于等于k,直接入堆中
     * 等于k的时候进行堆化
     *
     * @param x
     */
    public static void deal(int x) {
        if (index < k) {
            heap[index++] = x;
            if (index == k) {
                //堆化
                minHeap(heap);
            }
        } else if (heap[0] < x) {//x和堆顶进行比较,如果x大于堆顶,x将堆顶挤掉并向下调整
            heap[0] = x;
            minHeapFixDown(heap, 0, k);
            printRs();
        }
    }

    public static void printRs() {
        System.out.println(Arrays.toString(heap));
    }

    //对数组第一次堆化from n/2-1 to 0;
    public static void minHeap(int[] arr) {
        int n = arr.length;
        for (int i = n / 2 - 1; i >= 0; i--) {
            minHeapFixDown(arr, i, n);
        }
    }

    //小顶堆,堆化
    public static void minHeapFixDown(int[] arr, int i, int n) {
        int left = 2 * i + 1;//左子结点
        int right = 2 * i + 2;//右子结点
        if (right >= n) {//越界
            return;
        }
        int min;
        if (arr[right] > arr[left]) {
            min = left;
        } else {
            min = right;
        }
        if (arr[i] < min) {
            return;//已经是小顶堆
        }
        swap(arr, i, min);
        minHeapFixDown(arr, min, n);
    }

    public static void swap(int[] arr, int left, int right) {
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }
}

八、计数排序

基本介绍:

计数排序(Count Sort)是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。计数排序的思想是在给定的一组序列中,先找出该序列中的最大值和最小值,从而确定需要开辟多大的辅助空间,每一个数在对应的辅助空间中都有唯一的下标。

步骤:

  1. 找出序列中最大值,开辟Max+1的辅助空间
  2. 最小的数对应下标为0的位置,遇到一个数就给对应下标处的值+1,。
  3. 遍历一遍辅助空间,就可以得到有序的一组序列
public class CountSort {
    public static void main(String[] args) {
        int[] arr = new int[]{3, 2, 5, 2, 5, 1, 6, 7};
        countSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void countSort(int[] arr) {
        // 1.找出数组最大值Max
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (max < arr[i]) {
                max = arr[i];
            }
        }
        // 2.开辟一个辅助空间Max+1
        int[] temp = new int[max + 1];
        // 3.遍历原数组,在辅助数组中作记录
        for (int i = 0; i < arr.length; i++) {
            temp[arr[i]]++;
        }
        int index = 0;
        // 4.遍历辅助数组
        for (int i = 0; i < temp.length; i++) {
            // 1)如果等于0,跳过
            if (temp[i] == 0) {
                continue;
            }
            // 2)如果不等于0,就赋值给原来数组对应的下标,对应值要--
            while (temp[i] != 0) {
                arr[index++] = i;
                temp[i]--;
            }
        }
    }
}

九、桶排序

基本介绍:

1)基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
2)基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
3)基数排序(Radix Sort)是桶排序的扩展
4)基数排序是1887年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。

步骤:

将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

// 法一:
// 10个桶,每个桶装的数个数不定,适合用数组加ArrayList
private static ArrayList[] bucket=new ArrayList[10];

// 初始化桶
static{
	for(int i=0;i<bucket.length;i++){
		bucket[i]=new ArrayList();
	}
}

// 排序
sort(A){
	d=1;//入桶依据的位初始化为1
	max=maxOf(A);//求出数组的最大值
	/*
	dNum=1;// 最大数据的位数
	while(max/10!=0){
		dNum++;
		max/=10;
	}
	*/
	dNum=(max+"").length;
	while(d<=dNum){
		//依据第二个参数入桶和出桶
		sort(A,d++);
	}
}

// 将数组A,按d这个位来分配和收集
sort(A,d){
	// 全部入桶
	for(i=0;i<A.length;i++){
		//getDigitOn:获取第d位数字
		putInBucket(A[i],getDigitOn(A[i],d));
	}
	// 每个桶中的数据依次压入原数组
	k=0;
	for(i=0;j<bucket.length;j++){
		for(Object m:bucket[j]){
			A[k++]=(Integer)m;
		}
	}
	// 清空
	clearAll();
}

// 获取第d位数字
getDigitOn(data,digitOn){
	int n=1;
	if(n<digitOn){
		n*=((digitOn-n)*10);
	}
	return data/n%10;
}

// 最大值
maxOf(A){
	max=A[0];
	for(i=1;i<A.length;i++){
		if(A[i]>max){
			max=A[i];
		}
	}
}

putInBucket(data,digitOn){
	switch(digitOn){
		case 0:
			bucket[0].add(data);
			break;
		case 1:
			bucket[1].add(data);
			break;
		case 2:
			bucket[2].add(data);
			break;	
		case 3:
			bucket[3].add(data);
			break;
		case 4:
			bucket[4].add(data);
			break;
		case 5:
			bucket[5].add(data);
			break;
		case 6:
			bucket[6].add(data);
			break;
		case 7:
			bucket[7].add(data);
			break;
		case 8:
			bucket[8].add(data);
			break;
		case 9:
			bucket[9].add(data);
			break;
	}
}

clearAll(){
	for(ArrayList b:bucket){
		b.clear();
	}
}


// 法二:
sort(A){
	// 得到数组中最大的数的位数
	int max=A[0];
	for(int i=1;i<A.length;i++){
		if(A[i]>max){
			max=A[i];
		}
	}
	
	// 得到最大数是几位数
	int maxLength=(""+max).length;
	/*int maxLength=1;
	while(max/10!=0){
		maxLength++;
		max/=10;
	}*/
	
	// 定义一个二维数组表示10个桶,每个桶是一个一维数组
	// 说明
	// 1.二维数组包含10个一维数组
	// 2.为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length
	// 3.很明确,基数排序是使用空间换时间的经典算法
	int[][] bucket=new int[10][A.length];
		
	// 为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
	int[] bucketElementCounts=new int[10];
	
	for(int i=0,n=1;i<maxLength;i++,n*=10){
		for(int j=0;j<A.length;j++){
			// 针对每个元素的对应位进行排序处理,第一次是个位,第二次是十位...
			// 取出每个元素的对应位的值
			int dealNum=A[j]/n%10;
			// 放入到对应的桶中
			bucket[dealNum][bucketElementCounts[dealNum]]=dealNum;
			bucketElementCounts[dealNum]++;
		}
		// 照这个桶的顺序(一维数组的下标一次取出数据,放入原来数组)
		int index=0;
		// 遍历每一桶,并将桶中的数据放入到原数组
		for(int k=0;k<bucket.length;k++){
			// 如果桶中有数据,我们才放入到原数组
			if(bucketElementCounts[k]!=0){
				// 循环该桶,即第k个桶(即第k个一维数组),放入
				for(int l=0;l<bucketElementCounts[k].length;l++){
					// 取出元素放入到arr
					A[index++]=bucket[k][l];
				}
			}
			// 第i+1处理后需要将每个bucketElementCounts[k]=0!!!
			bucketElementCounts[k]=0;
		}
	}
}

十、基数排序

基础介绍:

基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中的数的最大的位数;基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

步骤:

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点);

十一、扩展:java特殊排序

特殊排序

输入一个正整数数组,把数组里所有整数拼接起来排成一个数,打印出能拼接出的所有数字中最小的一个。
例如输入数组{3,32,321},则打印出这3个数字能排成的最小数字为:321323。两两组合得到的数较小进行排序

public class SpecialOrder {
    public static void main(String[] args) {
        Integer[] arr = {3, 32, 321};
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                //o1:后一个 o2:前一个
                String s1 = o1 + "" + o2;//323
                String s2 = o2 + "" + o1;//332
                return s1.compareTo(s2);//-1 逆序
            }
        });
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            sb.append(arr[i]);
        }
        System.out.println(Integer.parseInt(sb.toString()));
    }
}

算法总结表

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明仔爱编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值