Java新手问题 01 算法和数据结构

Question: 什么时候该用数组型容器、什么时候该用链表型容器?

  • 数组数据结构:
    • 是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。
  • 链表数据结构:
    • 恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了
  • 数组型容器有:
    ArrayList
  • 链表型容器有:
    LinkedList
  • 使用原则:
    • 当数据删除比较多,而查询的情况相对较少的情况下使用链表型容器.
    • 当数据的查询比较多,删除/新增操作比较少的时候使用数组型容器.

Question: 什么是散列函数?HashMap 的实现原理是什么?

  • 散列函数是什么:
    • 散列函数又叫哈希函数,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
  • HashMap内部是由Entry[]数组实现,根据key的hash值取模Entry[].length得到数组下标(hash(key)%len).最终存储方式是无序的。插入元素时,如果两条Key落在同一个数组中(比如哈希值1和17取模16后都属于第一个Entry),Entry用一个next属性实现多个Entry以单向链表存放,先入Entry将next指向桶当前的Entry。

Question: 什么是递归?如果你以前从来没写过递归函数,尝试着写一个(比如用递归函数进行目录树遍历)。

递归(英语:Recursion),又译为递回,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。递归一词还较常用于描述以自相似方法重复事物的过程。例如,当两面镜子相互之间近似平行时,镜中嵌套的图像是以无限递归的形式出现的。也可以理解为自我复制的过程。

  • 递归经典算法是:斐波那契数列

费波那契数列(意大利语:Successione di Fibonacci),又译为费波拿契数、斐波那契数列、费氏数列、黄金分割数列。
在数学上,费波那契数列是以递归的方法来定义:

f0 = 0
f1 = 1
fn = f(n-1) + f(n-2)

用文字来说,就是费波那契数列由0和1开始,之后的费波那契系数就是由之前的两数相加而得出。首几个费波那契系数是:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……(OEIS中的数列A000045)

public class FibonacciDemo {
    public static void main(String[] args) {
        System.out.println(fibonacci(0, 1, 44));
    }
    public static int fibonacci(int cur, int next, int n) {
        if (n == 0) {
            return cur;
        } else {
            System.out.println(next);
            return fibonacci(next, cur + next, n - 1);
        }
    }

}

Question: 什么是算法复杂度?

  • 算法复杂度是指计算解决问题所需的资源(时间和空间)
  • 为什么:
    • 如果一个问题的求解需要相当多的资源(无论用什么算法),则被认为是难解的。计算复杂性理论通过引入数学计算模型来研究这些问题以及定量计算解决问题所需的资源(时间和空间),从而将资源的确定方法正式化了。其他复杂性测度同样被运用,比如通信量(应用于通信复杂性),电路中门的数量(应用于电路复杂性)以及中央处理器的数量(应用于并行计算)。计算复杂性理论的一个作用就是确定一个能或不能被计算机求解的问题的所具有的实际限制。

  • 怎么样:
    • 时间复杂度是一个函数,它定量描述了该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。例如,如果一个算法对于任何大小为 n (必须比 n0 大)的输入,它至多需要 5n3 + 3n 的时间运行完毕,那么它的渐近时间复杂度是 O(n3)。
    • 空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。比如直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1) 。而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息。一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量。

Question: 你是否理解空间换时间的思想?

  • 比如在数据库查询时,将频繁被访问的数据存储起来就是一种空间换时间的应用.
  • 在算法层面上,空间换时间,就比如HashMap,通过将数据以Hash数据分组存放的方式,以空间换取查询的时间的加快.

Question: 写一个针对整数数组的冒泡排序函数,看看你要修改几次才能跑通。

import com.alibaba.fastjson.JSON;
public class BubbleSort {
    public static void main(String[] args) {
        int[] aa = new int[] { 2, 4, 3, 2, 5, 6 };
        for (int i = 0; i < aa.length; i++) {
            for (int j = i; j < aa.length; j++) {
                if (aa[i] < aa[j]) {
                    int temp = aa[i];
                    aa[i] = aa[j];
                    aa[j] = temp;
                }
            }
        }
        System.out.println(JSON.toJSONString(aa));
    }
}

Question: 写一个针对整数数组的二分查找函数,看看你要修改几次才能跑通。

public class BisectionSearch {
    public static void main(String[] args) {
        int[] aa = new int[] { 6, 5, 4, 3, 2, 2 };
        int target = 5;
        int start = 0;
        int end = aa.length - 1;
        int mid = (start + end) / 2;
        while (aa[mid] != target && end > start) {
            if (aa[mid] > target) {
                start = mid + 1;
            } else {
                end = mid - 1;
            }
            mid = (start + end) / 2;
        }
        if (aa[mid] == target) {
            System.out.println("找到target");
        }
    }
}

参考链接

posted @ 2017-07-01 00:00  Alcc  阅读(292)  评论(0编辑  收藏  举报