数据结构(十五) -- 映射(Map)

实际上,借助关键码直接查找数据元素并对其进行操作的这一形式,已经为越来越多的数据结构所采用,也成为现代数据结构的一个重要特征。

本文将要讨论的映射(Map)及后面要介绍的词典(Dictionary)结构,就是其中最典型的例子,** 它们对优先队列中利用关键码的思想做了进一步发挥和推广——不再只是可以读取或修改最小元素,而是能够对任意给定的关键码进行查找,并修改相应的元素。**

与优先队列一样,映射和词典中存放的元素也是一组由关键码和数据合成的条目。

  • 映射要求不同条目的关键码互异

  • 词典则允许多个条目拥有相同的关键码。

一,映射(Map)

映射(Map)也是一种存放一组条目的容器。与优先队列一样,映射中的条目也是形如(key, value)的组合对象,其中 key 为关键码对象,value 为具体的数据对象。

需要特别指出的是,在映射中,各条目的关键码不允许重复冗余。比如,若准备将某个学校所有学生的记录组织为一个映射结构,则不能以年龄或班级作为关键码,因为不同记录的这些信息都有可能重复;反过来,通常学号都是学生的唯一标识,故可以将学号作为关键码。


二,映射的 ADT

映射ADT支持的操作:

操作方法 功能描述
get(key) 若 M 中存在以 key 为关键码的条目,则返回该条目的数据对象 ;否则,返回 null
输入:一个关键码对象
输出:数据对象
put(key, value) 若映射中不存在以 key 为关键码的条目,则将条目(key, value)加入到 M 中并返回 null ;否则,将已有条目的数据对象替换为 value,并返回原先的数据对象
输入:一个关键码对象和一个数据对象
输出:数据对象
remove(key) 若映射中存在以 key 为关键码的条目,则删除之并返回其数据对象;否则,返回 null
输入:一个关键码对象
输出:数据对象
entries() 返回映射中所有关键码对象的一个迭代器
输入:无
输出:条目对象的迭代器
getSize() 报告映射的规模,即其中元素的数目
输入:无
输出:非负整数
isEmpty() 判断映射是否为空
输入:无
输出:布尔标志

ps:在执行 get()、put() 或 remove()方法时,这里并未采用意外错的形式来处理,而是直接返回 null,之所以采取这一办法,是考虑到在实际应用中执行映射的这些方法时,出现退化情况的概率很高,若采用报意外错的方式,为了扔出与处理意外错需要耗费大量的时间,整个结构的效率将会大打折扣。

三,映射的实现

映射的接口:
package dsa.Map;

import dsa.Iterator.Iterator;

public interface Map {

    /*
     * 映射结构接口
     */
    // 查询映射结构当前的规模
    public int getSize();

    // 判断映射结构是否为空
    public boolean isEmpty();

    // 若映射中存在以key为关键码的条目,则返回该条目的数据对象;否则,返回null
    public Object get(Object key);

    // 若映射中不存在以key为关键码的条目,则插入条目(key, value)并返回null
    // 否则,将已有条目的数据对象替换为value,并返回原先的数据对象
    public Object put(Object key, Object value);

    // 若映射中存在以key为关键码的条目,则删除之并返回其数据对象;否则,返回null
    public Object remove(Object key);

    // 返回映射中所有条目的一个迭代器
    public Iterator entries();
}
判等器:

由其 ADT 描述可知,映射结构必须能够比较任意一对关键码是否相等,每个映射结构在被创建的时候,都需要指定某一具体标准,以便进行关键码的比较。因此,为了实现映射结构,首先必须实现这样的一个判等器(Equality tester):

package dsa.Map;

public interface EqualityTester {

    /*
     * 判等器接口
     */
    public boolean isEqualTo(Object a, Object b);// 若a与b相等,则返回true;否则,返回false
}
package dsa.Map;

public class EqualityTesterDefault implements EqualityTester {
    /*
     * 默认判等器
     */
    public EqualityTesterDefault() {
    }

    public boolean isEqualTo(Object a, Object b) {
        return (a.equals(b));
    }// 使用Java提供的判等器
}

尽管上面的默认判等器也是通过标准的equals()方法实现的,但重要的是,利用这种模式,程序员完全可以编写出独立的通用判等器而无需触及对象内部的结构。

基于列表实现映射类

实现映射结构的最简单办法,就是直接将映射 M 中的条目组织成双向链表形式的一个列表 L。

这样,getSize()和 isEmpty()方法可以直接套用 List 接口中对应的方法。而在 get(key)、put(key, value)和 remove(key)方法中为了确定操作条目的位置,可以将 S 中的元素逐一与给定的 key 做对比。

package dsa.Map;

import dsa.Iterator.Iterator;
import dsa.Iterator.IteratorElement;
import dsa.List.List;
import dsa.List.List_DLNode;
import dsa.PriorityQueue.Entry;
import dsa.PriorityQueue.EntryDefault;
import other.Position;

public class Map_DLNode implements Map {
    /*
     * 基于列表实现映射结构
     */
    private List L;// 存放条目的列表
    private EqualityTester T;// 判等器
    // 构造方法

    public Map_DLNode() {
        this(new EqualityTesterDefault());
    }

    // 默认构造方法
    public Map_DLNode(EqualityTester t) {
        L = new List_DLNode();
        T = t;
    }

    /***************************** ADT方法 *****************************/
    // 查询映射结构当前的规模
    public int getSize() {
        return L.getSize();
    }

    // 判断映射结构是否为空
    public boolean isEmpty() {
        return L.isEmpty();
    }

    // 若M中存在以key为关键码的条目,则返回该条目的数据对象;否则,返回null
    public Object get(Object key) {
        Iterator P = L.positions();
        while (P.hasNext()) {
            Position pos = (Position) P.getNext();
            Entry entry = (EntryDefault) pos.getElem();
            if (T.isEqualTo(entry.getKey(), key))
                return entry.getValue();
        }
        return null;
    }

    // 若M中不存在以key为关键码的条目,则将条目(key, value)加入到M中并返回null
    // 否则,将已有条目的数据对象替换为value,并返回原先的数据对象
    public Object put(Object key, Object value) {
        Iterator P = L.positions();
        while (P.hasNext()) {// 逐一对比
            Position pos = (Position) P.getNext();// 各个位置
            Entry entry = (EntryDefault) pos.getElem();// 处的条目
            if (T.isEqualTo(entry.getKey(), key)) {// 若发现key已出现在某个条目中,则
                Object oldValue = entry.getValue();// 先保留该条目原先的数据对象
                L.replace(pos, new EntryDefault(key, value));// 再替之以新数据对象
                return oldValue;// 最后返回原先的数据对象。注意:返回null时的歧义
            }
        } // 若此循环结束,说明key尚未在M中出现,因此
        L.insertFirst(new EntryDefault(key, value));// 将新条目插至表首,并
        return null;// 返回null标志
    }

    // 若M中存在以key为关键码的条目,则删除之并返回其数据对象;否则,返回null
    public Object remove(Object key) {
        Iterator P = L.positions();
        while (P.hasNext()) {// 逐一对比
            Position pos = (Position) P.getNext();// 各个位置
            Entry entry = (EntryDefault) pos.getElem();// 处的条目
            if (T.isEqualTo(entry.getKey(), key)) {// 若发现key已出现在某个条目中,则
                Object oldValue = entry.getValue();// 先保留该条目原先的数据对象
                L.remove(pos);// 删除该条目
                return oldValue;// 最后返回原先的数据对象。注意:返回null时的歧义
            }
        } // 若此循环结束,说明key尚未在映射中出现,因此
        return null;// 返回null标志
    }

    // 返回M中所有条目的一个迭代器
    public Iterator entries() {
        return new IteratorElement(L);
    }// 直接利用List接口的方法生成元素迭代器
}

上述实现虽然简单,但效率不高。为了执行其中的 get(key)、put(key, value)或 remove(key)方法,都需要扫描整个列表。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容