归并&快排&二分查找

归并排序

归并排序算法的核心思想是,先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并到一起,这样整个数组就是有序的了

mergeSort(p…r) = merge(mergeSort(p…q), mergeSort(q+1…r)) 终止条件:p >= r 不能再继续分解

merge数组A, B 部分的过程中,先申请一个临时数组 help ,用两个游标 q1, q2 分别指向A和B的第一个元素,比较A[q1] 和 B[q2], 如果A[q1] < B[q2], 就把A[q1]存入 help 数组, q1++, 否则将 B[q2] 存入 help, q2++

当遇到两个子数组其中一个全部存入 help 中时, 把另一个数组的中的数据依次放到 help 的末尾.这时临时数组中的数据就是有序状态, 之后再把 help 复制到原数组中.

性能分析: 归并排序是一个稳定的排序算法(由 merge 中的比较方式实现), 执行效率与原始数据的有序度无关, 时间复杂度是 O(nlogn), 由于要创建临时数组, 故空间复杂度 O(n)

public static void mergeSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    mergeSort(arr, 0, arr.length - 1);
}

public static void mergeSort(int[] arr, int l, int r) {
    if (l == r) {
        return;
    }
    int mid = l + ((r-l) >> 1);
    mergeSort(arr, l, mid);
    mergeSort(arr, mid + 1, r);
    merge(arr, l, mid, r);

}

public static void merge(int[] arr, int l, int m, int r) {
    int[] help = new int[r - l + 1];
    int i = 0;  //临时数组指针
    int q1 = l;  //浮动指针
    int q2 = m + 1;
    while (q1 <= m && q2 <= r) {
        help[i++] = arr[q1] <= arr[q2] ? arr[q1++] : arr[q2++];
    }
    while (q1 <= m) {  //q1剩余
        help[i++] = arr[q1++];
    }
    while (q2 <= r) { //q2剩余
        help[i++] = arr[q2++];
    }
    //复制到原数组
    for (i = 0; i < help.length; i++) {
        arr[l++] = help[i];
    }
}

快速排序

原理:

如果要排数组中 l 到 r 之间的一组数据, 我们选择l 到 r 之间的任意一个元素作为 分区点[pivot], 将大于 pivot 的数据分到右边, 将小于 pivot 的数据分到左边, 将 pivot 分到中间. 再对左边, 右边分别递归分区, 直到区间缩小为1, 即l == r (递归终止条件).
partition 中通过游标 l 把数组分为两部分, 已处理区间和未处理区间, 其中 less --> l 为pivot

递推公式: quickSort(p…r) = quickSort(p…q-1) + quickSort(q + 1…r)

性能分析: 由于交换的时候会改变相同元素的相对位置, 所以快排是不稳定的排序算法, 通过分区函数 partition, 实现原地排序, 不占用大量内存, 这一点优于归并排序, 空间复杂度O(logn 指的是递归深度, 要用到栈空间)
最坏情况下, 数据已经有序, 则会造成分区不均等, 分 n 次区, 时间复杂度为O(n^2)
最好情况下, 正好平均分区, 为O(nlogn)
平均时间复杂度O(nlogn)

public static void quickSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int l, int r) {
    if (l < r) {
        int[] p = partition(arr, l, r);
        quickSort(arr, l, p[0] - 1);
        quickSort(arr, p[1] + 1, r);
    }
}

public static int[] partition(int[] arr, int l, int r) {
    int less = l - 1;  //左边界
    int more = r;  //右边界   //把最后一个元素arr[r]作为 pivot
    while (l < more) {
        if (arr[l] < arr[r]) {
            swap(arr, ++less, l++);
        } else if (arr[l] > arr[r]) {
            swap(arr, --more, l);
        } else {
            l++;
        }  //此时 less 和 l 之间是pivot
    }  //l == more
    swap(arr, more, r); //把比较对象放到中间
    return new int[] {less + 1, more};   //返回 pivot 的区间
}

过程图
在这里插入图片描述

快排的一个应用--如何在O(n)时间复杂度内求无序数组中第K大元素

例如:arr = {6, 1, 3, 5, 7, 2, 4, 9, 11, 8} //10个数

把最后一个数8作为pivot, 一遍快排后为:

11, 9, 8 , 7, 2, 4, 3, 1, 6, 5

数组分为3部分, arr[0…p-1], arr[p], arr[p+1…r]

此时看pivot左边有2个元素就可以知道pivot为第2+1大的元素

  • 如果p+1 == k, 则找到

  • 如果p+1 > k, 则表示第k大元素在左边,需要对[0, p-1]再次分区

  • 如果p+1 < k, 则表示第k大元素在右边,需要对[p+1, r]再次分区

代码:

public static void foundK(int[] arr, int k) {
    if (arr == null || arr.length < k || k == 0) {
        System.out.println("error");
        return;
    }
    Partition(arr, 0, arr.length - 1, k);
}
public static void Partition(int[] arr, int l, int r, int k) {
    int left = l;
    int less = l - 1;
    int more = r;
    while (l < more) {
        if (arr[l] > arr[r]) {  //因为是减序排序,换一下布尔运算符号
            swap(arr, ++less, l++);
        } else if (arr[l] < arr[r]) {
            swap(arr, --more, l);
        } else {
            l++;
        }
    }
    swap(arr, more, r);
    if ((less + 2) == k) {
        System.out.println("第" + k + "大元素是" + arr[less + 1]);
    } else if ((less + 2) < k) {
        //在右边, 对右边再次分区
        Partition(arr, less + 2, r, k);
        return;
    } else {
        //在左边,对左边再次分区
        Partition(arr, left, less, k);
        return;
    }
}
二分查找

依稀记得大三上学期, 上算法分析课的时候, 黄大爷跟我们说"你们这些人当中还有的人连二分查找都不会写的", 脑瓜一想, 我擦这不就说的我嘛哈哈哈哈

二分查找的时间复杂度是 O(logn), 查找的数组要求有序.

适用于顺序存储结构, 一经建立就很少改动, 而又经常需要查找的线性表.

(对那些查找少而又经常需要改动的线性表,可采用链表作存储结构)

public static int binarySearch(int[] arr, int key) {
    if (arr == null || arr.length < 1) return -1;

    int l = 0;
    int r = arr.length - 1;
    int m;
    while (l <= r) {
        m = l + ((r - l) >> 1);
        if (key == arr[m]) {
            return m;
        } else if (key < arr[m]) {
            r = m - 1;
        } else {
            l = m + 1;
        }
    }
    return -1;
}

二分查找的链表实现?

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值