【Java基础】常见的排序(效率)

(一)冒泡排序

        从头依次对比相邻的两个位置,符合条件就交换(竖着看就想是数字向上冒泡一样)。

public void bubblingSort(){
    int[] arr = {8, 5, 3, 2, 4};
    System.out.println("==========冒泡排序===========");
    // 冒泡
    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 tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}

(二)选择排序

        遍历并选择第i位到最后一位之间最小的那个数,并与第i位进行交换。

 

public void selectSort(){
    int[] arr = {8, 5, 3, 2, 4};
    System.out.println("==========选择排序===========");
    // 选择
    for (int i = 0; i < arr.length; i++) {
        int min = arr[i];
        int index = i;
        // 查找从i到最后之间最小到值和下标进行记录
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[j] < min) {
                min = arr[j];
                index = j;
            }
        }
        // 交换i和最小到下标值
        int tmp = arr[i];
        arr[i] = arr[index];
        arr[index] = tmp;
    }
}

(三)插入排序

        每次遍历到第i个数,都要跟前面的遍历过的数进行比较:如果前面的数大于第i个数则进行交换。

public void insertSort(){
    int[] arr = {8, 5, 3, 2, 4};
    System.out.println("==========插入排序===========");
    // 插入
    for (int i = 1; i < arr.length; i++) {
        for (int j = i; j > 0; j--) {
            if (arr[j] < arr[j-1]) {
                int tmp = arr[j];
                arr[j] = arr[j-1];
                arr[j-1] = tmp;
            } else {
                break;
            }
        }
    }
}

(四) 希尔排序(插入排序的变种)

        希尔排序是插入排序的变种,是将整个数组按照步长进行分组,比如说步长是4数组就被分为ABCDABCD,步长是2则数组就被分为ABABABABAB,每个组中进行插入排序,直到步长为1时,排序完就是顺序的了。

public void shellSort(){
    int[] arr = {8, 5, 3, 2, 4};
    System.out.println("==========希尔排序===========");
    // 希尔
    for (int i = arr.length / 2; i > 0; i /= 2) {
        // i用于控制步长,每次循环都是走i个步长
        for (int j = i; j < arr.length; j++) {
            for (int k = j; k > 0 && k - i >= 0; k -= i) {
                if (arr[k-i] > arr[k]) {
                    int tmp = arr[k];
                    arr[k] = arr[k-i];
                    arr[k-i] = tmp;
                }else break;
            }
        }
        // i用于表示步长,j、k用于插入排序
    }
}

(五)归并排序

        归并排序,先分组,直到不能再分即可,也就是l>=r时,退出递归。如果不满足这个条件,那么就继续递归,递归拆分,拆分返回的数组,是将数组按照中点各自排好序的数组,我们只需要将这两个数组进行排序即可。

public void mergeSort(int[] arr, int l, int r){
    if (l >= r) return;
    // 继续拆分
    int m = l + (r - l) / 2;
    mergeSort(arr, l, m);
    mergeSort(arr, m+1, r);
 
    // 递归结束之后,arr数组中以m为分界点,左右两边都是顺序都,
    // 所以需要进行这两部分合并排序
    // 定义一个数组用于存放临时数据
    int[] tmp = new int[r-l+1];
    int t = 0;
    int i = l;
    int j = m + 1;
 
    System.out.print("l = " + l + ",r = " + r + ": ");
    while (i <= m && j <= r) {
        if (arr[j] <= arr[i]) {
            tmp[t++] = arr[j++];
        }else {
            tmp[t++] = arr[i++];
        }
    }
 
    while (i <= m) {
        tmp[t++] = arr[i++];
    }
 
    while (j <= r) {
        tmp[t++] = arr[j++];
    }
 
    // 降临时数组都元素复制到原始数组中去
    t = 0;
    for (int k = l; k <= r; k++) {
        arr[k] = tmp[t++];
    }
 
    for (int k : arr) {
        System.out.print(k);
    }
    System.out.println();
}

(六)快速排序

        快速排序就是认定一个中点,然后遍历其他的数,大于等于中点的数就放在右边,小于等于中点的数就放到左边,最后再将整个分界点放到分界的位置即可,然后再进行递归的排序。

        注意一点,如果分界点选为数组中第一个元素,按照上述方法进行排序,也就是从小到大,需要先移动右指针再移动左指针,因为如果左右指针不满足l<r时,也就是l=r时,此时l指向的仍然是小于分界点的数,所以直接让分界点与左指针交换即可。否则当l=r时,i指向的可能是大于分界点的数,如果交换的话,就破坏了顺序。

public void quickSort(int[] arr, int l, int r){
    // 如果指针在同一位置则直接返回
    if (r - l < 1) return;
    // 将第一个数看作是分界点
    int tmp = arr[l];
    int i = l;
    int j = r;
    // 开始左右指针进行遍历[l+1,r]
    while (i < j) {
        // 要先遍历右指针,当右指针遍历到i停止时,i指向到也是小于tmp到位置,
        // 否则i停止时可能等于j,造成大于tmp
        // 遍历右指针,查找到第一个小于tmp的数的下标
        while (i < j && arr[j] > tmp) j--;
        // 遍历左指针,查找到第一个大于tmp的数的下标
        while (i < j && arr[i] <= tmp) i++;
 
        // 进行交换
        if (i < j) {
            int t = arr[j];
            arr[j] = arr[i];
            arr[i] = t;
        }
    }
    // 将终点进行交换(将tmp放到中间的位置上,左边的都小于等于tmp,右边的都大于tmp)
    arr[l] = arr[i];
    arr[i] = tmp;
    quickSort(arr,l,i-1);    //对tmp左边的数字进行排序
    quickSort(arr,i+1,r);    //对tmp右边的数字进行排序
 
}

 (七)堆排序

        大顶堆满足:arr[i] >= arr[2*i] && arr[i] >= arr[2*i+1]

        先将数组排序成大顶堆,然后将堆顶的元素与最后一个元素交换,然后将剩余的元素再次排序成大顶堆重复以上,就排序成升序的数组了。

 

 

/**
 * 堆排序
 */
public static void heapSort(int[] arr) {
    // arr.length/2-1开始
    for (int i = arr.length / 2 - 1; i >= 0; i--) {
        adjustHeap(arr, i, arr.length);
    }
 
    // 经历过刚刚的调整,现在数组已经是一个大顶堆,
    // 也就是满足arr[i] >= arr[2*i] && arr[i] >= arr[2*i+1]
    // 所以第一个元素就是最大值
    for (int j = arr.length - 1; j > 0; j--) {
        // 将最大值与最后一个元素调换,将最大值放到最后
        int temp = arr[j];
        arr[j] = arr[0];
        arr[0] = temp;
        // 调整0-j进行大顶堆重置
        adjustHeap(arr, 0, j);
    }
}
 
/**
 * 构建大顶堆
 * 注意:
 * 这个方法并不是将整个树调整成大顶堆
 * 而是以i对应的非叶子结点的子树调整成大顶堆
 *
 * @param arr    待调整的数组
 * @param i      非叶子结点在数组中的索引(下标)
 * @param length 进行调整的元素的个数,length是在逐渐减少的
 */
public static void adjustHeap(int[] arr, int i, int length) {
    // 取出当前非叶子结点的值保到临时变量中
    int temp = arr[i];
 
    // j=i*2+1表示的是i结点的左子结点
    for (int j = i * 2 + 1; j < length; j = j * 2 + 1) {
        // 比较左右子节点,找到较大的那个
        // 左子结点小于右子结点
        if (j + 1 < length && arr[j] < arr[j + 1]) {
            // j指向右子结点
            j++;
        }
 
        // 较大的子节点与父节点进行比较,如果子节点大于父节点
        if (arr[j] > temp) {
            // 把较大的值赋值给父节点
            arr[i] = arr[j];
            // arr[j] = temp; 这里没必要换,让i指向与其换位的子结点,在最后赋值即可
            i = j; //
        } else {
            // 子树已经是大顶堆了
            break;
        }
    }
    arr[i] = temp;
}

(八)计数排序

private static int[] countingSort(int[] arr) {
    if (arr == null || arr.length == 0) return new int[0];
 
    // 找到最大值和最小值,用于创建计数数组
    int min = arr[0];
    int max = arr[0];
    for (int i = 1; i < arr.length; i++) {
        if (arr[i] > max) max = arr[i];
        if (arr[i] < min) min = arr[i];
    }
 
    // 创建计数数组并记录每个数字出现的次数
    int[] count = new int[max - min + 1];
    for (int i = 0; i < arr.length; i++) {
        // 数字arr[i]对应到count数组中到下标应该是arr[i]-min
        count[arr[i]-min]++;
    }
 
    // 计数数组改造成之前所有数字个数的和,这样改造是因为可以更好的确定下标
    // 比如0、1、2出现的次数分别是0、2、1,那么改造后的count数组就是0,2,3
    // 那么当排序的时候,遍历到arr[i] 就可以确定其在排序好的数组中的下标为count[arr[i]-min]-1
    // 比如上面的例子112对应的下标就是012
    for (int i = 1; i < count.length; i++) {
        count[i] = count[i] + count[i-1];
    }
 
    // 创建输出数组,用于保存排序好的数组
    int[] output = new int[arr.length];
    for (int i = 0; i < arr.length; i++) {
        // 遍历到arr[i],这个数在count数组中的下标是arr[i]-min
        // 其对应到输出数组的下标就是count[arr[i]-min]-1
        output[count[arr[i]-min]-1] = arr[i];
        // 需要将count数组中对应的值-1,这样就能保证下一次遍历到arr[i]时,可以添加到这个数的前面了
        count[arr[i]-min]--;
    }
    return output;
}

(九)基数排序

        将个、十、百等位数逐个进行排序。

public static int[] sort(int[] array) {
    if (array.length < 2) return array;
    // 找到数组中的最大值
    int max = array[0];
    for (int temp : array) {
        if (temp > max) {
            max = temp;
        }
    }
 
    // 算出位数digit
    int maxDigit = 0;
    while (max != 0) {
        max /= 10;
        maxDigit++;
    }
 
    // 创建桶并初始化
    ArrayList<ArrayList<Integer>> bucket = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        bucket.add(new ArrayList<>());
    }
    // 按照从右往左的顺序,依次将每一位都当做一次关键字,
    // 然后按照该关键字对数组排序,每一轮排序都基于上轮排序后的结果
    int mold = 10;  // 取模运算
    int div = 1;    // 获取对应位数的值
    for (int i = 0; i < maxDigit; i++, mold *= 10, div *= 10) {
        for (int j = 0; j < array.length; j++) {
            // 获取个位/十位/百位......
            int num = (array[j] % mold) / div;
            // 把数据放入到对应的桶里
            bucket.get(num).add(array[j]);
        }
        // 把桶中的数据重新写回去,并把桶的元素清空,开始第二轮排序
        int index = 0;
        for (int k = 0; k < bucket.size(); k++) {
            // 桶中对应的数据
            ArrayList<Integer> list = bucket.get(k);
            for (int m = 0; m < list.size(); m++) {
                array[index++] = list.get(m);
            }
            // 清除桶
            bucket.get(k).clear();
        }
    }
    return array;
}

(十)桶排序

public static int[] bucketSort(int[] arr){
    if (arr == null) return new int[0];
    if (arr.length <= 1) return arr;
    int min = arr[0];
    int max = arr[0];
    for (int i = 1; i < arr.length; i++) {
        if (arr[i] > max) max = arr[i];
        if (arr[i] < min) min = arr[i];
    }
 
    int[] bucket = new int[max - min + 1];
    for (int i = 0; i < arr.length; i++) {
        bucket[arr[i]-min]++;
    }
 
    int[] output = new int[arr.length];
    int index = 0;
    for (int i = 0; i < bucket.length; i++) {
        for (int j = 0; j < bucket[i]; j++) {
            output[index++] = i + min;
        }
    }
    return output;
}


 转载自:Java常见的排序_java常见排序_Hawyer-的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值