HashMap就是Key-Value成对出现;HashSet就是可以只有key没有value,两者区别就是有没有伴随数据。
哈希表是无序组织的key
HashMap:put方法既是添加数据也是更新数据,当put中key在Map中没有就是添加,如果有的话就更新key的value值;同时也可以remove(key),删除key同时删除掉伴随数据。查的话,我们可以用containKey方法查这个key是不是存在同时也可以利用get方法查询key所对应的value。
HashSet:add,remove就是添加和删除,可以看这个元素是否存在与Set;contain方法查询key是否存在
哈希表在使用的时候时间复杂度都是常数级别(增、删、改、查)。
①哈希表的简单介绍
1>哈希表在使用层面可以理解为一种集合结构。
2>如果只有key,没有伴随数据value,可以使用HashSet结构
3>如果既有key,又有伴随数据value,可以使用HashMap结构
4>有无伴随数据,是HashSet和HashMap唯一区别,底层的实际结构是一回事
5>使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为O(1),但是常数时间比较大
6>放入哈希表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
7>放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是这个东西内存地址的大小(占用8byte)
TreeSet:只有key
TreeMap:有key和value
有序表是有序组织的key,哈希表能完成的功能有序表都能完成,同时还能根据key有序来添加功能
put:既是添加,又是更新,而且key是可以进行比较的,比如可以找出所有key中的最大和最小,或者小于等于某个key大于等于某个key。性能比哈希表差一点。增删改查都是O(logN)级别的
②有序表的简单介绍
1>有序表在使用层面上可以理解为一种集合结构
2>如果只有key,没有伴随数据value,可以使用TreeSet结构
3>如果既有key,又有伴随数据value,可以使用TreeMap结构
4>有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事
5>有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织
6>红黑树、AVL树,size-balance-tree和跳表都属于有序表结构,只是底层具体实现不同
7>放入有序表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
8>放入有序表的东西,如果不是基础类型,必须提供比较器,内部按引用传递,内存占用是这个东西内存地址的大小
9>不管是什么底层具体实现,只要是有序表,都有一下固定的基本功能和固定的时间复杂度
有序表的固定操作:
1>void put(K key,V value):将一个(key,value)记录加入到表中,或者将key的记录更新成value
2>V get(K key):根据指定的key,查询value并返回
3>void remove(K key):移除key的记录
4>Boolean containKey(K key):询问是否有关于key的记录
5>K firstKey():返回所有键值的排序结果中,最左(小)的那个
6>K lastKey():返回所有键值的排序结果中,最右(大)的那个
7>K floorKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中,key的前一个
8>K ceilingKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中,key的后一个
单向链表和双向链表都是,给一个头部head就可以找到后面的节点
一个节点的属性next是下一个节点
/**
* 1.反转单项链表
* 1->2->3 变成1<-2<-3
*/
public static singleNode reverseSingleNode(singleNode head){//传来一个头部
if(head == null || head.next == null){//如果head为null代表链表为空,返回null,如果head的下一个为null代表该链表就只有一个节点返回该节点
return head;
}
//定义该节点的上一个和下一个节点
//链表连接节点是通过next,无法回溯,所以我们定义preNode,而如果我们完成1 2节点的反转,就会丢失2 3节点的连接,所以我们要通过nextNode完成连接 用于保存上一个和下一个节点
singleNode preNode = null;
singleNode nextNode = null;
//当head为null的时候就代表已经反转完最后一个了
while(head != null){
nextNode = head.next;//需要将下一个节点的地址保存下来,否则我们将这个链表反转后,我们会找不到下一个链表,也就是我们的head找不到下一个node了
head.next = preNode;//将当前节点的next指向前一个节点的地址 完成1 2 节点的反转后,2 3 之间就没有next作为连接了,所以还想继续完成反转,我们需要让下一个节点
//
preNode = head;//由于你当前节点已经完成了链翻转,而我们依旧要完成下一个链的翻转,所以我们要让当前节点成为下一个节点的前一个(虽然已经断开连接)
head = nextNode;//我们需要将head移动到下一个节点的地址
}
return preNode;//这就是源链表的最后一个数也就是翻转后链表的头节点
}
/**
* 2.反转双向链表
* @param head
* @return
*/
public static doubleNode reverseDoubleNode(doubleNode head){
if(head == null || head.next == null){//如果head为null代表链表为空,返回null,如果head的下一个为null代表该链表就只有一个节点返回该节点
return head;
}
doubleNode nextNode = null;//定义下一个节点因为我们完成该节点和上一个节点的连接反转后,无法通过next或者是pre找到下一个节点,所以我们先把下一个节点保存下来方便寻找
doubleNode temp = null;//定义当前节点,用于最后返回
//当head为null的时候就代表已经反转完最后一个了
while (head != null){
nextNode = head.next;//将下一个节点先保存下来,避免找不到
head.next = head.pre;//将当前节点的next指针换到前一个节点,也就是当前节点的pre
head.pre = nextNode;//该节点的pre指针指到将保存起来的节点(原链表中的下一个节点)
//完成next和pre的变换后,我们需要换head
//需要一个变量保存当前值,用于返回
temp = head;//为什么要这个? 因为要保存当前节点用于最后的返回,节点后移继续反转
//将head移动到下一个节点,我们之前保存过那个地址
head = nextNode;
}
return temp;
}
有点类似于外部排序的过程
/**
* 3.给定两个有序链表的头指针head1和head2,打印两个链表 的公共部分(要求时间复杂度为O(n),额外空间复杂度O(1))
* 主体思想:
* 两个链表定义两个指针分别指向链表头部,判断两个指针所指向的节点的value是不是相同,如果相同一起向下移动,如果第一个小于第二个的话第一个下移第二个不动,反之亦然。
*/
public static void xiangtongLianBiao(singleNode s1,singleNode s2){//传参的时候可以传入两个链表的头部节点
//定义两个指针表明链表的当前位置
singleNod