二分搜索、快排、堆排序、归并排序

二分搜索

对于一个有序数组,采用二分搜索的方式,可将时间复杂度由O(n) 降为 O(logn)。
二分搜索的思想即,每次取数组中间位置的值与目标进行比较,根据比较结果可排除当前搜索范围的一半。
二分搜索的难点在于边界的判断,规定搜索区间即可解决这一问题,常选择的区间为左闭右开。

public int searchInsert(int[] nums, int target) {
        // 指针初始化,这里体现左闭右开[0, n)
        int l = 0;  // 左指针
        int r = nums.length;  // 右指针 
        while (l < r){
            int mid = l + (r - l) / 2; // 若采用(r + l) / 2 会有超出int最大值的风险
            if (nums[mid] > target){
                r = mid; // 右指针等于mid。(体现右开原则)
            }else if (nums[mid] < target){
                l = mid + 1; // 左指针等于mid + 1。 (体现左闭原则)
            }else {
                return mid;
            }
        }
        return l;
    }

快排

快排相较于选择排序和冒泡排序,可将时间复杂度由O(n2) 降为O(nlogn)。
快排的思想为:以左边为基准,定义左右指针(左闭右闭),左指针向右移动直至遇到大于基准值,右指针向左移动直至小于基准值。随后两者交换,直至两个指针相等。然后与基准值交换,这样就能保证交换后的基准值,左边都小于基准值,右边都大于基准值。最后利用递归,将左半部分和右半部分进行上述操作。
需要注意的是,左指针与基准相等,以左边为基准,左边的右边先向左移。

public void quickSort(int[] nums, int l, int r) {
        if (l >= r) {
            return;
        }
        int i = l;
        int j = r;
        while (j > i) {
            // 找到比第一位小的值, 所以这里符号为 >=
            while (nums[j] >= nums[l] && j > i) { j--; };
            // 找到比第一位大的值, 所以这里符号为 <=
            while (nums[i] <= nums[l] && j > i) { i++; };
            // 两者交换
            int temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }
        // 第一位进行交换
        int temp = nums[l];
        nums[l] = nums[i];
        nums[i] = temp;
        // 前半段递归
        quickSort(nums, l, i - 1);
        // 后半段递归
        quickSort(nums, i + 1, r);
    }

堆排序

堆的概念是由二叉树引出了的,该二叉树的特点为根节点大于子节点(大顶堆)。
大顶堆每次可取出最大值,随后通过取两个子节点进行下沉操作,以维持大顶堆的结构。
添加元素时,将添加元素与根元素进行比较,进行上浮操作,以维持大顶堆的结构。
时间复杂度也为O(nlogn),为不稳定排序,极端情况下退化为O(n2),java中Arrays.sort(),采用的排序方式即为堆排序。

public class MaxHeap<E extends Comparable<E>> {

    public static void main(String[] args) {
        MaxHeap<Integer> maxHeap = new MaxHeap<>();
        Random rand = new Random();
        for (int i = 0; i < 10; i++) {
            maxHeap.add(rand.nextInt(10));
        }
        while (!maxHeap.isEmpty()) {
            System.out.println(maxHeap.poll());
        }
    }

    private List<E> list;

    // 无参构造
    public MaxHeap() {
        list = new ArrayList<>();
    }

    // 有参构造
    public MaxHeap(int capacity) {
        list = new ArrayList<>(capacity);
    }

    // 返回堆的容量
    public int getSize() {
        return list.size();
    }

    // 判断堆是否为空
    public boolean isEmpty() {
        return list.isEmpty();
    }

    // 获取左子节点元素
    public int getLeft(int index) {
        return index * 2 + 1;
    }

    // 获取右子节点元素
    public int getRight(int index) {
        return index * 2 + 2;
    }

    // 获取父节点元素
    public int getParent(int index) {
        if (index == 0) {
            throw new IllegalArgumentException("0元素不包含父亲节点!");
        }
        return (index - 1) / 2;
    }

    // 添加元素
    public void add(E e) {
        list.add(e);
        siftUp(list.size() - 1);
    }

    // 上浮操作
    public void siftUp(int index) {
        if (index == 0) {
            return;
        }
        int parentIndex = getParent(index);
        E e = list.get(index);
        E parent = list.get(parentIndex);
        if (e.compareTo(parent) > 0) {
            swap(index, parentIndex);
            siftUp(parentIndex);
        }
    }

    // 获取最大值
    public E getMax() {
        if (list.isEmpty()) {
            throw new IllegalArgumentException("堆为空!");
        }
        return list.get(0);
    }

    // 取出最大值
    public E poll() {
        E ret = getMax();
        // 将最大值与末尾交换
        swap(0, list.size() - 1);
        // 删除最大值
        list.remove(list.size() - 1);
        // 下沉操作
        siftdown(0);
        return ret;
    }

    // 下沉操作
    public void siftdown(int index) {
        if (getLeft(index) >= list.size()) {
            // 如果是叶子节点,不用下沉操作。
            return;
        }
        // list.get(maxIndex) 是左右节点的最大值。
        int maxIndex = getLeft(index);
        if (maxIndex + 1 < list.size() && list.get(maxIndex).compareTo(list.get(maxIndex + 1)) < 0) {
            maxIndex++;
        }
        if (list.get(index).compareTo(list.get(maxIndex)) < 0) {
            // 与子节点交换
            swap(index, maxIndex);
            siftdown(maxIndex);
        }

    }

    // 交换操作
    public void swap(int i, int j) {
        E temp = list.get(i);
        list.set(i, list.get(j));
        list.set(j, temp);
    }


}

归并排序

归并排序的思想是分治,我们可以把长度为n的数组,平分成两个长度为n / 2的数组,并将平分之后的数组分别排序,最后合并这两个大小为n /2 的有序数组。
归并排序的时间复杂度为O(nlogn),为稳定排序。

public class MergeSort {

    public static void main(String[] args) {
        int[] nums = {1,4,3,7,8,2,0,9,5,6};
        MergeSort mergeSort = new MergeSort();
        mergeSort.sort(nums, 0, nums.length - 1);
        System.out.println(Arrays.toString(nums));
    }

    public void sort(int[] nums, int head, int tail) {
        if (head < tail) {
            int mid = head + (tail - head) / 2;
            sort(nums, head, mid);
            sort(nums, mid + 1, tail);
            merge(nums, head, mid, tail);
        }
    }

    public void merge(int[] nums, int head, int mid, int tail) {
        int p = head;
        int q = mid + 1;
        // 申请临时空间存放
        int[] temp = new int[tail - head + 1];
        int k = 0;
        while (p <= mid && q <= tail) {
            if (nums[p] > nums[q]) {
                temp[k++] = nums[p++];
            }else {
                temp[k++] = nums[q++];
            }
        }

        if (p > mid) {
            while (q <= tail) {
                temp[k++] = nums[q++];
            }
        }else {
            while (p <= mid) {
                temp[k++] = nums[p++];
            }
        }
        k--;
        for (int i = tail; i >= head; i--) {
            nums[i] = temp[k--];
        }
    }


}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值