排序算法(快排、归并、堆)

15 篇文章 0 订阅
1 篇文章 0 订阅

排序算法

重点掌握三个排序算法:

快速排序

思路:

假如给你一个数组{-9, 78,45, 23, -567, 89}

  1. 首先你先确定一个基数pivot (选取的规则一般是left + right >> 1),这里是优化,否则每次选左边或者右边在有序数组中的时间复杂度是O(n的平方)。

  2. 然后将选取的该基数放在最右边

  3. 然后运用双指针的操作,创建 指针i指针j,然后两个指针都是指向最左边的那个元素,然后指针j开始遍历,指针i不动

  4. 直到指针j遇到比基数小的数时,就跟指针i的指向的数进行互换,此时,j++i也需要++ 。以此反复。

  5. 直到指针j指向pivot时,再将pivot指针i的指向的数进行互换。从而结束第一次的排序。此时pivot这个数就到了它正确的位置了

  6. 这样以第一次的pivot为基点就切割为两个子数组。再对两个子数组分别重复进行 步骤1- 5的操作即可。

流程图如下:

img

代码如下:

private void quickSort(int[] nums, int left, int right) {
        //递归退出条件
        if (left >= right) {
            return;
        }
        int mid = left + (right - left) / 2;
        swap(nums, mid, right);
        // 基数
        int pivot = nums[right];
        int i = left,j = left;
        while (j <= right) {
            if (nums[j] <= pivot) {
                swap(nums, i, j);
                j++;
                i++;
            } else {
                j++;
            }
        }

        // 递归条件中 为什么是 i - 2 以及 i 呢? 因为当 j 指针到达了 pivot 元素的时候,这时候 i 和 j 同时加加,而且又是左闭右闭区间,对于右区间来说,左区间的终点肯定是 i - 2的,而对于右区间来说,此时的 i 就是起点
        quickSort(nums, left, i - 2);
        quickSort(nums, i, right);
    }

    public void swap(int[] nums, int left, int right) {
        int tmp = nums[left];
        nums[left] = nums[right];
        nums[right] = tmp;
    }
/*
时间复杂度:最坏O(n * n) 最优O(n * log n) 平均O(n * log n)
空间复杂度:O(1)
*/

归并排序

思路:归并排序用到了分治的思想。所谓分治,对于我个人的理解来说,就是将数组拆分为个体(这里体现的思想),然后进行操作,操作完了之后,就进行合并操作(这里体现的思想)。

思路

假如给你一个数组{-9, 78,45, 23, -567, 89}

  1. 利用二分拆解的方法将原始数组为两个等份(一份区间为[left , mid],一份为[mid+1 , right])
  2. 原先的数组的[left,mid]再等份划分之后,原来的mid其实就是现在的新数组的right了。一直划分,直到将全部的数组分为单个元素为止
  3. 然后就需要进行 了,这时就需要一个临时数组(大小:[right - left + 1])来装这些治后的元素,也同样从最底下的子数组开始进行合并。(这里看文字也许无法理解,可以画个图,每个数组都有自己的子数组,我说的最底下的子数组是区间为2的时候的子数组了。)
  4. 完成治后,将临时数组的元素再拷贝覆盖到原来的数组即可得出结果。

流程图如下:

img

代码如下:

class Solution {
    public static void main(String[] args) {
        int[] ints = new Solution().sortArray(new int[]{9, 25, 7, 678, 19, 14});
        System.out.println(Arrays.toString(ints));
    }

    public int[] sortArray(int[] nums) {
        recursion(nums, 0, nums.length - 1);
        return nums;
    }

    public void recursion(int[] nums, int i, int j) {
        if (i >= j) {
            return;
        }
        int mid = i + j >> 1;
        // 左闭右闭
        recursion(nums, i, mid);
        recursion(nums, mid + 1, j);
        merge(nums, i, mid, j);
    }

    public void merge(int[] nums1, int left, int mid, int right) {
        int[] res = new int[right - left + 1];
        int i = left, j = mid + 1, k = 0;
        while (i <= mid && j <= right) {
            // 即使相等也不改变这个顺序,稳定性
            if (nums1[i] <= nums1[j]) {
                res[k] = nums1[i];
                i++;
            } else if (nums1[i] > nums1[j]) {
                res[k] = nums1[j];
                j++;
            }
            k++;
        }
        while (i <= mid) {
            res[k++] = nums1[i];
            i++;
        }
        while (j <= right) {
            res[k++] = nums1[j];
            j++;
        }

        // 将res数组的数 转移在 nums1中
        for (int t = 0; t < res.length; t++) {
            nums1[t + left] = res[t];
        }
    }
}
/*
时间复杂度:最坏O(n * n) 最优O(n * log n) 平均O(n * log n)
空间复杂度:O(1)
*/

堆排序

思路:

其实堆排序是利用了大顶堆的思想,但不是真的构建一个二叉树,真实的数据是在数组中操作的。

假如给你一个数组{-9, 78,45, 23, -567, 89}

  1. 将数组的数构建成一个大顶堆(大顶堆就是每个父节点肯定比其左右孩子节点的值要大);
  2. 先从倒数第1个非叶子节点的节点算起,先比较 该节点(就是父节点)的两个子节点的大小,然后将大的那个子节点和父节点进行比较,如果是子节点比父节点大的话,就互换位置,如果小的话,就不动;
  3. 按照 2 的思路,直到根节点符合大顶堆的逻辑,然后将根节点的值将最后一个节点的值互换,即可完成一次排序;

流程图如下:

img

代码如下:

public int[] sortArray(int[] nums) {
        // 先对传入进来的数组进行堆的构建  []
        int len = nums.length;
        for (int i = (len - 1) / 2; i >= 0; i--) {
            heapAdjust(nums, i, len - 1);
        }
        // 逐步取掉第一个元素,然后不断调整堆
        int i = len - 1;
        while (i >= 0) {
            swap(nums, 0, i);
            // 相当于删除最后一个元素
            i--;
            heapAdjust(nums, 0, i);
        }
        return nums;
    }

    /**
     * 堆调整(按序调整)
     *
     * @param nums  数组
     * @param index 根节点(父节点)索引
     * @param len   堆操作的范围
     */
    public void heapAdjust(int[] nums, int index, int len) {
        int maxIndex;
        while (2 * index + 1 <= len) {
            int left = 2 * index + 1;
            if (left + 1 <= len && nums[left] < nums[left + 1]) {
                maxIndex = left + 1;
            } else {
                maxIndex = left;
            }

            if (nums[index] < nums[maxIndex]) {
                swap(nums, index, maxIndex);
                index = maxIndex;
            } else {
                break;
            }
        }
    }

    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
/*
时间复杂度:最坏O(n * n) 最优O(n * log n) 平均O(n * log n)
空间复杂度:O(1)
*/

这里附上一个大根堆的一个代码:

/**
 * @author: hero生仔
 * 大顶堆
 */
public class MaxHeap {
    /**
     * 堆的底层就是数组
     */
    private final List<Integer> list;

    public MaxHeap() {
        list = new ArrayList<>();
    }

    /**
     * 堆中添加元素
     */
    public void push(int val) {
        // 往集合中添加元素
        list.add(val);
        int len = list.size();
        // 如果集合中头节点的元素 > 集合中该头节点的子节点的元素
        while (list.get(len - 1) > list.get((len - 1) / 2)) {
            // 78 跟 -9 互换 比如数组:[-9,78,45,23,-567,89]
            swap(len - 1, (len - 1) / 2);
            // 将指向父节点的指针 移向 指向子节点
            len = (len - 1) / 2 + 1;
        }
    }

    /**
     * 堆中第一个最大的元素
     */
    public void top() {
        int len = list.size();
        if (len == 0) {
            System.out.println("堆为空~");
        } else {
            System.out.println("top的元素为:" + list.get(0));
        }
    }

    /**
     * 堆弹出元素
     */
    public void pop() {
        int len = list.size();
        if (len == 0) {
            System.out.println("堆为空~");
        } else {
            System.out.println("弹出来的元素为:" + list.get(0));
            // 每次弹出元素,都需要将堆顶元素与堆底元素进行互换
            list.set(0, list.get(len - 1));
            // 然后删除堆底元素(也就是原来的堆顶元素)
            list.remove(len - 1);
            // 此时需要再次调整
            heapAdjust(0, list.size() - 1);
        }
    }

    private void heapAdjust(int index, int len) {
        // 记录最大节点的元素的索引值
        int maxIndex;
        while (index * 2 + 1 <= len) {
            // 子节点的索引值
            int left = 2 * index + 1;
            // 比较两个子节点的值
            if (left + 1 <= len && list.get(left) < list.get(left + 1)) {
                maxIndex = left + 1;
            } else {
                maxIndex = left;
            }
            // 如果子节点此时大于根(父)节点
            if (list.get(index) < list.get(maxIndex)) {
                // 交换位置
                swap(index, maxIndex);
                // 且需要将索引从原来指向根(父)节点变到子节点
                index = maxIndex;
            } else {
                break;
            }
        }
    }

    /**
     * 交换元素
     */
    private void swap(int left, int right) {
        int tmp = list.get(left);
        list.set(left, list.get(right));
        list.set(right, tmp);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值