<mark>哈希</mark>
<mark>请谈一谈,hashCode() 和equals() 方法的重要性体现在什么地方?</mark>
Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。
<mark>请说一说,Java中的HashMap的工作原理是什么?</mark>
HashMap类有一个叫做Entry的内部类。这个Entry类包含了key-value作为实例变量。 每当往hashmap里面存放key-value对的时候,都会为它们实例化一个Entry对象,这个Entry对象就会存储在前面提到的Entry数组table中。Entry具体存在table的那个位置是 根据key的hashcode()方法计算出来的hash值(来决定)。
<mark>介绍一下,什么是hashmap?</mark>
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
hashmap共有4个构造函数:
// 默认构造函数。HashMap()
// 指定“容量大小”的构造函数
HashMap(int capacity)
// 指定“容量大小”和“加载因子”的构造函数
HashMap(int capacity, float loadFactor)
// 包含“子Map”的构造函数
HashMap(Map<? extends K, ? extends V> map)
<mark>讲一讲,如何构造一致性 哈希算法。</mark>
先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0, 232-1]),接着在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。
这种算法解决了普通余数Hash算法伸缩性差的问题,可以保证在上线、下线服务器的情况下尽量有多的请求命中原来路由到的服务器。
== 请问,Object作为HashMap的key的话,对Object有什么要求吗?==
要求Object中hashcode不能变。
<mark>请问 hashset 存的数是有序的吗?</mark>
Hashset是无序的。
<mark>树</mark>
<mark>TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?</mark>
TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。
代码示例:
public class
Student implements Comparable<Student> {
private String
name; // 姓名
private int
age; // 年龄
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" +
name + ", age=" + age + "]";
}
@Override
public int compareTo(Student o) {
return this.age - o.age; // 比较年龄(年龄的升序)
}
}
import java.util.Set;
import java.util.TreeSet;
class Test01 {
public static void main(String[] args) {
Set<Student> set = new
TreeSet<>(); // Java 7
}
}
import java.util.Set;
import java.util.TreeSet;
class Test01 {
public static void main(String[] args) {
Set<Student> set = new
TreeSet<>(); // Java 7的钻石语法(构造器后面的尖括号中不需要写类型)
set.add(new Student("Hao
LUO", 33));
set.add(new Student("XJ
WANG", 32));
set.add(new Student("Bruce
LEE", 60));
set.add(new Student("Bob
YANG", 22));
for(Student stu : set) {
System.out.println(stu);
}
//
set.add(new Student("Hao
LUO", 33));
set.add(new Student("XJ
WANG", 32));
set.add(new Student("Bruce
LEE", 60));
set.add(new Student("Bob
YANG", 22));
for(Student stu : set) {
System.out.println(stu);
}
// 输出结果:
// Student [name=Bob YANG, age=22]
// Student [name=XJ WANG, age=32]
// Student [name=Hao LUO, age=33]
// Student [name=Bruce LEE, age=60]
}
}
// Student [name=Bob YANG, age=22]
// Student [name=XJ WANG, age=32]
// Student [name=Hao LUO, age=33]
// Student [name=Bruce LEE, age=60]
}
}
<mark>如何打印二叉树每层的节点?</mark>
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
// 定义节点
class Node {
int val;
Node left;
Node right;
public Node(int val) {
this.val = val;
}
}
public ArrayList<Integer> gzy; // 保存根左右的序列
public ArrayList<Integer> zgy; // 保存左跟右的序列
public ArrayList<Node> pack; // 保存已经排好的节点
public static void main(String[] args) {
Main main = new Main();
main.getResult();
}
public void getResult() {
//init
Scanner scanner = new Scanner(System.in);
int count = scanner.nextInt();
gzy = new ArrayList<>();
zgy = new ArrayList<>();
for(int i = 0; i < count; i++) {
gzy.add(scanner.nextInt());
}
for(int j = 0; j < count; j++) {
zgy.add(scanner.nextInt());
}
pack = new ArrayList<>(); // 已经还原的节点
//exception
if(count== 1) {
System.out.println(gzy.get(0));
return;
}
// 构造最左侧节点的二叉树
Node node = new Node(gzy.get(0));
pack.add(node);
int index1 = 1; // 根左右的下标
Node tmp = node;
while(gzy.get(index1)!= zgy.get(0)) { // 如果没访问到最左边的叶子节点,继续还原最左侧二叉树
tmp.left = new Node(gzy.get(index1++));
tmp = tmp.left;
pack.add(tmp);
}
tmp.left = new Node(gzy.get(index1++));
pack.add(tmp.left);
// 加入剩余的节点完善二叉树
for(int k = index1; k < gzy.size(); k++) {
fillErCS(gzy.get(k));
}
// 层次遍历
ArrayList<Node> res = new ArrayList<>();
res.add(node);
int num = 0;
while(res.size()!= num) {
System.out.print(res.get(num).val + "");
if(res.get(num).left != null) {
res.add(res.get(num).left);
}
if(res.get(num).right != null) {
res.add(res.get(num).right);
}
num++;
}
}
// 将值为val的节点加入二叉树
private void fillErCS(int val) {
int index = zgy.indexOf(val);
// 每一个遍历的节点都是val节点的根或者在其左边
for (int i = index - 1; i >= 0; i--) {
if (findNode(zgy.get(i)) != null) { // 找到待插入节点的根节点或者其左边的节点
Node node = findNode(zgy.get(i));
insert(node, val);
break;
}
}
}
// 将节点val插入二叉树
private void insert(Node node, int val) {
if(zgy.indexOf(node.val)> zgy.indexOf(val)) { // node在待插入节点的右边
if(node.left == null) {
node.left= new Node(val);
pack.add(node.left);
return;
}
insert(node.left, val);
}else{ //node在待插入节点的左边或是其根
if(node.right == null) {
node.right= new Node(val);
pack.add(node.right);
return;
}
insert(node.right, val);
}
}
// 根据val找到pack里的节点
private Node findNode(int val) {
for (Node node : pack) {
if (node.val == val) {
return node;
}
}
return null;
}
}
<mark>从上往下打印出二叉树的每个节点,同层节点从左至右打印</mark>
import java.util.ArrayList;
import javax.swing.tree.TreeNode;
/** * 从上往下打印二叉树每个节点,同层节点从左至右打印 * @author 96971 * */
public class TreeNode {
int val=0;
TreeNode left=null;
TreeNode right=null;
public TreeNode(int val) {
this.val=val;
}
}
class Solution{
public ArrayList<Integer> printFromTopToBootom(TreeNode root){
ArrayList<Integer> vlist=new ArrayList<>();//存储二叉树节点的值
ArrayList<TreeNode> treeNodes=new ArrayList<>();//存储二叉树的节点
if (root==null) {
return vlist;
}
treeNodes.add(root);
vlist.add(root.val);
for (int i = 0; i < treeNodes.size(); i++) {
TreeNode node=treeNodes.get(i);
if (node.left!=null) {
treeNodes.add(node.left);
vlist.add(node.left.val);
}
if (node.right!=null) {
treeNodes.add(node.right);
vlist.add(node.right.val);
}
}
return vlist;
}
}
<mark>如何知道二叉树的深度?</mark>
实现二叉树的深度方式有两种,递归以及非递归。
①递归实现:
为了求树的深度,可以先求其左子树的深度和右子树的深度,可以用递归实现,递归的出口就是节点为空。返回值为0;
示例代码:
//递归方式一:
public int findDeep(BiTree root){
int deep=0;
if(root!=null){
int lChildDeep=findDeep(root.left);
int rChildDeep=findDeep(root.right);
deep=lChildDeep>rChildDeep? lChildDeep+1:rChildDeep+1;
}
return deep;
}
//递归方式二:
public int findDeep1(BiTree root){
if(root==null){
return 0;
}
else{
int lChildDeep=findDeep(root.left);
int rChildDeep=findDeep(root.right);
return lChildDeep>rChildDeep? lChildDeep+1:rChildDeep+1;
}
}
②非递归实现:
利用层次遍历的算法,设置变量level记录当前节点所在的层数,设置变量last指向当前层的最后一个节点,当处理完当前层的最后一个节点,让level指向+1操作。设置变量cur记录当前层已经访问的节点的个数,当cur等于last时,表示该层访问结束。
层次遍历在求树的宽度、输出某一层节点,某一层节点个数,每一层节点个数都可以采取类似的算法。
树的宽度:在树的深度算法基础上,加一个记录访问过的层节点个数最多的变量max,在访问每层前max与last比较,如果max比较大,max不变,如果max小于last,把last赋值给max;
// 非递归实现
public int findDeep2(BiTree root) {
if (root == null)
return 0;
BiTree current = null;
LinkedList<BiTree> queue = new LinkedList<BiTree>();
queue.offer(root);
int cur, last;
int level = 0;
while (!queue.isEmpty()) {
cur = 0;// 记录本层已经遍历的节点个数
last = queue.size();// 当遍历完当前层以后,队列里元素全是下一层的元素,队列的长度是这一层的节点的个数
while (cur < last)// 当还没有遍历到本层最后一个节点时循环
{
current = queue.poll();// 出队一个元素
cur++;
// 把当前节点的左右节点入队(如果存在的话)
if (current.left != null) {
queue.offer(current.left);
}
if (current.right != null) {
queue.offer(current.right);
}
}
level++;// 每遍历完一层level+1
}
return level;
}
== 二叉树任意两个节点之间路径的最大长度==
算法步骤:
(1)初始化二叉树,并将每个节点Lm和Rm初始化为0,定义二叉树中节点的最大距离Max = -1
(2)为了计算一个节点的Lm和Rm,需要采用后序遍历策略,递归的计算出它左孩子Left的Lm,Rm,Lm = max(Left->Lm + Left->Rm) + 1;和右孩子Right的Lm,Rm,Rm = max(Right->Lm,Right->Rm) + 1;
(3)对于每一个节点,计算Lm + Rm,如果其值大于Max,则Max = Lm + Rm,最后Max就是最大距离。
public class TreeNode {
int val=0;
TreeNode left=null;
TreeNode right=null;
int maxleft;//左子树最长距离
int maxright;//右子树最长距离
public TreeNode(int val) {
this.val=val;
}
class Solution{
int max=-1;//最大距离长度
public int maxLen(TreeNode root) {
if (root==null) { //树为空返回0
return 0;
}
if (root.left!=null) {
root.maxleft=maxLen(root.left)+1;
}
if (root.right!=null) {
root.maxright=maxLen(root.right)+1;
}
//如果以该节点为根节点的子树有最大的距离,那就更新最大的距离
int sum=root.maxleft+root.maxright;
if (sum>max) {
max=sum;
}
return root.maxleft>root.maxright? root.maxleft:root.maxright;
}
}
}
<mark>算法题:二叉树层序遍历,进一步提问:要求每层打印出一个换行符</mark>
public class TreeNode {
int val=0;
TreeNode left=null;
TreeNode right=null;
int maxleft;//左子树最长距离
int maxright;//右子树最长距离
public TreeNode(int val) {
this.val=val;
}
class Solution{
//二叉树遍历,要求每层打印出一个换行符
public void BST(TreeNode root) {
if (root==null) {
return;
}
LinkedList<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size=queue.size();
while (size-->0) {
TreeNode node=queue.poll();
System.out.print(node.val+" ");
if (node.left!=null) {
queue.offer(node.left);
}
if (node.right!=null) {
queue.offer(node.right);
}
}
System.out.println();
}
}
}
}
<mark>请你说一下,B+树和B-树?</mark>
b+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更“矮胖”;
b+树查询必须查找到叶子节点,b树只要匹配到即可不用管元素位置,因此b+树查找更稳定(并不慢);
对于范围查找来说,b+树只需遍历叶子节点链表即可,b树却需要重复地中序遍历。
<mark>二叉树 Z 字型遍历</mark>
public class TreeNode {
int val=0;
TreeNode left=null;
TreeNode right=null;
int maxleft;//左子树最长距离
int maxright;//右子树最长距离
public TreeNode(int val) {
this.val=val;
}
}
class Solution{
//二叉树Z字型遍历
public List<List<Integer>> zizagLevelOrder(TreeNode root) {
List<List<Integer>> resList=new ArrayList<List<Integer>>();
LinkedList<TreeNode> treeNodes=new LinkedList<>();
boolean flag=true;//控制反转
if (root==null) {
return resList;
}
treeNodes.add(root);
while (treeNodes.size()!=0) {
flag=!flag;
int size=treeNodes.size();
List<Integer> list=new ArrayList<>();
while (size-->0) {
TreeNode node=treeNodes.remove();
list.add(node.val);
if (node.left!=null) {
treeNodes.add(node.left);
}
if (node.right!=null) {
treeNodes.add(node.right);
}
}
if (flag) {
Collections.reverse(list);//反转list列表中元素顺序;
}
resList.add(list);
}
return resList;
}
}
<mark>编程题:写一个函数,找到一个文件夹下所有文件,包括子文件夹</mark>
import java.io.File;
public class FileCounts {
public static void main(String[] args) {
//取得目标目录
File file = new File("D:");
//获取目录下子文件及子文件夹
File[] files = file.listFiles();
readfile(files);
}
public static void readfile(File[] files) {
if(files == null) {// 如果目录为空,直接退出
return;
}
for(File f:files) {
//如果是文件,直接输出名字
if(f.isFile()) {
System.out.println(f.getName());
}
//如果是文件夹,递归调用
else if(f.isDirectory()) {
readfile(f.listFiles());
}
}
}
}