【算法】排序算法

66 阅读9分钟

1.冒泡排序

冒泡排序的思想十分简单,如果左边的数字大于右边的数字,二者进行交换,每一次都会将当前未排序的数据中最大值移动到数组的最右端。

public static void bubbleSort(int[] nums){
    for(int i = 0; i < nums.length;i++){
        for(int j = 0; j < nums.length-1-i;j++){
            if(nums[j] > nums[j+1]){
                int temp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = temp;
            }
        }
    }
}

如果此时数组已经有序,上边的算法依然会一直进行比较,我们可以使用一个标记来检查当前数组元素是否已经有序,若有序,则结束程序。

public static void bubbleSort(int[] nums){
    boolean flag = false;
    for(int i = 0; i < nums.length && !flag;i++){
        flag = true;
        for(int j = 0; j < nums.length-1-i;j++){
            if(nums[j] > nums[j+1]){
                flag = false;
                int temp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = temp;
            }
        }
    }
    System.out.println(count);
}

2.选择排序

选择排序的思想则是,首先找到最大的元素,移动到数组的末尾,然后在剩下的n-1的元素中找到最大的元素,将其移动到数组的倒数第二个位置,重复这个过程,直到剩下一个元素。

public static void selectSort(int[] nums){
    int len = nums.length;
    for(int i = 0; i < nums.length;i++){
        int maxIndex = findMax(nums,i);
        int temp = nums[maxIndex];
        nums[maxIndex] = nums[len-1-i];
        nums[len-1-i] = temp;
    }
}
public static int findMax(int[] nums, int times){
    int res = 0;
    for(int i = 1; i < nums.length-times;i++){
        if(nums[res] < nums[i]){
            res = i;
        }
    }
    return res;
}

同样的,如果数组已经有序,上边的代码依然会去遍历数组,找最大值,我们依然可以使用一个标记位来记录当前数组是否已经有序,如果有序,程序结束。

public static void selectSort(int[] nums){
    int len = nums.length;
    boolean isSorted = false;
    for(int i = 0; i < nums.length && !isSorted;i++){

        int maxIndex = nums.length - i - 1;
        isSorted = true;
        //找最大值的下标
        for(int j = maxIndex-1; j >= 0;j--){
            if(nums[maxIndex] < nums[j]){
                maxIndex = j;
                isSorted = false;
            }
        }
        int temp = nums[maxIndex];
        nums[maxIndex] = nums[len-1-i];
        nums[len-1-i] = temp;
    }
}

3.快速排序

快速排序的思想是,选中一个基准,把小于当前基准位置数据的数据移动到基准索引的左边,大于当前基准位置数据的数据移动到基准索引的右边,之后在对基准左边使用这个算法,对基准右边使用这个算法。

public static void quickSort(int[] nums,int start, int end){
     //只有一个数据
    if(start >= end){
        return;
    }
    int midlle = partation(nums,start,end);
    quickSort(nums,start,midlle-1);
    quickSort(nums,midlle+1,end);

}
public static int partation(int[] nums, int start,int end){
    int pivot = nums[start];
    int left = start+1;
    int right = end;

    while(left < right){
        //找打第一个大于基准的数字
        while(left < right && nums[left] <= pivot){
            left++;
        }
        //找到第一个小于基准的数字
        while(left < right && nums[right] >= pivot){
            right--;
        }
        //交换两者位置
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
    //如果left 和 right 相等,单独比较 nums[right]和pivot比较
    if(left == right && nums[right] > pivot) right--;
        //交换两者位置
        int temp = nums[right];
        nums[right] = pivot;
        nums[start] = temp;
        return right;

}

4.插入排序

插入排序的思想很简单,将数组分为已经排好序的一部分和未排序的一部分,排序算法开始的时候,我们认为第1个元素已经排好序,从后往前判断未排好序第一个元素应该插入到哪里。

public static void insertSort(int[] nums){
    //默认第一个数字已经排好序
    int sorted = 1;
    for (int i = 1; i < nums.length; i++) {
        int value = nums[i];
        int position = i;
        //从后往前,判断当前元素应该插入到哪里
        while(position > 0 && nums[position-1] > value){
            nums[position] = nums[position-1];
            position--;
        }
        nums[position] = value;
    }
}

5.归并排序

通常情况下使用大顶堆,对堆进行层序遍历,得到升序排序。

   public static void heapSort(int[] arr) {
       // 构建初始大顶堆
       buildMaxHeap(arr);
       for (int i = arr.length - 1; i > 0; i--) {
           // 将最大值交换到数组最后
           swap(arr, 0, i);
           // 调整剩余数组,使其满足大顶堆
           maxHeapify(arr, 0, i);
       }
   }
/*   // 构建初始大顶堆
   private static void buildMaxHeap(int[] arr) {
       // 从最后一个非叶子结点开始调整大顶堆,最后一个非叶子结点的下标就是 arr.length / 2-1
       for (int i = arr.length / 2 - 1; i >= 0; i--) {
           maxHeapify(arr, i, arr.length);
       }
   }
   // 调整大顶堆,第三个参数表示剩余未排序的数字的数量,也就是剩余堆的大小
   private static void maxHeapify(int[] arr, int i, int heapSize) {
       // 左子结点下标
       int l = 2 * i + 1;
       // 右子结点下标
       int r = l + 1;
       // 记录根结点、左子树结点、右子树结点三者中的最大值下标
       int largest = i;
       // 与左子树结点比较
       if (l < heapSize && arr[l] > arr[largest]) {
           largest = l;
       }
       // 与右子树结点比较
       if (r < heapSize && arr[r] > arr[largest]) {
           largest = r;
       }
       if (largest != i) {
           // 将最大值交换为根结点
           swap(arr, i, largest);
           // 再次调整交换数字后的大顶堆
           maxHeapify(arr, largest, heapSize);
       }
   }
   private static void swap(int[] arr, int i, int j) {
       int temp = arr[i];
       arr[i] = arr[j];
       arr[j] = temp;
   }*/

6.希尔排序

希尔排序是对插入排序的优化,当数组部分有序的时候,插入排序的效率较高,因此我们构建相对有序的分段数组,在每一段使用插入排序,不断缩小小分组的大小,直到小分组大小为1.

public static void shellSort(int[] nums){
    int len = nums.length;
    //分组不停的缩小
    for(int gap = len / 2; gap > 0; gap /= 2){

        //获取每个分组中的元素
        for (int startIndex = 0; startIndex < gap; startIndex++) {
            //开始插入排序
            for (int currentIndex = startIndex + gap;  currentIndex < len; currentIndex+= gap) {
                int curNumber = nums[currentIndex];
                int preIndex = currentIndex - gap;
                while(preIndex >= startIndex && nums[preIndex] > curNumber){
                    nums[preIndex + gap] = nums[preIndex];
                    preIndex -= gap;
                }
                nums[preIndex+gap] = curNumber;
            }

        }
    }
}

7.计数排序

计数排序就是记录对应元素出现的个数,统计比当前元素小的元素个数,就知道了当前元素在排好序数组中的位置。

public static void countingSort(int[] nums){

    // 判空及防止数组越界
    if (nums == null || nums.length <= 1) return;
    //找到最大值和最小值
    int max = nums[0];
    int min = nums[0];
    for (int i = 1; i < nums.length; i++) {
        max = Math.max(max,nums[i]);
        min = Math.min(min,nums[i]);
    }
    int len = max - min + 1;
    int[] counting = new int[len];
    //开始统计个数
    for (int i = 0; i < nums.length; i++) {
        counting[nums[i] - min]++;
    }
    //记录前边比自己小的元素个数
    int preMin = 0;
    for (int i = 0; i < len; i++) {
        preMin += counting[i];
        counting[i] = preMin - counting[i];
    }
    //开始排序
    int[] result = new int[nums.length];

    for (int i = 0; i < nums.length; i++) {
        result[counting[nums[i] - min]] = nums[i];
        counting[nums[i] - min]--;
    }

    //结果赋回nums
    for (int i = 0; i < nums.length; i++) {
        nums[i] = result[i];
    }
}

8.基数排序

基数排序的思想是,我们准备0-9 10个bucket,先按照个位数字放入桶中,取出来,再按照十位数字放入桶中,重复这个过程,直到最大元素的最高位放完,就得到了排好序的数组。(每一轮排序采用计数排序,采用的是倒序的计数排序,直接计算当前元素在排好序数组中的位置)。,第一个算法是在待排序元素中没有负数的情况,若排序元素中有负数,我们可以将桶设置为-9 - 9 19个桶(0-18 给对应的元素+9即可获得当前位置)。

public static void radixSort(int[] nums){

    if (nums == null || nums.length == 1) {
        return;
    }
    //找到最大值
    int max = nums[0];
    for (int i = 1; i < nums.length; i++) {
        max = Math.max(max, nums[i]);
    }
    //得到最大值的位数
    int maxLength = 0;
    while(max > 0){
        maxLength++;
        max /= 10;
    }

    //开始基数排序
    int[] counting = new int[10];
    int[] result = new int[nums.length];
    int dev = 1;
    for (int i = 0; i < maxLength; i++) {
        //根据当前位数 统计基数数量
        for(int value : nums){
            int radix = value/dev % 10;
            counting[radix]++;
        }
        //采用逆序的计数排序,统计当前元素在排好序元素什么位置
        for (int j = 1; j < counting.length; j++) {
            counting[j] += counting[j-1];
        }
        //开始基数排序
        for (int j = nums.length-1; j >= 0 ; j--) {
            int radix = nums[j] / dev % 10;
            result[--counting[radix]] = nums[j];
        }
        // 计数排序完成后,将结果拷贝回 arr 数组
        System.arraycopy(result, 0, nums, 0, nums.length);
        // 将计数数组重置为 0
        Arrays.fill(counting, 0);
        dev *= 10;
    }

}
public class RadixSort {

    public static void radixSort(int[] arr) {
        if (arr == null) return;
        // 找出最长的数
        int max = 0;
        for (int value : arr) {
            if (Math.abs(value) > max) {
                max = Math.abs(value);
            }
        }
        // 计算最长数字的长度
        int maxDigitLength = 0;
        while (max != 0) {
            maxDigitLength++;
            max /= 10;
        }
        // 使用计数排序算法对基数进行排序,下标 [0, 18] 对应基数 [-9, 9]
        int[] counting = new int[19];
        int[] result = new int[arr.length];
        int dev = 1;
        for (int i = 0; i < maxDigitLength; i++) {
            for (int value : arr) {
                // 下标调整
                int radix = value / dev % 10 + 9;
                counting[radix]++;
            }
            for (int j = 1; j < counting.length; j++) {
                counting[j] += counting[j - 1];
            }
            // 使用倒序遍历的方式完成计数排序
            for (int j = arr.length - 1; j >= 0; j--) {
                // 下标调整
                int radix = arr[j] / dev % 10 + 9;
                result[--counting[radix]] = arr[j];
            }
            // 计数排序完成后,将结果拷贝回 arr 数组
            System.arraycopy(result, 0, arr, 0, arr.length);
            // 将计数数组重置为 0
            Arrays.fill(counting, 0);
            dev *= 10;
        }
    }
}

9.桶排序

桶排序的思想是:

将区间划分为 n 个相同大小的子区间,每个子区间称为一个桶

遍历数组,将每个数字装入桶中

对每个桶内的数字单独排序,这里需要采用其他排序算法,如插入、归并、快排等

最后按照顺序将所有桶内的数字合并起来

桶排序在实际工作中的应用较少,不仅因为它需要借助于其他排序算法,还因为桶排序算法基于一个假设:所有输入数据都服从均匀分布,也就是说输入数据应该尽可能地均匀分布在每个桶中。只有这个假设成立时,桶排序运行效率才比较高。

public static void bucketSort(int[] nums){
    if (nums == null || nums.length == 1) {
        return;
    }
    //获得最大值 最小值
    int max = nums[0];
    int min = nums[0];
    for (int i = 1; i < nums.length; i++) {
        max = Math.max(max,nums[i]);
        min = Math.min(min,nums[i]);
    }
    int bucketAmount = 100;
    //桶和桶之间的间距
    double gap = 1.0 * (max - min)/(bucketAmount - 1);

    int[][] buckets = new int[bucketAmount][];

    //装桶
    for(int value : nums){
        int index = (int) ((value - min)/gap);
        buckets[index] = add(buckets[index],value);
    }

    //对每一个桶单独排序
    int index = 0;
    for (int i = 0; i < bucketAmount; i++) {
        if(buckets[i] == null || buckets[i].length == 0) continue;
        insertSort(buckets[i]);
        //排序后将元素收集起来
        System.arraycopy(buckets[i],0,nums,index,buckets[i].length);
        index += buckets[i].length;
    }
}

    // 数组扩容
    public static int[] add(int[] arr, int num) {
        if (arr == null) return new int[]{num};
        int[] newArr = Arrays.copyOf(arr, arr.length + 1);
        newArr[arr.length] = num;
        return newArr;
    }

10.堆排序

堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。

public static void heapSort(int[] arr) {
       // 构建初始大顶堆
       buildMaxHeap(arr);
       for (int i = arr.length - 1; i > 0; i--) {
           // 将最大值交换到数组最后
           swap(arr, 0, i);
           // 调整剩余数组,使其满足大顶堆
           maxHeapify(arr, 0, i);
       }
   }

   public static void buildMaxHeap(int[] nums){
       //从最后一个非叶子节点开始调整
       int len = nums.length;

       for (int i = len/2-1; i >= 0; i--) {
           maxHeapify(nums,i,len);
       }
   }
   public static void maxHeapify(int[] nums,int i, int size){
       //找到当前根节点的左右孩子
       int left = i*2 + 1;
       int right = left + 1;
       //找到三者中的最大值
       int large = i;

       if(left < size && nums[left] > nums[large]){
           large =left;
       }
       if(right < size && nums[right] > nums[large]){
           large = right;
       }
       if (large != i) {
           // 将最大值交换为根结点
           swap(nums,i,large);

           // 再次调整交换数字后的大顶堆
           maxHeapify(nums,large,size);
       }

   }
   public static void swap(int[] nums, int i, int large){
       int temp = nums[i];
       nums[i] = nums[large];
       nums[large] = temp;
   }