力扣算法前置知识——复杂度+简单排序算法(1)

本文探讨了如何利用异或交换和插入排序实现时间复杂度为O(n)且空间复杂度为O(1)的奇数查找算法,包括选择排序的时间复杂度分析、异或操作在交换中的优势与限制,以及插入排序的原理和最差情况下的复杂度。还介绍了二分法在查找和定位局部最小值的应用,以及对数器在比较中的角色。
摘要由CSDN通过智能技术生成

复杂度+简单排序算法

传送门

时间复杂度

可以用选择排序进行举例,选择排序就是每次遍历确定最小值,将最小值和每次遍历的第一个数交换位置,假设N个数

第一次比,遍历N个数,比较N次,交换1次 (此时第1个数已经确定,不必再动)

第二次比,遍历N-1个数,比较N-1次,交换1次(此时第2个数已经确定,不必再动)

以此往复,总时间

  • 遍历 N + N-1 + N-2 + …
  • 比较 N + N-1 + N-2 + …
  • 比较 1 + 1 + 1 + … = N

利用通式表示:a * (N^2) + b * N + c ,因为前两个都是等差数列,等差数列求和必有二次一次常数项,所以将三式整合形成上式,在看时间复杂度的时候只看最高次数,而且舍弃最高次数的系数,因此选择排序的时间复杂度就是 N^2,表示为 O(N^2)

空间复杂度

还是用选择排序举例:

public static void selectionSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    for (int i = 0; i < arr.length - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < arr.length; j++) {
            minIndex = arr[minIndex] < arr[j] ? minIndex : j;
        }
        swap(arr, i, minIndex);
    }

}

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

代码中需要开辟空间的量就是:i、j、minIndex、tmp,并且每次循环都会释放空间进行重新开辟,所以每次都是四个有限变量的空间,所以空间复杂度为O(1)

异或交换两数

异或运算的知识就是相同为0,不同为1,但实际上可以理解为无进位相加

  • 第一个数:1 0 1 1 0
  • 第二个数:0 0 1 0 1
  • 异或结果:1 0 0 1 1

异或的性质:

  • 性质1:0 异或任何数还是等于任何数
  • 性质2:自己和自己异或结果为0
  • 性质3:异或满足交换律、结合律,a和b的异或等于b和a的异或,abc = a(bc)

交换两个数采用异或手段:

public static void swap(int[] arr, int i, int j) {
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

如何理解上述代码用异或交换两个数?我们假设有这样两个数:a=甲 b=乙,要去交换a和b的值

  • 执行a = a ^ b ,现在 a = 甲 ^ 乙,b = 乙
  • 执行b = a ^ b ,现在 a = 甲 ^ 乙,b = 甲 ^ 乙 ^ 乙 = 甲 ^ 0 = 甲 (利用结合律和性质1)
  • 执行a = a ^ b ,现在 a = 甲 ^ 甲 ^ 乙 = 0 ^乙 = 乙,b = 甲

最后结果a=乙 b=甲,完成交换

  • 好处:不用另外开辟一个新的空间变量tmp
  • 坏处:要保证两个数的内存地址不是指向同一处,同一处地址会将值变成0

题目:

1)一组数组,一种数出现奇数个,其余数都是偶数个,找出奇数值,要求时间复杂度O(n),空间复杂度O(1)

解答:声明一个变量,用这个变量遍历异或数组每一个数,最后结果就是那个奇数

2)一组数组,两种数(两数不相等)出现奇数个,其余数都是偶数个,找出奇数值,要求时间复杂度O(n),空间复杂度O(1)

解答:声明变量eor、eor‘,设两个为奇数个的数为a、b

  • eor遍历异或数组每一个数,得到结果必为 a^b
  • a和b不相等,势必导致eor至少有一位为1,则可以按照这一位对数组全部数分类,势必导致a和b不在一类
  • eor’遍历异或该位为1的类别的数,得到结果必为a或者b
  • eor和eor‘异常得到另外一个数,这样a和b就都找出
public static void printOddTimeNum2(int[] arr) {
    int eor = 0;
    for (int i = 0; i < arr.length; i++) {
        eor ^= arr[i];
    }

    int rightOne = eor & (~eor + 1); //取出最右边的1

    int onlyOne = 0; //eor'
    for (int i = 0; i < arr.length; i++) {
        if ((arr[i] & rightOne) == 0) {
            onlyOne ^= arr[i];
        }
    }
    System.out.println(onlyOne + " " + (eor ^ onlyOne)); //找出两个数
}

位运算是要比算术运算快的多

插入排序

排序流程:

第一次排序:0位置和0位置比有序,0有序

第二次排序:1位置和0位置比,若0位置大于1位置数,交换位置,0-1有序

第三次排序:2位置和1位置比,若1位置大于2位置数,交换位置;1位置和0位置比,若0位置大于1位置数,交换位置,到前面没有可比较的数停止,0-2有序

循环往复

public static void insertionSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }

    for (int i = 0; i < arr.length; i++) {
        for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
            swap(arr, j, j + 1);
        }
    }
}

public static void swap(int[] arr, int i, int j) {
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

那明显有时候数据好坏完美影响排序的效率,最差的情况就是完全倒序的,最好的情况只用比较一次,此时时间复杂度如何计算?

  • 需要考虑最差情况下的时间复杂度,也就是插入排序的时间复杂度就是O(N^2)

二分法

有序数组找寻某个数是否存在,可采用二分法

查找流程:

第一次查找:先找到中间数a,和要查找的数比较,看大于还是小于,如果一样那直接找到了,若大于,在大于的那一半内寻找

第二次查找:在大于这一半再找中间数进行比较,看大于还是小于,如果一样那直接找到了,若大于,在大于的那一半内寻找

循环往复,假设原来16个,最差的情况一直切一半查找:8、4、2、1,则此时的时间复杂度就是O(log₂N),默认将2省略,就是O(logN)

补充:

  • 有序数组,找出≥某个数最左侧的位置

    • 利用二分,位置就是被二分的数用一个变量记录,要找最左侧,就看左边二分有没有满足≥该数这个条件,满足,那这个位置记录变量就要随之改变,不满足不变
  • 局部最小值问题:数组无序,但是任何两个相邻数不相等,找出一个局部最小值(分三种情况:为0位置,需要比1位置值小;为n-1位置,需要比n-2位置值小;为中间位置i,需要比i-1,i+1位置值小)要求时间复杂度小于O(N)

    • 采用二分,先去查验0位置和N-1位置,如何二者其一有一满足上述最小值情况直接返回,如果都不满足,比是下图情况,中间存在局部最小

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CGHIv6Mj-1649583428002)(../ZNV/%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87/%E5%8A%9B%E6%89%A3%E7%AE%97%E6%B3%95/image-20220410170504355.png)]

    • 找到中值点看其是否满足上述最小值情况,不满足,必定有一边是上图情况,继续二分,循环往复找到局部最小值

对数器

比较两种方法结果是否相同,机器比较,防止出现异常情况

/**
 * 对数器
 */
public class code05 {

    public static void insertionSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        for (int i = 0; i < arr.length; i++) {
            for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
                swap(arr, j, j + 1);
            }
        }
    }

    public static void swap(int[] arr, int i, int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }

    public static void comparator(int[] arr) {
        Arrays.sort(arr);
    }

    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        System.arraycopy(arr, 0, res, 0, arr.length);
        return res;
    }

    public static boolean isEqual(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
            return false;
        }
        if (arr1 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int value : arr) {
            System.out.print(value + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int testTime = 1000;
        int maxSize = 100;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            insertionSort(arr1);
            comparator(arr2);
            if (!isEqual(arr1, arr2)) {
                succeed = false;
                break;
            }
        }
        System.out.println(succeed ? "good" : "bad");

        int[] arr = generateRandomArray(maxSize, maxValue);
        printArray(arr);
        insertionSort(arr);
        printArray(arr);
    }
    
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

友培

数据皆开源!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值