复杂度和简单排序算法_笔记01


本文知识点记录自左程云 算法与数据结构基础到高级教程 图片截取自视频中

常数操作

和数据量无关的操作

+ - * / 位运算 都是常数操作;
int a= arr[i]//常数操作
int b= list.get(i)    //非常数操作,list是一个链表
时间复杂度

常数操作的数量写成表达式,不要低阶项,除去最高项的系数剩下的。例如a*N²+b*N+c,写为O(N²)

对任何算法,估算其时间复杂度时,按照最差情况来估计,即耗费的时间最多是多少,取最大阶作为时间复杂度

当时间复杂度指标相同,需要比较不同算法流程常数项时间的时候,不需要再分析了,用具体的数据样本测试。因为虽然可以估算出常数操作数量,但是不同常数操作花费的时间不同,比较常数项时间就不能仅仅靠常数项的值的大小

加减法和位运算进行一次花费的时间是不同的

01_选择排序

时间复杂度:O(N^2)

//selection sort
static void selectionSort(int[] arr){
    for (int i = 0; i < arr.length-1; i++) {
        int min = arr[i];
        for (int j = i+1; j < arr.length; j++) {
            if(arr[j] < min){
                swap(arr[i],arr[j]);
            }
        }
    }
}
02_冒泡排序

时间复杂度:O(N^2)

//bubble sort
static void bubbleSort(int[] arr){
    for (int i = arr.length-1; i > 1; i--) {
        for (int j = 0; j < i; j++) {
            if(arr[j] > arr[j+1])
                swap(arr[j], arr[j+1]);
        }
    }
}
swap() (用^运算符)

异或运算^,按位 相异为1,相同为0,可以理解为无进位相加,满足交换律、结合律,即一堆数进行^操作,与其顺序无关

为什么^满足 结果与顺序无关的性质?

根据^的无进位相加来理解:

一批数,最终结果的每一位上是0还是1,与该位上有几个1相加有关。比如,若有N个数相^,某一位上,1出现了奇数次,结果值对应的该位上,值就为1;1出现偶数次,结果值对应的该位上,值为0。显然,结果与顺序无关。

若两个数存储在两个不同的内存区域中,则可以这样交换

int a=10,b=20;
void swap(int a, int b){    //若传入参数是数组元素,则两数下标不能相同
	a= a^b;
	b= a^b;
	a= a^b;
}
^的应用

剑指 Offer 56 - I. 数组中数字出现的次数

//在一个数组中,只有两个数出现了奇数次,其他数都出现了偶数次,返回这两个数
class Solution {
    //从n右边往左,找第一个值为1的位,在该处,a与b的值是不同的,根据这一位上的值可以把a和b从nums里面分到两个区域
    int GetRightOne(int n){
        return n^(n&(n-1));
    }
    public int[] singleNumbers(int[] nums) {
        int[] res = new int[2];
        res[0] = 0;
        res[1] = 0;
        int tmp = 0;
        for(int i = 0; i < nums.length; ++i){
            tmp ^= nums[i];    //temp=a^b(a b即是要返回的两数)
        }
        int rightone = GetRightOne(tmp);
        for(int i = 0; i < nums.length; ++i){
            if((nums[i] & rightone) == rightone){
                res[0] ^= nums[i];
            }else{
                res[1] ^= nums[i];
            }
        }
        return res;
    }
}
03_插入排序

时间复杂度:O(N^2)

//insertion sort, method b
static void insertionSort(int[] arr){
    for (int i = 1; i < arr.length; i++) {
        for (int k = i; k > 0 && (arr[k] < arr[k-1]); --k) {
            swap(arr[k], arr[k-1]);
        }
    }
}
二分

二分与数组有序无序无关,不是只有有序才能用二分

  • 在一个有序数组中,找某个数是否存在
O(logN) //时间复杂度,logN默认以2为底
//每次对半分,找比较中间的数和要找的数的大小关系,最坏的情况:分到不能再分才找到,即进行了logN次比较
  • 在一个有序数组中,找 >=某个数最左侧的位置
  • 局部最小值

给一个无序且互不相等的数组,求其局部最小值。局部最小值定义为:若a[0]<a[1],则0位置为局部最小;若a[n-2]>a[n-1],n-1位置局部最小;若a[i]<a[i-1] && a[i]<a[i+1] ,i位置为局部最小。

在这里插入图片描述

求L和R的中点(L和R是数组索引):

int mid = L + (R-L)>>1;

这样写不容易越界(当L和R非常大时,L+R可能溢出);

(R-L)>>1 比(R-L)/2 更快。

对数器
  • 有一个想测试的方法a(复杂度更好)
  • 实现复杂度不好,但是容易实现的方法b;
  • 实现一个随机样本产生器
  • 方法a和方法b跑相同的随机样本,看看结果是否一致
  • 若有一个样本使得比对结果不一致,打印样本进行人工干预,更改方法a或方法b
  • 当样本数量很多时,比对测试依然正确,可以确定方法a已经正确
为什么要有对数器?

各种OJ也是人工输入的测试用例,不排除你的算法有问题时,测试用例没有测出来(没包含进所有的情况),所以自己设计对数器,进行人工干预,这是最稳妥的测试方法,不会出现上面的错误。

对数器的实现
import java.util.Arrays;

public class Test {
    //交换两数
    static void swap(int a, int b){
        int tmp = a;
        a = b;
        b = tmp;
    }
    //若确定a和b不指向同一块内存,则swap可以这样实现
//    static void swap(int a, int b){
//        a = a ^ b;
//        b = a ^ b;
//        a = a ^ b;
//    }

    //打印数组
    static void print(int[] arr){
        for(int a : arr)
            System.out.print(a+" ");
    }

    //bubble sort
    static void bubbleSort(int[] arr){
        for (int i = arr.length-1; i > 1; i--) {
            for (int j = 0; j < i; j++) {
                if(arr[j] > arr[j+1])
                    swap(arr[j], arr[j+1]);
            }
        }
    }

    //selection sort, method a
    static void selectionSort(int[] arr){
        for (int i = 0; i < arr.length-1; i++) {
            int min = arr[i];
            for (int j = i+1; j < arr.length; j++) {
                if(arr[j] < min){
                    swap(arr[i],arr[j]);
                }
            }
        }
    }
    //insertion sort, method b
    static void insertionSort(int[] arr){
        for (int i = 1; i < arr.length; i++) {
            for (int k = i; k > 0 && (arr[k] < arr[k-1]); --k) {
                swap(arr[k], arr[k-1]);
            }
        }
    }

    //generate random array
    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+1)*Math.random());
        }
        return arr;
    }

    public static void main(String[] args) {
        //logarithmic detector
        int testTime = 500000;
        int maxSize = 100;
        int maxValue = 100;
        boolean sign = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = Arrays.copyOf(arr1,arr1.length);
            selectionSort(arr1);
            bubbleSort(arr2);
            if(!Arrays.equals(arr1,arr2)){
                sign = false;
                break;
            }
        }
        System.out.println(sign ? 666 : "gg");
        if((sign ? 666 : "gg") == "gg"){
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = Arrays.copyOf(arr1,arr1.length);
            print(arr1);

            selectionSort(arr1);
            print(arr1);

            bubbleSort(arr2);
            print(arr2);
        }
    }
}
递归行为和递归行为时间复杂度的估算

递归行为可以理解为多叉树的遍历,每一个节点要计算值,必须依赖当前节点的子节点。由于这种依赖关系,在没执行到叶子节点前,就相当于不断把节点压入栈中(这实际上是一个递推的过程);到了叶子节点,不再依赖其他节点就可以返回当前值了,于是相当于弹栈(相当于回溯过程)。

在这里插入图片描述

递归时间复杂度的估算

master公式(适用于子问题等规模的递归时间复杂度的求解)

T(N) = a*T(N/b) + O(N^d)

l o g b ⁡ a < d → O ( N d ) log_b⁡a<d→O(N^d ) logba<dO(Nd)

l o g b a > d → O ( N x ) . . . . . . x = l o g b a log_ba>d→O(N^x)......x=log_ba logba>dO(Nx)......x=logba

l o g b a = d → O ( N d ∗ l o g N ) log_ba=d→O(N^d*logN) logba=dO(NdlogN)

算法的复杂度与Master定理(必看!!!)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AbyssPraise

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值