认识复杂度,对数器,二分法

  • 常见的常数时间操作

常见的常数时间操作通常包括三种情况,第一固定的时间操作,第二寻址操作,第三就是非固定的时间操作。

关于固定的时间操作我们可以这么理解

int a = 1;
int b = 2;

上面这两行代码虽然是两行,但是他的时间复杂度确是O(1),假设我们把每一行执行的代码时间记作unit_time,那么执行这两行所需要的代码就是2unit_time,根据大O阶的表示方法既O(2)就可以表示位O(1).

关于寻址操作我们大家都第一时间想到的可能就是数组的,数组支持随机访问,并且在O(1)的时间复杂度下按照下标来快速访问数组中的元素,提到数组,我们不得不提到寻址公式这一个知识点,在你创建一个数组的时候,会在内存中开辟一个连续的内存空间,这个内存空间会存在一个基础地址,简称基址(base_address),下图右侧代表的就是数组在内存中的地址,左侧就是index下标,我们如果访问数组中的一个元素,自然可以的得到这样一个公式:

a[i]_address = base_address + i * date_type_size,

翻译出来就是基址+下标*数据类型的大小

在这里还要提一下关于运算的一些知识:

常见的算数运算+,-,*,/,%这些就不必多说

常见的位运算,>>,>>>,

这里主要介绍的就是>>,各二进制位全部右移若干位,对无符号数,高位补0,对有符号数,高位补1,0100 >> k  = 0001,这里的k = 2
                 <<这个自然就是左移

还有一个就是非固定时间的操作,单链表LinkedList,跳转结构,不是连续结构,比如说我要申请1000万长度,是不能通过寻址公式来直接获取的

  • 算法的复杂度

时间复杂度:

我们在探究时间复杂度的时候先来看看大O复杂度,这里就直接把结论说出来了,也就是代码的执行时间T(n)与每一条语句总的执行次数成正比:

即:T(n) = O(f(n))

举一个例子,T(n) = (2n^2 + 2n + 3) * unit_time = O(2n^2 + 2n + 3)

这个时候我们只需要保留最高项就可以了,即O(n^2)

时间复杂度又叫渐进时间复杂度,描述的是代码的执行时间随着数据规模的变化趋势

 

看这样一段代码
int cal(int n){
    int sum1 = 0;
    int p = 0;
    //这一段就不是O(n),因为100是一个明确的数字
    for(;p < 100;p++){
        sum1 += p;    
    }
    //这段代码就是O(n)
    int sum2 = 0;
    for(;p < n;p++){
        sum2 += p;    
    }
    //这段代码就是O(n^2)
    int i = 1;
    int j = 1;
    int sum3 = 0;
    for(;i < n;i++){
        for(;j < n;j++){
            sum2 += i * j;        
        }    
    }
}

常见的复杂度量级常量阶,O(1),对数阶,O(logn),线性阶O(n),O(m+n),线性对数阶O(nlogn),平方阶O(n^2),指数阶O(2^n),阶乘阶O(n!)

- 假设数据量为N的一个样本中我执行完整个流程, 我常数操作的数量是一个什么样的关系。

- 一定要把算法流程的每一步都分解成常数时间的操作,然后再去分析它的复杂度。

- 复杂度的用法是说,当我的 N 逼近于无穷大的时候,讨论的是你的数据量和你的运行时间的一个关系。

- 当我的数据量N很大的时候,算法的优良,你会发现只跟最高阶有关。

评估算法优劣的核心指标:

时间复杂度,额外空间复杂度,常数项时间

常见的常数时间的操作

- 常见的算术运算(+、-、*、/、% 等)

- 常见的位运算(>>、>>>、

- 赋值、比较、自增、自减操作等- 数组寻址操作- 总之,执行时间固定的操作都是常数时间的操作。

- 反之,执行时间不固定的操作,都不是常数时间的操作。

如果你某个操作执行时间不是固定的,它跟数量有关,那就不是常数时间的操作,你必须得分解成常数时间的操作

// LinkedList, 下面打印list中每一个值, 复杂度O(N^2)
for(int i = 0; i < N; i++) {
    num = list.get(i); // get操作是O(N)的print(num); // get出来打印}
}

在估计时间复杂度自己列流程一定要保证每一个小动作都是常数时间的你才能估计准

额外空间复杂度

-用户要求返回的空间数组,不算额外的空间复杂度

-如果在算法的流程中,我开辟的空间只有有限的几个变量,那么这个算法的额外空间复杂度就是O(1)

-额外空间复杂度也要按照最坏的情况下计算

-用户要什么,最终你给他的东西不算你的额外空间,输入的什么参数也不算,额外的空间复杂度就是在你涉及算法过程的时候,必须使用的空间

  • 简单排序算法

选择排序,时间复杂度O(n^2)

选择排序的核心思想在于,首先假设第一个数为最小的,即0位置,那么我们需要找到1到n位置上数的最小值然后与第一个数进行对比
比他小就把下标从新赋值给最小值那个变量,每次循环结束,进行交换
class selectSort{
    public void selectSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;        
        }    
        for(int i = 0;i < arr.length - 1;i++){
            //arr.length - 1的原因是交换之后最后一个一定是最大的了
            int minIndex = i;//首先假设i = 0为最小值的下标,之后依次++
            for(int j = i + 1;j < arr.length;j++){
                minIndex = arr[minIndex] > arr[j] ? j : minIndex;            
            }        
            swap(arr,i,minIndex);
        }
    }
    public void swap(int[] arr,int i,int minIndex){
        int temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;    
    }
}

冒泡排序,时间复杂度O(n^2)

冒泡排序就是每次比较每次交换
我们可以对冒泡进行简单的优化,我们知道每次冒泡循环一次之后,最后的数一定就是最大的那个,我们可以采用逆序的方法
public void bubbleSort(int[] arr){
    if(arr == null || arr.length < 2){
        return;    
    }
    for(int i = arr.length();i > 0;i--){
        for(int j = 0;j < i;j++){
            if(arr[j] > arr[j+1]){
                swap(arr,j,j+1);            
            }        
        }    
    }
}

插入排序,时间复杂度O(n^2)

插入排序可以想象成打牌,每当你摸一个牌的时候,去已有的牌中找到合适的顺序去插入
public void insertSort(int[] arr){
    if(arr == null || arr.length < 2){
        return ;    
    }
    //选择第一张牌为基本牌
    for(int i = 1;i < arr.length();i++){
        for(int j = i - 1;j >= 0 && arr[j] > arr[j + 1];j--){
            swap(arr,j,j+1);        
        }    
    }
}

一个问题的最优解

一般情况下,认为解决一个问题的算法流程,在时间复杂度的指标上,一定要尽可能的低,`先满足了时间复杂度最低这个指标之后,使用最少的空间的算法流程`,叫这个问题的最优解。 一般说起最优解都是忽略掉常数项这个因素的,因为这个因素只决定了实现层次的优化和考虑,而和怎么解决整个问题的思想无关。

1) 时间复杂度尽可能低

2) 尽可能优化空间注: 不包括常数时间的优化

  • 认识对数器

对数器可以用在你想验证的方法不一定依赖别人的测试用例,自己可以采用大样本随机测试来检验正确性

以插入排序为例,怎样生成随机数组

public static void main(String[] args){
    int testTime = 500000;
    int maxSize = 100;//随机数组长度100
    int maxValue = 100;//-100,100
    boolean succeed = true;
    for(int i = 0;i < testTime;i++){
        int[] arr1 = generateRandomArray(maxSize,maxValue);
        int[] arr2 = copyArray(arr1);
        insertionSort(arr1);
        comparetor(arr2);
        if(!isEqual(arr1,arr2)){
            succeed = false;
            break;        
        }    
    }
}
public static int[] geherateRandomArray(int maxSize,int maxValue){
    int[] arr = new int[(int)((maxSize + 1) * Math.random())];
    for(int i = 0;i < arr.length;i++){
        arr[i] = (int)((maxSize + 1) * Math.random()) - (int)(maxSize * Math.random());    
    }
    return arr;
}
  • 认识二分法,时间复杂度O(logN)

——在一个有序数组中,查找某个值是否存在

1.在一个有序数组中,查找某个值是否存在
class BSExist{
    public static boolean exist(int[] sortedArr,int num){
        //判断数组是否有效
        if(sortedArr == null || sortedArr.length == 0){
            return false;        
        }    
        int left = 0;
        int right = sortedArr.length - 1;
        int mid = 0;
        //这样的话最少有两个数
        while(left < right){
            mid = L + ((right - left) >> 1);
            if(sortedArr[mid] == num){
                return true;            
            } else if(sortedArr[mid] > num){
                right = mid - 1;            
            } else {
                left = mid + 1;            
            }
        }
        //return false;直接返回false不太准确,可能num还等于第一个数
        return sortedArr[left] == num;
    } 
    //使用对数器进行测试
    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 arrr;
    }
    public static boolean test(int[] arr,int num){
        for(int cur : arr){
            if(cur == num){
                return true;            
            }        
        }
        return false;
    }
    public static void main(String[] args){
        int testTime = 500000;
        int maxSize = 100;
        int maxValue = 100;
        boolean succeed = true;
        for(int i = 0;i < testTime;i++){
           int[] arr = generateRandomArray(maxSize,maxValue);
           Arrays.sort(arr);
           //生成随机数测试
           int value = (int)((maxValue + 1) * Math.random()) - (maxValue * Math.random());
            if(exist(arr,value) != test(arr,value)){
                succeed = false;
                break;            
            }
         }
         sout(succeed ? "Nice" : "fuck");
    }
}
  • 二分法的while写法说明
  • while(L<R) {}
    return sortedArr[L] == num;
    // OR
    while(L<=R) {}
    return false;

    这两种写法,啥时候写 ≤ 时候写 < . 我只能说都对你如果把你的大逻辑定成至少两个数的时候,你就有这种逻辑下的边界条件。如果你把你的逻辑定成至少一个数字我就能二分,那就有这种逻辑下的边界条件,这个东西其实相当的仁者见仁智者见智,写Code扣边界条件这个过程是必不可少的。

    ——在一个有序数组中,找大于等于(>=)某个数最左侧的位置

class BSNearLeft{
    //在arr上,满足>=value最左侧的位置
    public static int nearestIndex(int[] arr,int value){
        //还是采用二分
        int left = 0;
        int right = 0;
        int index = -1;//记录最左侧下标
        //此次需要保证只有一个数存在,当左右两侧相同时,如果这个数还时小于value,那么下一个数就是要求的数
        while(left <= right){
            int mid = left + ((right - left) >> 1);
            if(arr[mid] >= value){
                //这次加上等于条件的原因时>=这个value
                index = mid;
                right = mid - 1;            
            } else {
                left = mid + 1;            
            }       
        }    
        return index;
    }
    public static int test(int[] arr,int value){
        for(int i = 0;i < arr.length;i++){
            if(arr[i] == value)
               return i;
        }    
        return -1;
    }
    //for test
    public static int[] generateRandomArray(int maxSize,int maxValue){
        int[] arr = new int[(int)((maxSzie+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 void main(String[] args){
        int maxSize = 100;
        int testTime = 500000;
        int maxValue = 100;
        boolean succeed = true;
        for(int i = 0;i < testTIme;i++){
            int[] arr = generateRandomArray(maxSize,maxValue);
            Arrays.sort(arr);
            int value = (int)((maxValue + 1) * Math.random()) - (int)(maxValue * Math.random());
            if(test(arr,value) != nearestIndex(arr,value)){
                succeed = false;
                break;            
            }        
        } 
        sout(succeed ? "nice" : "fuck");mid    
    }
}

 ——在一个有序数组中,找到一个

class BSNearRight{
    public static int nearrestIndex(int[] arr,int value){
        int left = 0;
        int right = arr.length - 1;
        int index = -1;
        while(left <= right){
            int mid = left + ((right - left) >> 1);
            if(arr[mid] <= value){
                index = mid;
                left = mid + 1;            
            } else {
                right = mid - 1;            
            }
        }
        return index;
    }
    public static int test(int[] arr,int value){
        for(int i = 0;i < arr.length;i++){
            if(arr[i] == value)
               return i;
        }    
        return -1;
    }
    //for test
    public static int[] generateRandomArray(int maxSize,int maxValue){
        int[] arr = new int[(int)((maxSzie+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 void main(String[] args){
        int maxSize = 100;
        int testTime = 500000;
        int maxValue = 100;
        boolean succeed = true;
        for(int i = 0;i < testTIme;i++){
            int[] arr = generateRandomArray(maxSize,maxValue);
            Arrays.sort(arr);
            int value = (int)((maxValue + 1) * Math.random()) - (int)(maxValue * Math.random());
            if(test(arr,value) != nearestIndex(arr,value)){
                succeed = false;
                break;            
            }        
        } 
        sout(succeed ? "nice" : "fuck");mid    
    }
}
}

 

——局部最小值问题

数组中元素无序,任何两个相邻数不相等

局部最小时值:0位置上a,小于1位置上b,则0位置时局部最小

N-1位置a,小于N-2位置b,n-1局部最小

i-1 > i < i+1,i是局部最小

class BSAwesome{
    public static int getLessIndex(int[] arr){
    if(arr == null || arr.length == 0){
        return -1;    
    }
    if(arr.length == 1 || arr[0] < arr[1]){
        return 0;    
    }
    if(arr[arr.length - 1] < arr[arr.length - 2]){
        return arr.length - 1;    
    }
    //为什么这样定义呢,因为排除0位置,和arr.length - 1位置不是局部最小,那么他两者之间一定有局部最小
    int left = 1;
    int right = arr.length - 2;
    int mid = 0;
    while(left < right){
        mid = left + ((right - left) >> 1);
        //如果mid 比mid - 1和mid+1小,直接返回,如果mid>mid-1,则在0到mid - 1之前一定存在局部最小
        if(arr[mid] > arr[mid - 1]){
            right = mid - 1;
        } else if(arr[mid] > arr[mid + 1]){
            left = mid + 1;        
        } else {
            return mid;        
        }
    }
    return left;
  }
}

为什么能二分

二分不一定必须要有序的,不是只有有序才能二分, 一种策略,如果有一边肯定有或者有一边肯定没有或者有一边可能有但另一边一定没有,则可以去进行二分操作

1) 数据状况特殊

2) 问题本身特殊

二分不一定要求有序

要看你问题是什么,要看你的数据状况是什么只要你能够构建一种排他性的东西,左右两侧有一半肯定有, 有一半可能没有,不确定,如果你只找一个, 砍一半就行了,只要你能构建出类似于排他性的东西,你就能二分, 不一定要求数组有序  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值