Set与Map是 javaSE api中的两个重要接口,Set中元素的不重复,Map中存放Key value。今天我们就先简单的实现下这两种数据结构。(至于TreeMap,TreeSet什么底层使用平恒二叉树了我们以后再实现)
声明本文的实现借助了链表二分搜索树这两种数据结构。
参考文章:数据结构之二分搜索树(BST)
参考文章:数据结构之链表
项目参考:DataStructure看这个也行,我的所有数据结构代码都在这。
要点
一 、集合具体实现(Set)
1、接口的定义
package setandmap;
/**
* Create by SunnyDay on 2019/03/06
*/
public interface Set<E> {
void add(E e);
void remove(E e);
boolean contains(E e);
int getSize();
boolean isEmpty();
}
2、基于链表为底层实现
package setandmap;
import LinkedList.LinkedList;
/**
* Create by SunnyDay on 2019/03/06
* <p>
* 以我们写的链表为基础 实现Set的简单功能
* 链表为我们自己写的非java原装的(参考前面我们封装的链表)
* 参考链接:https://blog.csdn.net/qq_38350635/article/details/86906834
*/
public class LinkedListSet<E> implements Set<E> {
private LinkedList<E> list;
public LinkedListSet() {
list = new LinkedList<E>();
}
/**
* 不能添加重复元素
* 检查是否包含,不包含再添加。
*/
@Override
public void add(E e) {
if (!list.contain(e)) {
list.addFirst(e);// 添加头部复杂度小为1
}
}
@Override
public void remove(E e) {
list.removeElement(e);
}
@Override
public boolean contains(E e) {
return list.contain(e);
}
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
}
可以看出我们使用链表作为底层实现的,就有一点要注意,添加元素不能有重复,判断下就行了。
3 基于二分搜索树为底层实现
package setandmap;
import binarySearchTree.BST;
/**
* Create by SunnyDay on 2019/03/06
* 以二分搜索树为基础实现简单的Set
*
* Set集合的具体应用 统计不重复的元素
*/
public class BSTSet<E extends Comparable<E>> implements Set<E> {
private BST<E> bst;// 以二分搜索树为成员 相关操作围绕此操作实现
public BSTSet() {
bst = new BST<>();
}
/**
* 所有操作使用bst操作就行
*
* */
@Override
public void add(E e) {
bst.add(e);
}
@Override
public void remove(E e) {
}
@Override
public boolean contains(E e) {
return bst.contain(e);
}
@Override
public int getSize() {
return bst.getSize();
}
@Override
public boolean isEmpty() {
return bst.isEmpty();
}
}
由于我们的二分搜索树具有不重复性所以直接调用之前我们写好的二分搜索树封装下就行了。
4、效率比较
首先看看链表为底层的实现:
1 添加元素首先要查找一遍(从头到尾)
2 删除 也需要便利一遍
3查询 需要遍历一遍
三者时间复杂度都为o(n)级别
在看看二分搜索树的遍历:
1 添加 删除 查询 只需要走一条路线,不需要全部遍历节点。最多走的路线也就是树的深度, 复杂度就是 o(h) h为树的高度。
2 满二叉树情况下 ,满二叉树的h层的节点 2^h个(参考下图)
ps:图片来源某课网截图
则两者的差距如下:
二、映射具体实现(Map)
1、接口的定义
package setandmap.map;
/**
* Create by SunnyDay on 2019/03/06
* 集合映射接口
*/
public interface Map<K, V> {
void add(K key, V value);
V remove(K key);
boolean contains(K key);
V get(K key);
void set(K key, V newValue);
int getSize();
boolean isEmpty();
}
2 、以链表方式实现
package setandmap.map;
/**
* Create by SunnyDay on 2019/03/07
* 以链表为基础实现 映射
* <p>
* 类的设计 api 从上到下顺序看 比较简单 作者按照这个逻辑 排版的
*/
public class LinkedListMap<K, V> implements Map<K, V> {
/**
* 节点
* 内部类 方便我们使用
*/
public class Node {
K key;
V value;
Node next;
/**
* 构造
* 完成节点元素初始化
*/
public Node(K key, V value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
/**
* 构造
* <p>
* 键不可以为空,通过键才能找到值
*/
public Node(K key) {
this(key, null, null);
}
/**
* 构造
* 各元素使用默认值
*/
public Node() {
this(null, null, null);
}
@Override
public String toString() {
return key.toString() + ":" + value.toString();
}
}
private int size;//容量
private Node dummyHead;// 虚拟头结点
/**
* 构造
* 对虚拟头结点,size 完成初始化
*/
public LinkedListMap() {
size = 0;
dummyHead = new Node();
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
/**
* 辅助类:本映射类的核心工具 使用后下面的api封装变得简单
*
* @param key
* @function 根据键获得节点的引用
*/
private Node getNode(K key) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.key.equals(key)) {
return cur;
}
cur = cur.next;
}
return null;//没有键为key的就返回空
}
/**
* 是否包含key键
* <p>
* 使用辅助类
*/
@Override
public boolean contains(K key) {
return getNode(key) != null;
}
/**
* 查找 key 对应的value
* <p>
* 使用辅助类
*/
@Override
public V get(K key) {
Node node = getNode(key);
return node == null ? null : node.value;
}
/**
* 添加元素 (映射中key是唯一的)
*/
@Override
public void add(K key, V value) {
Node node = getNode(key);// 获得key对应的引用 判断是否存在此元素
if (node == null) {
dummyHead.next = new Node(key, value, dummyHead.next);//插入表头
size++;
} else {
// TODO 已经存在key的话我们功能可以自己实现 抛异常或者覆盖已存在的key对应的value
node.value = value;//此处我们选择覆盖value值
}
}
/**
* 修改元素
*/
@Override
public void set(K key, V newValue) {
Node node = getNode(key);// 获得key对应的引用 判断是否存在此元素
if (node == null) {
throw new IllegalArgumentException("key is not exist");
} else {
node.value = newValue;
}
}
/**
* 删除key对应的元素
*
* @param key 参考链表的删除任意元素
*/
@Override
public V remove(K key) {
// 首先找到前一个节点
Node prev = dummyHead;
while (prev.next != null) {
if (prev.next.key.equals(key)) {
break;
}
prev = prev.next;
}
// 操作要删除的节点
if (prev.next != null) {
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;// call gc
size--;
return prev.next.value;
}
return null;
}
}
由于有key value此处我们就自己封装节点 ,但是思路还是我们封装链表的思路。留意下我们的辅助方法 getNode就行了。
3、以二分搜索树为基础实现
package setandmap.map;
/**
* Create by SunnyDay on 2019/03/07
* 使用二分搜索树实现简单的map
* <p>
* ps:map的kev必须具有可比较性(通过key才能找到value)
*/
public class BSTMap<K extends Comparable<K>, V> implements Map<K, V> {
/**
* 定义节点
*/
private class Node {
K key;
V value;
Node left;
Node right;
public Node(K key, V value) {
this.key = key;
this.value = value;
this.left = null;
this.right = null;
}
}
private Node root;
private int size;
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
/**
* 向二分搜索树中添加新的元素 key value
*/
@Override
public void add(K key, V value) {
root = add(root, key, value);
}
/**
* public add 的辅助函数
*/
private Node add(Node node, K key, V value) {
// 书写递归条件
if (node == null) {
size++;
return new Node(key, value);
}
if (key.compareTo(node.key) < 0) {
node.left = add(node.left, key, value);
} else if (key.compareTo(node.key) > 0) {
node.right = add(node.right, key, value);
} else { // key相等 时 value后者覆盖
node.value = value;
}
return node;
}
/**
* 辅助函数
* 返回以node为根节点的二分搜索树中key 所在的节点
*/
private Node getNode(Node node, K key) {
if (node == null) {
return null;
}
if (key.compareTo(node.key) == 0) {
return node;
} else if (key.compareTo(node.key) < 0) {
return getNode(node.left, key);
} else { // >0时
return getNode(node.right, key);
}
}
/**
* 使用getNode辅助函数
*/
@Override
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
/**
* 使用getNode 辅助方法
*/
@Override
public boolean contains(K key) {
return getNode(root, key) != null;
}
/**
* 使用getNode工具类
*/
@Override
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null) {
throw new IllegalArgumentException("key is not exist");
}
node.value = newValue;
}
/**
* 二分搜索树的删除借助了几个方法
*
* */
@Override
public V remove(K key) {
Node node = getNode(root, key);
if(node!=null){
root = remove(root,key);
return node.value;
}
return null;
}
/**
* 删除以node为根节点的二分搜索树的最小节点
* 返回删除节点后新的二分搜索树的节点
*/
private Node removeMin(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
/**
* 二分搜索树最小值
*
* */
private Node minimum(Node node) {
if (node.left == null) {
return node;
}
return minimum(node.left);
}
/**
* 删除以node为根的二分搜索树中 键为k的节点 递归算法
* <p>
* 返回删除节点后,新的二分搜索树的根
*/
private Node remove(Node node, K key) {
if (node == null) {
return null;
}
// 递归寻找
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
return node;
} else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
return node;
} else {
/*e.compareTo(node.e)==0
找到了删除节点
三种情况:
1 待删除的节点左子树为空
2 待删除节点的右子树为空
3 待删除节点左右子树都不为空
*/
if (node.left==null){
Node rightNode= node.right;
node.right = null;
size--;
return rightNode;
}
if (node.right==null){
Node leftNode= node.left;
node.left = null;
size--;
return leftNode;
}
/*
* 待删除节点的左右孩子都不为空时思路:
* 1 找到以待删除节点为根节点的最小节点
* 2 用这个节点顶替待删除节点
* */
Node successor = minimum(node.right);
successor.right = removeMin(node.right);
successor.left = node.left;
node.left=node.right=null;
return successor;
}
}
}
思路参考二分搜索的封装的思路
小结
本文就是简单的实现下Set Map这两个接口的一些功能,也算是Set Map的简单实现类了