数据结构与算法——1.day
1、为什么要学习数据结构与算法?
1)数据结构与算法是学习所有计算机语言的基础。
2)数据结构与算法是一线互联网公司必考内容。
3)告别CURD工程师,为架构师做铺垫。
4)防止被行业淘汰,避免出现中年危机:不懂数据结构与算法的程序开发人员,中年危机是必不可少的。
5)写出优雅的代码,减少系统bug。
6)随着计算机行业的快速发展,技术在变化,架构在变化,但是基本的数据结构与算法没有发生变化。
7)以“不变”应“万变”。
8)…
2、数据结构与算法入门
1)学习书籍:算法入门经典(基础-刘汝佳)
2)学习网站:牛客网或者力扣网
3、算法特征
1)算法基本概念:简单来理解,算法就是解决问题的步骤。
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就满足了高效率低存储的算法设计要求。在此不深入说明,感兴趣的小伙伴可以深入学习。