自己动手实现一个抽象的字典(java语言)


介绍ADT字典的抽象框架是自己写的,目前还有很多不足。Java类库中有一个类似的java.util.Map,可以好好对比以下。

字典

ADT(抽象数据结构)字典又叫映射,表或关联数组,包含两部分:查找键key和对应值value

字典ADT的相关操作

我们思考一下,对于一个抽象的数据结构,一般包括插入,删除,查找,获取值,判断是否为空,获取大小等基本操作;

方法(K和V都是泛型)方法说明
+add(key:K,value:V):void向字典中添加一个键值对,其实返回值也可以是boolean类型的,添加成功返回true,失败返回false
+remove(key:K):V通过键值来移除某个数据,返回被移除的数据
+getValue(key:K):V通过键值获取数据并返回该数据
+contains(key:K):boolean判断该键值是否存在
+getKeyIterator():Iterator< K >获取键值的迭代器
+getValueIterator():Iterator< V>获取值的迭代器
+isEmpty():boolean判断字典是否为空
+getSize():integer获取字典的大小
+clear():void清除所有项

查找键key的设定不同,具体的实现方法也不同,key的设定有以下几种情况:

  • 唯一:如果该ADT中的查找键是唯一的,那么在添加相同的key时可以拒绝添加,也可以用新值替换掉原来的value;
  • 重复:如果查找键key允许重复,那么我们就要考虑getValue()方法的实现,是返回第一个value值呢,还是返回所有key相对应的value值;remove方法是移除第一个值呢,还是移除key对应得所有值;
  • 第二查找键:当字典中已有查找键,添加的时候可以考虑设置第二个查找键;

当然我们这里只讨论查找键唯一的情况。

字典ADT框架

import java.util.Iterator;
public interface DictionaryInteface <K,V>{

   public void add(K key,V value);
   public V remove(K key);
   public V getValue(K key);
   public boolean contains(K key);
   public Iterator<K> getKeyIterator();
   public Iterator<V> getValueIterator();
   public boolean isEmpty();
   public Integer getSize();
   public void clear();
}

java.util中就含有Map接口,和我们这里的Dictionary类似。

具体实现

字典的实现可以基于数组,也可以基于链表,都可分有序和无序来讨论。

基于数组实现

无序数组

我们用一个类实现这个接口,以下是对该类方法的一些说明:

  • 首先我们需要一个私有的内部类Entry,给它设置构造方法和get,set方法,但注意key一旦添加就不可变,所以没有setKey()方法;
  • ensureCapacity():基于数组实现,在添加完一项之后需要确保是否还有空间进行下一个元素的添加,如果空间不够,则扩大数组容量,这里将数组扩大为原来的两倍;
  • locateIndex():后序会有很多获取数组下标的工作,这里把这个方法分离出来能让我们的类的结构更加清晰;
  • 构造方法:这里我们用了两个构造方法,第一个构造方法调用第二个构造方法;
  • add()方法:因为字典不允许出现重复的key值,所以在添加前必须先进行查找,如果存在,则更新;不存在,则插入,插入完成之后还要调用ensureCapacity方法检查数组容量;
import java.util.Arrays;
import java.util.Iterator;

public class ArrayDictionary<K,V> implements DictionaryInteface<K,V>{

   private Entry<K,V>[]dictionary ;//字典数组
   private int numberOfEntries;//数组的元素个数
   private final static int DEFAULT_CAPACITY=25;//初始容量
   private final static int MAX_CAPACITY=10000;//最大容量

   public ArrayDictionary(){
       this(DEFAULT_CAPACITY);
   }
   public ArrayDictionary(int inialCapacity){
       Entry<K,V>[]tempDic=(Entry<K, V>[]) new Entry[inialCapacity];
       dictionary=tempDic;
       numberOfEntries=0;
   }
   public void ensureCapacity(){
       if(dictionary.length>=DEFAULT_CAPACITY){
           int newLength=2*dictionary.length;
           dictionary= Arrays.copyOf(dictionary,newLength);
       }
   }
   //获取下标
   public int locateIndex(K key){
       int index=0;
       while(index<numberOfEntries&&!key.equals(dictionary[index].getKey())){
           index++;
       }
       return index;
   }
   @Override
   public void add(K key, V value) {
       int keyindex=locateIndex(key);
       //字典数组中存在该key
       if(keyindex<numberOfEntries){
           dictionary[keyindex].setValue(value);
       }
       //不存在,则添加
       else{
           dictionary[keyindex]=new Entry<>(key,value);
           numberOfEntries++;
           ensureCapacity();
       }
   }

   @Override
   public V remove(K key) {
       int keyindex=locateIndex(key);
       V result=dictionary[keyindex].getValue();
       dictionary[keyindex]=null;
       numberOfEntries--;
       return result;
   }

   @Override
   public V getValue(K key) {
       return dictionary[locateIndex(key)].getValue();
   }

   @Override
   public boolean contains(K key) {
       int keyindex=locateIndex(key);
       return keyindex<numberOfEntries;
   }

   @Override
   public Iterator getKeyIterator() {
       return null;
   }

   @Override
   public Iterator getValueIterator() {
       return null;
   }

   @Override
   public boolean isEmpty() {
       return numberOfEntries>0;
   }

   @Override
   public Integer getSize() {
       return dictionary.length;
   }

   @Override
   public void clear() {
       for(int i=0;i<dictionary.length;i++){
           dictionary[i]=null;
       }
       numberOfEntries=0;
   }

   private class Entry<K,V>{
       private K key;
       private V value;
       //构造方法
       private Entry(K searchKey,V dataValue){
           key=searchKey;
           value=dataValue;
       }
//        获取Key
     private K getKey(){
           return key;
     }
//      获取value
     private V getValue(){
           return value;
       }
//        设置value
     private void setValue(V dataValue){
         value=dataValue;
     }
   }
}
有序数组

有序数组和无序数组在以下几个方法上略有区别:

  • locateIndex():因为数组是有序的,所以在查找下标的时候可以采用二分法,时间复杂度可以从线性减小为O(logn);
  • add():和基于无序数组不一样,有序数组不是随便添加在数组末尾,而是找到应该在的位置;
  • makeRoom():找到添加元素的位置后,应该移动有序数组中元素的位置,为待添加元素腾出空间,这就是makeRoom()方法要做的事情;
  • remove():移除一个元素之后数组会空出来,后面的元素要补上,添加一个私有方法removeRoom()来完成这件事情;
import java.util.Arrays;
import java.util.Iterator;

public class SortedArrayDictionary<K extends Comparable<? super K>,V> implements DictionaryInteface<K,V>{

    private Entry<K,V>[]dictionary ;//字典数组
    private int numberOfEntries;//数组的元素个数
    private final static int DEFAULT_CAPACITY=25;//初始容量
    private final static int MAX_CAPACITY=10000;//最大容量

    public SortedArrayDictionary(){
        this(DEFAULT_CAPACITY);
    }
    public SortedArrayDictionary(int inialCapacity){
        Entry<K,V>[]tempDic=(Entry<K, V>[]) new Entry[inialCapacity];
        dictionary=tempDic;
        numberOfEntries=0;
    }
    public void ensureCapacity(){
        if(dictionary.length>=DEFAULT_CAPACITY){
            int newLength=2*dictionary.length;
            dictionary= Arrays.copyOf(dictionary,newLength);
        }
    }
    //获取下标
    public int locateIndex(K key){
        int index=0;
        while(index<numberOfEntries&&key.compareTo(dictionary[index].getKey())<0){
            index++;
        }
        return index;
    }

    @Override
    public void add(K key, V value) {
        int keyindex=locateIndex(key);
        //字典数组中存在该key
        if(keyindex<numberOfEntries){
            dictionary[keyindex].setValue(value);
        }
        //不存在,则添加
        else{
            makeRoom(keyindex);
            dictionary[keyindex]=new Entry<>(key,value);
            numberOfEntries++;
            ensureCapacity();
        }
    }
    private void makeRoom(int keyIndex){
        for(int i=dictionary.length-1;i>=keyIndex;i--){
            dictionary[i+1]=dictionary[i];
        }
    }

    @Override
    public V remove(K key) {
        int keyindex=locateIndex(key);
        dictionary[keyindex]=null;
        removeRoom(keyindex);
        numberOfEntries--;
        return null;
    }
    private void removeRoom(int keyIndex){
        for(int i=keyIndex;i<dictionary.length;i++){
            dictionary[i]=dictionary[i+1];
        }
    }

    @Override
    public V getValue(K key) {
        return dictionary[locateIndex(key)].getValue();
    }

    @Override
    public boolean contains(K key) {
        return false;
    }

    @Override
    public Iterator getKeyIterator() {
        return null;
    }

    @Override
    public Iterator getValueIterator() {
        return null;
    }

    @Override
    public boolean isEmpty() {
        return numberOfEntries>0;
    }

    @Override
    public Integer getSize() {
        return dictionary.length;
    }

    @Override
    public void clear() {
        for(int i=0;i<dictionary.length;i++){
            dictionary[i]=null;
        }
    }

    private class Entry<K,V>{
        private K key;
        private V value;
        //构造方法
        private Entry(K searchKey,V dataValue){
            key=searchKey;
            value=dataValue;
        }
        //        获取Key
        private K getKey(){
            return key;
        }
        //      获取value
        private V getValue(){
            return value;
        }
        //        设置value
        private void setValue(V dataValue){
            value=dataValue;
        }
    }
}

基于链表实现

链式实现每一个内部类应该都含有key,value和下一个节点的指针

 private class Node{
        private K key;
        private V value;
        private Node next;
        //构造方法
        private Node(K searchKey,V dataValue,Node next){
            key=searchKey;
            value=dataValue;
            this.next=next;
        }
        private Node getNext(){
            return next;
        }
        private void setNext(Node next){
            this.next=next;
        }
        //        获取Key
        private K getKey(){
            return key;
        }
        //      获取value
        private V getValue(){
            return value;
        }
        //        设置value
        private void setValue(V dataValue){
            value=dataValue;
        }
    }
无序链表

对于无序链表,只有添加元素的复杂度为O(1),其他的操作都需要遍历,时间复杂度为O(n)。代码很简单,就不写了。

有序链表

有序链表和数组不一样,在获取时不能通过下标直接获得,而是先要获得前一个节点,再修改指针。

import java.util.Iterator;

public class SortedLinkedDictionary<K extends Comparable<? super K>,V> implements DictionaryInteface<K,V>{

    private int numberOfEntries=0;//节点个数
    public Node firstNode;

    public SortedLinkedDictionary(){
    }
    
    @Override
    public void add(K key, V value) {
        Node currentNode=firstNode;
        Node beforeNode=null;
        while(currentNode!=null&&key.compareTo(currentNode.getKey())>0){
            beforeNode=currentNode;
            currentNode=currentNode.getNext();
        }
        //字典数组中存在该key
        if(currentNode!=null&&key.equals(currentNode.getKey())){
            currentNode.setValue(value);
        }
        //不存在,则添加
        else {
            Node newNode=new Node(key,value);
            if(beforeNode==null){
                newNode.setNext(currentNode);
                firstNode=newNode;
            }else{
                newNode.setNext(currentNode);
                beforeNode.setNext(newNode);
            }
            numberOfEntries++;
        }
    }

    @Override
    public V remove(K key) {
        Node currentNode=firstNode;
        Node beforeNode=null;
        while(currentNode!=null&&key.compareTo(currentNode.getKey())>0){
            beforeNode=currentNode;
            currentNode=currentNode.getNext();
        }
        if(beforeNode==null){
            firstNode=currentNode.next;//删除的是第一个节点
        }else if(currentNode.getNext()==null){
            beforeNode.setNext(null);//删除的是最后一个节点
        }else{//删除中间的节点
            beforeNode.setNext(currentNode.getNext();
        }
        numberOfEntries--;
        return null;
    }
    @Override
    public V getValue(K key) {
        Node currentNode=firstNode;
        while(currentNode!=null&&key.compareTo(currentNode.getKey())>0){
            currentNode=currentNode.getNext();
        }
        return currentNode.getValue();
    }

    @Override
    public boolean contains(K key) {
        return false;
    }

    @Override
    public Iterator getKeyIterator() {
        return null;
    }

    @Override
    public Iterator getValueIterator() {
        return null;
    }

    @Override
    public boolean isEmpty() {
        return numberOfEntries>0;
    }

    @Override
    public Integer getSize() {
        return numberOfEntries;
    }

    @Override
    public void clear() {

    }

    private class Node{
        private K key;
        private V value;
        private Node next;
        //构造方法
        private Node(K searchKey,V dataValue,Node next){
            key=searchKey;
            value=dataValue;
            this.next=next;
        }
        private Node(K searchKey,V dataValue){
            key=searchKey;
            value=dataValue;
        }
        private Node getNext(){
            return next;
        }
        private void setNext(Node next){
            this.next=next;
        }
        //        获取Key
        private K getKey(){
            return key;
        }
        //      获取value
        private V getValue(){
            return value;
        }
        //        设置value
        private void setValue(V dataValue){
            value=dataValue;
        }
    }
}

附:以上只是最简单的框架,还有很多安全性,比如异常等都没有处理,比较抽象。

散列

数组可以为字典项提供地方,如果知道下标,就可以直接访问。散列(又叫哈希)就是只利用查找键就能知道下标的一个技术,这个下标就叫做散列索引。
散列函数:f(查找键)=散列索引
如果每个查找键通过散列函数得到的都是唯一且不重复的散列索引,那么这个散列就是完美的。但是上帝怎么可能允许完美的事物存在呢?几乎所有的散列值都有可能重复,这些是典型散列。

冲突

什么是冲突?举个例子,我们说散列就是通过key能够直接获取到value的技术,比如f(身份证号码)=特定的人,如果不同的身份证号码经过散列函数后得到的是同一个人,人人相撞,这就是冲突。
所以一个好的散列函数应该具有最少冲突且计算要快。解决冲突,就是散列的关键。

解决冲突

开放地址法

说白了就是发现冲突后进行遍历,知道找到一个未用元素为止。

线性探查
 //线性探查
    public int LinearIndex(K key,int index){
        boolean found=false;
        while(!found&&hashTable[index]!=null){
            if(key.equals(hashTable[index].getKey)){
                found=true;
            }else{
                index=(index+1)%hashTable.length;
            }
        }
       return index; 
    }

线性探查会导致散列表中一组连续的位置被占用,每一组称为一个簇,这种现象叫基本聚集。

二次探查
 //二次探查
    public int LinearIndex(K key,int index){
        boolean found=false;
        int i=0;
        while(!found&&hashTable[index]!=null){
            if(key.equals(hashTable[index].getKey)){
                found=true;
            }else{
                index=(index+i*i)%hashTable.length;
                i++;
            }
        }
       return index; 
    }

二次探测基本可以避免基本聚集,但与表中已有的项发生冲突的项,会使用相同的探测序列,增加了探查序列的长度,这种现象叫二级聚集,增加了查找时间。

双散列

对于线性探查和二次探测都不依赖查找键,而双散列顾名思义就是使用第二个散列函数来计算增量。尽量把表长设置为素数,则能到达散列表中的每个元素。

拉链法

说白了就是改变表的结构,让每个元素可以表示多个值,这样的元素称为。当新的查找键映射到某个元素时,只需将键和其对应得值放入桶中。获取值时,先找到对应的桶,再找到对应的值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值