java算法的基本之一

昨天看的算法,简单总结一下相关知识点。

一:首先:

基本的算法复杂度的理解。

首先借用《算法导论》的一个例子:

例如:插入排序的最坏情况运行时间刻画为:an^2+m+c,其中a,b,c为常量,运行时间写成O(n^2)。

 

在某某上找的图:侵则删

    补充:

(1)     O(1)表示基本语句的执行次数是一个常数,范围:一般情况,只要不存在循环语句,

(2)  顺便贴一个二分查找的代码

publicstaticint biSearch(int []arr,intkey){

       int lo=0;

       int hi=arr.length-1;

       int mid;

       while(lo<=hi){

           mid=(lo+hi)/2;

           if(array[mid]==key){

                returnmid+1;

           }elseif(array[mid]<key){

                lo=mid+1;

           }else{

                hi=mid-1;

           }

       }

       return -1;

    }

 

于此大家对于基本的算法的复杂度已经了解

 

二:接下来介绍一下算法中需要经常使用的基础

Arrays.Sort()方法:

里面有两种主要的排序方式:

(1)快速排序主要是对基本类型数据(int,short,long等)排序,

(2)归并排序用于对对象类型排序。

 

使用的一些特点:

  • 主要是由于快速排序是不稳定的,而合并排序是稳定的。
  • 稳定是指比较相等的数据在排序之后仍然按照排序之前的前后顺序排列。对于基本数据类型,稳定性没有意义
  • 对象类型,稳定性是比较重要的,归并排序相对而言比较次数比快速排序少,移动(对象引用的移动)次数比快速排序多,
  • 对于对象来说,比较一般比移动耗时。

补充:合并排序的时间复杂度是n*logn, 快速排序的平均时间复杂度是n*logn,但是合并排序的需要额外的n个引用的空间。

 

 

三:做算法题时候发现HashMap里面的containsKey的复杂度为O(1),就此深入研究一下。

不急不急:;先看另一个方法,

1.    KeySet()方法

返回HaspMap中的所有key 放到一个Set中。Set,功能类同视图,只是显示一个结果集,可以对其进行的操作有:删除KEY,这样对应的值也删除掉了,但不能进行增加操作,因为其只是一个视图。JAVA类中定义Set 是一个接口。

2.containsKey(Object key)

如果此映射包含对于指定的键的映射关系,则返回true。

基于API的注释:

首先:containsKey方法调用了getNode(hash(key), key)方法,若结果非null则返回true,否则返回false。所以getNode(hash(key),key)方法就是问题的关键。

key对应的头节点在数组table中的存放位置,也就是下标是(n - 1) & hash这个位运算的结果。n是table的长度(必为2的倍数),则n - 1就是table下标的取值范围,用二进制表示是1111...,共log(n)个1。因此(n - 1) & hash实际上是取了hash二进制形式的后n位数,正好能对应数组table的下标。
    数组通过下标访问Node<K, V>的时间复杂度是O(1),而Node<K, V>访问字段的时间复杂度也是O(1),如果头节点后没有节点,时间复杂度就是O(1)。
头节点后存在节点时,则按下面的代码遍历这些节点,时间复杂度大于O(1)。

            if ((e= first.next) != null) {

                if(first instanceof TreeNode)

                   return ((TreeNode<K,V>)first).getTreeNode(hash, key);

                do{

                   if (e.hash == hash &&

                       ((k = e.key) == key || (key != null && key.equals(k))))

                       return e;

                } while((e = e.next) != null);

            }

至于如何尽量避免产生相同的位运算值,那就是hash算法的事了,不在本文讨论范围内。实际上一个好的hash算法是可以让平均时间复杂度为O(1)的。

至此,containsKey(Object key)方法的时间复杂度问题就基本解决了。

 

 

四:同时经常出错的还有i++,于++i的问题

原文出处: Ticmy

1

2

int i = 0;

i = i++;

程序执行顺序:因为++在后面,所以先使用i,“使用”的含义就是i++这个表达式的值是0,但是并没有做赋值操作,它在整个语句的最后才做赋值,也就是说在做了++操作后再赋值的,所以最终结果还是0

让我们看的更清晰点:

1

2

int i = 0;//这个没什么说的

i = i++;//等效于下面的语句:

 

 

1

2

3

int temp = i;//这个temp就是i++这个表达式的值

i++; //i自增

i = temp;//最终,将表达式的值赋值给i

这是java里的实现,每种语言都有各自的理由去做相应的处理。不要在单个的表达式中对相同的变量赋值超过一次

让我们从字节码层次看一看,源码如下:

1

2

3

4

5

6

7

8

9

10

11

12

public class Test {

 

    public static void main(String... args) {

 

        int i = 0;

 

        i = i++;

 

        System.out.println(i);

 

    }

}

使用javac编译后再使用javap -c Test反编译这个类查看它的字节码,如下(只摘取main方法):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public static void main(java.lang.String[]);

 

Code:

 

0: iconst_0

 

1: istore_1

 

2: iload_1

 

3: iinc 1, 1

 

6: istore_1

 

7: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;

 

10: iload_1

 

11: invokevirtual #3; //Method java/io/PrintStream.println:(I)V

 

14: return

 

这里,我从第0行开始分析(分析中【】表示栈,栈的底端在左边,顶端在右边):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

0:将常数0压入栈,栈内容:【0】

 

1:将栈顶的元素弹出,也就是0,保存到局部变量区索引为为1(也就是变量i)的地方。栈内容:【】

 

2:将局部变量区索引为1(也就是变量i)的值压入栈,栈内容:【0】

 

3:将局部变量区索引为1(也就是常量i)的值加一,此时局部变量区索引为1的值(也就是i的值)是1。栈内容:【0】

 

6:将栈顶元素弹出,保存到局部变量区索引为1(也就是i)的地方,此时i又变成了0。栈内容:【】

 

7:获取常量池中索引为2所表示的类变量,也就是System.out。栈元素:【】

 

10:将局部变量区索引为1的值(也就是i)压入栈。栈元素:【0】

 

11:调用常量池索引为3的方法,也就是System.out.println

 

14:返回main方法

 

 

 

 

 

 

 

 深夜所做。如有错误;立即改正,

查的较多,如有侵犯,私我秒删。


 

参考资料:

1.     https://www.jianshu.com/p/06e2be54d4d6

2.      作者:lllaser

链接:https://www.jianshu.com/p/06e2be54d4d6


有很多东西都不会是你的,因为你压根就没有付出努力争取。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页