数据结构与算法——第一天

数据结构与算法——1.day

1、为什么要学习数据结构与算法?

1)数据结构与算法是学习所有计算机语言的基础。

2)数据结构与算法是一线互联网公司必考内容。

3)告别CURD工程师,为架构师做铺垫。

4)防止被行业淘汰,避免出现中年危机:不懂数据结构与算法的程序开发人员,中年危机是必不可少的。

5)写出优雅的代码,减少系统bug。

6)随着计算机行业的快速发展,技术在变化,架构在变化,但是基本的数据结构与算法没有发生变化。

7)以“不变”应“万变”。

8)…

2、数据结构与算法入门

1)学习书籍:算法入门经典(基础-刘汝佳)

2)学习网站:牛客网或者力扣网

3、算法特征

1)算法基本概念:简单来理解,算法就是解决问题的步骤

扫描二维码关注公众号,回复: 14660381 查看本文章

2)五个基本特性:

​ 1、有输入:在算法中可以有零个或者多个输入。

​ 2、有输出:在算法中可以有零个或者多个输出。

​ 3、确定性:算法中的每一步都有确定的含义,不会有第二种含义。

​ 4、可行性:算法的每一步都是可行的,也就是说算法中的每一步都可以在有限的次数中完成。

​ 5、有穷性:在执行算法的时候,不会出现死循环的情况,并且执行的时间是在可接收的范围之内的

4、算法设计原则

1)正确性:程序语法正确;程序输入的数据得到正确的处理,并输出正确的结果。

2)可读性:算法的设计要便于使用者理解,使用者能够看懂。

3)健壮性:程序的设计尽量零bug,当输入的数据非法时,算法应当恰当的做出反应或进行相应处理,而不是产生莫名其妙的输出结果。并且,处理出错的方法不应是中断程序执行,而是应当返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理。

4)高效性与低存储:算法在设计的时候既要考虑效率问题,又要考虑存储问题,在效率上,算法使用到的cpu应当尽量少,并且效率还要高,在内存的使用上,尽量用最少的内存来完成算法的设计。(cpu+内存)

5、衡量算法好坏的指标

1)时间复杂度

​ 1、基本概念:执行一个程序或者一段代码所消耗的时间。

​ 2、基本表示法:大O表示法->O(n)

​ 3、常见的时间复杂度:O(1)、O(logn)、O(n)、O(logn N)、O(n^2)、O(n!)

​ 4、时间复杂度的计算:计算程序中执行次数最多的代码片段,时间复杂度是一个大概数,不需要将所有的程序代码都计算进去。

package com.oneday;
public class BigO {
    
    
//    时间复杂度为O(1)
    public static void O1(){
    
    
        int i = 5;//执行一次
        while(i > 1){
    
    //执行6次
            i=i-1;//执行5次
//            i=i --;//执行5次
            //注意 i=i--是先赋值,后计算,因此i的值一直为5,进入死循环
            System.out.println("i->"+i);
        }
        System.out.println("i->"+i);
    }
//    时间复杂度为O(logn),最经典就是二分查找
    public static void Ologn(){
    
    
        int i = 1;
        int n = Integer.MAX_VALUE;//表示n是未知数
        while(i <= n){
    
    
            i = i * 2;//此时不知道n的具体数据,当这行代码执行结束的时候,需要多少次
            //1 2 4 8 16 32 => 2^x = n =>x=log2(n),因此该处的时间复杂度为log2(n)
//            计算机里面要忽略常数,因此时间复杂度为logn
        }
    }
//    时间复杂度为O(nlogn)
    public static void Onlogn(){
    
    
        int i = 1;
        int n = Integer.MAX_VALUE;//表示n是未知数
        for(;i<n;) {
    
    
            while (i <= n) {
    
    
                i = i * 2;//此时不知道n的具体数据,当这行代码执行结束的时候,需要多少次
                //1 2 4 8 16 32 => 2^x = n =>x=log2(n),因此该处的时间复杂度为log2(n)
//            计算机里面要忽略常数,因此时间复杂度为logn
            }
        }
        //加上外层循环,时间复杂度为nlogn
    }
//    时间复杂度为On
    public static void On(){
    
    
        int i = 0 ;
        int n = Integer.MAX_VALUE;
        while (i < n){
    
    //执行了N次,时间复杂度为On
            i=i + 1;
        }
        System.out.println(i);
    }
//    时间复杂度为O(n^2)
    public  static void On2(){
    
    
        int i = 0 ;
        int n = Integer.MAX_VALUE;
        for(;i<n;){
    
    //外层执行n次
            while (i < n){
    
    //执行了N次,时间复杂度为On
                i=i + 1;
            }
        }
        //总的执行了n^2次,时间复杂度为n^2
        System.out.println(i);
    }
    public static void main(String[] args) {
    
    
       BigO.On();
    }
}

注意:在进行时间复杂度分析的时候,找运行次数最多的地方,比如循环的地方,网络请求,RPC(远程调用),数据库请求等地方。log打印,求平均时间。

2)空间复杂度

​ 1、基本概念:执行一个程序或者一段代码所消耗的空间。

​ 2、基本表示法:大N表示法->N(n)。

6、开胃菜

1)判断一个树是不是2的n次方。如2,4,8,16,32…等是2的n次方,6,10,12…不是2的n次方。

解题思路:

1、可以把2的n次方看作是多个2连续相乘,如8=2x2x2;16=2x2x2x2等,如果8%2==0,表示该数是2的n次方,否则不是2的n次方。

package com.oneday;
/**
 * 判断一个数是不是2的n次方
 */
public class TwoFor {
    
    
    //    方法一:暴力循环
    public static boolean isTwoFor_circle(int num) {
    
    
//        标志是否能够整除2
        boolean flag = false;
        if (num < 1) {
    
    
            return flag;
        }
        while (num > 1) {
    
    
            //System.out.println(num);
            if (num % 2 == 0) {
    
    //表示能够整除2,说明num是2的n次方
                flag = true;
            } else {
    
    
                flag = false;
            }
            //判断结束之后,num需要更新,不然进入死循环
            num = num / 2;
        }
        return flag;
    }
    //测试代码
    public static void main(String[] args) {
    
    
        System.out.println(TwoFor.isTwoFor_circle(0));
    }
}

2、当看到2这个数的时候,我们要想到的是二进制,因此本题可以考虑用二进制来解决:

二进制表示2的n次方:

十进制的2^n 二进制2^n 二进制2^n-1
2 10=0x2^0 + 1x2 ^1 1=01=1x2^0 + 0x2^1
4 100=0x2^0 + 0x2^1 + 1x2^2 3=011=1x2^0 + 1x2^1 + 0x2^2
8 1000=0x2^0 + 0x2^1 + 0x2^2 + 1x2^3 7=0111=1x2^0 + 1x2^1 + 1x2^2 + 0x2^3
16 10000=0x2^0 + 0x2^1 + 0x2^2 + 0x2^3 + 1x2^4 15=01111=1x2^0 + 1x2^1 + 1x2^2 + 1x2^3 + 0x2^4

注意:从表中不难看出,2^n 用二进制来表示,最高位都是1,2^n-1 的最高位都是0,低位都是1,采用按位与(&)的算法,当2^n & 2^n-1==0的时候,说明该数是2的n次方。

​ 补充:所谓的按位与(&)算法,相同位置的数字相同时,结果为1,否则为0,如2&1=0(即10 & 01,10第一位为1,01第一位为1,10第二位为0,01第二位为1,最终结果为0,该运算是在二进制下完成的),更多基本运算请参考计算机组成原理教材。因此上述的代码可以简化如下:

package com.oneday;

/**
 * 判断一个数是不是2的n次方
 */
public class TwoFor {
    
    
//  方法二:二进制表示法
    public static boolean isTwoFor_Two(int num){
    
    
        if(num < 1){
    
    
           return false;
        }
//        采用按位与进行计算,如果(num & (num-1)) == 0表示num是2的n次方,
//        并且减少循环,提高效率
        return (num & (num-1)) == 0;
    }
    public static void main(String[] args) {
    
    
        System.out.println(TwoFor.isTwoFor_Two(16));
    }
}

从解法1和解法2中可以看出,越是代码少的算法,对于计算机底层原理的要求越高,对计算基础知识掌握要求越高,只有越了解计算机的工作原理,才能设计更好的算法,解法2就满足了高效率低存储的算法设计要求。在此不深入说明,感兴趣的小伙伴可以深入学习。

猜你喜欢

转载自blog.csdn.net/qq_45095925/article/details/125094029