手撕十大排序算法②——思路讲解

本文详细解析了四种经典的排序算法:快速排序、堆排序、计数排序和桶排序。快速排序通过选取基准值并递归划分数组实现高效排序;堆排序利用大根堆的性质,通过调整和重建堆进行排序;计数排序通过统计每个元素出现次数直接确定其位置;桶排序则将元素分布到不同桶中,每个桶单独排序后再合并。这些算法各有优缺点,适用于不同场景。
摘要由CSDN通过智能技术生成

快速排序

我这里只讲解单轴快排,优化版则是双轴快排,思想是一样的。

class Quick {
	// 单轴快排
    public int[] signalSort(int[] sourceArray) throws Exception {
        int length = sourceArray.length;
        int[] arr = Arrays.copyOf(sourceArray, length);

        return quickSort(arr, 0, length - 1);
    }

    private int[] quickSort(int[] arr, int left, int right) {
        if (left < right) {
            int partitionIndex = partition(arr, left, right);
            quickSort(arr, left, partitionIndex - 1);
            quickSort(arr, partitionIndex + 1, right);
        }
        return arr;
    }

    private int partition(int[] arr, int left, int right) {
        // 设定基准值
        int pivot = left;
        int index = pivot + 1;
        for (int i = index; i <= right; i ++) {
            if (arr[i] < arr[pivot]) {
                swap(arr, i, index);
                index ++;
            }
        }
        swap(arr, pivot, index - 1);
        return index - 1;
    }

    private void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

在这里插入图片描述

  • 对应到代码则是:
  • 随便找个值当基准值,一般都是第一个数
  • 之后遍历整个数组,小于基准值的紧挨着基准值放,然后是将这些小的中的最后一个跟基准值互换位置
  • 这样基准值左边都是小于基准值的,右边都是大于基准值的
  • 看下代码:
		if (left < right) {
            int partitionIndex = partition(arr, left, right);
            quickSort(arr, left, partitionIndex - 1);
            quickSort(arr, partitionIndex + 1, right);
        }
  • 会发现这个基准值的位置固定住了,因为他一定在这个位置
  • 为什么呢?
    • 因为它左边的比他小,右边的比他大
  • 然后就是同理
  • 递归的是两部分,左边和右边,很好理解
  • 一直到一部分只有一个值为止
  • 一定不要陷到代码里。

堆排序

class Heap {
	public int[] sort(int[] sourceArray) throws Exception {
        int length = sourceArray.length;
        int[] arr = Arrays.copyOf(sourceArray, length);
        buildMaxHeap(arr, length);
        for (int i = length - 1; i > 0; i --) {
            // 下面这三步挺巧妙的
            swap(arr, 0, i);
            length --;
            heapify(arr, 0, length);
        }
        return arr;
    }
    private void buildMaxHeap(int[] arr, int len) {
        // 这里的关键在于可以从中间开始是没有问题的
        for (int i = (int) Math.floor(len / 2); i >= 0; i --) {
            heapify(arr, i, len);
        }
    }
    private void heapify(int[] arr, int i, int len) {
        // 构造树的关键在这里
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int largest = i;

        if (left < len && arr[left] > arr[largest]) {
            largest = left;
        }
        if (right < len && arr[right] > arr[largest]) {
            largest = right;
        }
        if (largest != i) {
            swap(arr, i, largest);
            // 这步的作用在于对下面的树进行重新排列,因为有可能上面的调动导致之前排列好的树现在不符合题意了。
            heapify(arr, largest, len);
        }
    }
    private void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这三张图不错,能比较清楚的表现出堆排序的思想内核

  • 首先就是构造大根堆
  • 通过如下两个逻辑即可把数组存储数据转为完全二叉树的形式
		int left = 2 * i + 1;
        int right = 2 * i + 2;
        int largest = i;
  • 这样接下来就是比大小,排列成大根堆了
  • for (int i = (int) Math.floor(len / 2); i >= 0; i --)
  • 从上面代码开始遍历即可。
  • 需要注意的是:他也是从较下层开始往上的,所以如果在上层发现位置交换,那么又可能交换之后,下层不符合大根堆要求了,于是需要对下层进行大根堆构建,如下:
if (largest != i) {
    swap(arr, i, largest);
 // 这步的作用在于对下面的树进行重新排列,因为有可能上面的调动导致之前排列好的树现在不符合题意了。
	heapify(arr, largest, len);
}
  • 当大根堆构建好了之后,就可以开始取值操作了
  • 挺巧妙地
for (int i = length - 1; i > 0; i --) {
    // 下面这三步挺巧妙的
    swap(arr, 0, i);
    length --;
    heapify(arr, 0, length);
}
  • 因为是大根堆
  • 所以arr[0]是最大的
  • 让他跟arr[len - 1]互换
    • 这样数组最后一个就是最大值了
    • 再把数组长度减一
    • 然后重新构建大根堆
    • 同理,以此类推即可

计数排序

优化点在于:

  • 找到最小值
class Counting {
	public int[] sort(int[] sourceArray) throws Exception {
        int len = sourceArray.length;
        int[] arr = Arrays.copyOf(sourceArray, len);
        int maxValue = getMaxValue(arr);

        return countingSort(arr, maxValue);
    }
    private int[] countingSort(int[] arr, int maxValue) {
        int bucketLen = maxValue + 1;
        int[] bucket = new int[bucketLen];

        for (int value : arr) {
            bucket[value] ++;
        }
        int sortedIndex = 0;
        for (int j = 0; j < bucketLen; j ++) {
            while (bucket[j] > 0) {
                arr[sortedIndex++] = j;
                bucket[j] --;
            }
        }
        return arr;
    }
    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value)
                maxValue = value;
        }
        return maxValue;
    }
}
  • 思路比较简单,就不加图了
  • 上面代码逻辑为:
    • 找出最大值,生成max + 1 长的数组
    • 然后遍历原数组,每遍历一个,都可以在对应的新数组中对应的下标的值+ 1
    • 最后遍历新数组,按值的个数排列即可
  • 优化点:
    • 找到最小值,缩短生成数组长度
    • 如果有负数,可以将生成数组长度加上负数最大值即可

桶排序

桶排序可以说是计数排序的进阶版

class Bucket {
	private static final Inserter inserter = new Inserter();

    public int[] sort(int[] sourceArray) throws Exception {
        int length = sourceArray.length;
        int[] arr = Arrays.copyOf(sourceArray, length);
        return bucketSort(arr, 5);
    }

    private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
        if (arr.length == 0) {
            return arr;
        }
        int minValue = arr[0];
        int maxValue = arr[0];
        for (int value : arr) {
            if (value < minValue)
                minValue = value;
            else if (value > maxValue)
                maxValue = value;
        }

        int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
        int[][] buckets = new int[bucketCount][0];

        // 利用映射函数将数据分配到各个桶里
        for (int i = 0; i < arr.length; i ++) {
            int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
            buckets[index] = arrAppend(buckets[index], arr[i]);
        }
        int arrIndex = 0;
        for (int[] bucket : buckets) {
            if (bucket.length <= 0)
                continue;
            // 对每个桶进行排序,这里使用了插入排序
            bucket = inserter.insert(bucket);
            for (int value : bucket)
                arr[arrIndex++] = value;
        }
        return arr;
    }

    // 自动扩容,并保存数据
    private int[] arrAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }
}
  • 这种思路也挺简单,就不找图了
  • 思路如下:
    • 先确定最大值,最小值,确定数组最大差值
    • 最大插值假设是200,我规定一个桶能装5个数(可能装超,不要紧),那么一共会有40 + 1个桶
    • 通过这个映射公式int index = (int) Math.floor((arr[i] - minValue) / bucketSize)将数值插入到对应的桶里面
    • 然后对每个桶单独进行排序
    • 排好序之后就遍历往数组中赋值就可以了。
  • 上述代码没有考虑负值,不过思路和计数排序处理负值一样

基数排序

这种排序方式比较古怪。

class Radix {
	public int[] sort(int[] sourceArray) throws Exception {
        int length = sourceArray.length;
        int[] arr = Arrays.copyOf(sourceArray, length);
        int maxDigit = getMaxDigit(arr);
        return radixSort(arr, maxDigit);
    }
    // 获取最高位数
    private int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);
        return getNumLength(maxValue);
    }
    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value)
                maxValue = value;
        }
        return maxValue;
    }
    protected int getNumLength(long num) {
        if (num == 0)
            return 1;
        int length = 0;
        for (long tmp = num; tmp != 0; tmp /= 10)
            length ++;
        return length;
    }
    private int[] radixSort(int[] arr, int maxDigit) {
        int mod = 10;
        int dev = 1;

        for (int i = 0; i < maxDigit; i ++, dev *= 10, mod *= 10) {
            // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
            int[][] counter = new int[mod * 2][0];

            for (int j = 0; j < arr.length; j ++) {
                int bucket = ((arr[j] % mod) / dev) + mod;
                counter[bucket] = arrayAppend(counter[bucket], arr[j]);
            }

            int pos = 0;
            for (int[] bucket : counter) {
                for (int value : bucket)
                    arr[pos++] = value;
            }
        }
        return arr;
    }
    // 自动扩容,并保存数据
    private int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }
}

在这里插入图片描述

  • 计数排序的逻辑理解也不是太难
  • 先得到最大数字的长度
  • 这个数多大就遍历多少遍
  • 每次存好之后再依次取出放回原数组中(这一步的原因是出于顺序的考虑)
    • 上面代码的算法处理这个过程有点呆,完全没必要多大数就多大数组,这很傻
    • 只用20个长度即可,不用随着数字位数的变大,数组长度跟着变大
    • 感兴趣的可以对上面代码做做优化
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值