zoukankan      html  css  js  c++  java
  • 数据结构之映射Map

    1、映射Map,存储键值数据对的数据结构(key,value),可以根据键key快速寻找到值Value,可以使用链表或者二分搜索树实现的。

    首先定义一个接口,可以使用链表或者二分搜索树进行实现。

     1 package com.map;
     2 
     3 /**
     4  * @ProjectName: dataConstruct
     5  * @Package: com.map
     6  * @ClassName: Map
     7  * @Author: biehl
     8  * @Description: ${description}
     9  * @Date: 2020/3/14 17:37
    10  * @Version: 1.0
    11  */
    12 public interface Map<K, V> {
    13 
    14     /**
    15      * 映射Map的添加操作,键值对的形式新增元素
    16      *
    17      * @param key
    18      * @param value
    19      */
    20     public void add(K key, V value);
    21 
    22     /**
    23      * 映射Map中根据key的值删除key-value键值对,将key对应的value返回
    24      *
    25      * @param key
    26      * @return
    27      */
    28     public V remove(K key);
    29 
    30     /**
    31      * 判断映射是否包含某个key
    32      *
    33      * @param key
    34      * @return
    35      */
    36     public boolean contains(K key);
    37 
    38     /**
    39      * 映射Map中根据key的值获取到键值对的值
    40      *
    41      * @param key
    42      * @return
    43      */
    44     public V get(K key);
    45 
    46     /**
    47      * 向映射Map中设置键值对,即将key对应的value值修改成新的value值。
    48      *
    49      * @param key
    50      * @param value
    51      */
    52     public void set(K key, V value);
    53 
    54     /**
    55      * 获取到映射的大小
    56      *
    57      * @return
    58      */
    59     public int getSize();
    60 
    61     /**
    62      * 判断映射Map是否为空
    63      *
    64      * @return
    65      */
    66     public boolean isEmpty();
    67 }

    1.1、基于链表的映射实现的映射Map。

      1 package com.map;
      2 
      3 import com.linkedlist.LinkedList;
      4 
      5 /**
      6  * @ProjectName: dataConstruct
      7  * @Package: com.map
      8  * @ClassName: LinkedListMap
      9  * @Author: biehl
     10  * @Description: ${description}
     11  * @Date: 2020/3/14 17:45
     12  * @Version: 1.0
     13  */
     14 public class LinkedListMap<K, V> implements Map<K, V> {
     15 
     16     // 链表是由一个一个节点组成
     17     private class Node {
     18         // 设置公有的,可以让外部类进行修改和设置值
     19         public K key;// 成员变量key存储键值对的键
     20         public V value;// 成员变量value存储键值对的值
     21         public Node next;// 成员变量next指向下一个节点,指向Node的一个引用
     22 
     23         /**
     24          * 含参构造函数
     25          *
     26          * @param key
     27          * @param value
     28          * @param next
     29          */
     30         public Node(K key, V value, Node next) {
     31             this.key = key;
     32             this.value = value;
     33             this.next = next;
     34         }
     35 
     36         /**
     37          * 无参构造函数
     38          */
     39         public Node() {
     40             this(null, null, null);
     41         }
     42 
     43         /**
     44          * 如果用户只传了key,那么可以调用含参构造函数,将next指定为null
     45          *
     46          * @param key
     47          */
     48         public Node(K key) {
     49             this(key, null, null);
     50         }
     51 
     52         /**
     53          * 重写toString方法
     54          *
     55          * @return
     56          */
     57         @Override
     58         public String toString() {
     59             return key.toString() + " : " + value.toString();
     60         }
     61 
     62     }
     63 
     64 
     65     private Node dummyHead;// Node类型的变量dummyHead,虚拟头节点
     66     private int size;// 链表要存储一个一个元素,肯定有大小,记录链表有多少元素
     67 
     68     /**
     69      * 无参的构造函数
     70      */
     71     public LinkedListMap() {
     72         // 虚拟头节点的元素是null空,初始化的时候next的值也为null空。
     73         dummyHead = new Node(null, null, null);// 初始化一个链表,虚拟头节点dummyHead是一个节点。
     74         // 链表大小是0,此时对于一个空的链表来说,是存在一个节点的,虚拟头节点。
     75         size = 0;// 大小size为0
     76     }
     77 
     78 
     79     /**
     80      * 获取链表的大小,获取链表中的元素个数
     81      *
     82      * @return
     83      */
     84     @Override
     85     public int getSize() {
     86         return size;
     87     }
     88 
     89     /**
     90      * 判断返回链表是否为空
     91      *
     92      * @return
     93      */
     94     @Override
     95     public boolean isEmpty() {
     96         return size == 0;
     97     }
     98 
     99 
    100     /**
    101      * 根据映射Map的key获取到这个节点。
    102      *
    103      * @param key
    104      * @return
    105      */
    106     private Node getNode(K key) {
    107         // 获取到指向虚拟头节点的下一个节点,就是头部节点。
    108         Node current = dummyHead.next;
    109         // 循环遍历,如果不为空,就继续遍历
    110         while (current != null) {
    111             // 如果当前节点的键值对的键和你想要查询的键相等的话,就返回该节点
    112             if (current.key.equals(key)) {
    113                 // 返回给当前要查询的节点
    114                 return current;
    115             }
    116             // 如果不相等的话,就向下一个节点移动即可。
    117             current = current.next;
    118         }
    119         // 如果链表遍历完了,没有找到,就直接返回空即可。
    120         return null;
    121     }
    122 
    123     @Override
    124     public void add(K key, V value) {
    125         // 不允许有相同的key,即key不可以重复的。
    126 
    127         // 首先判断是否已经存在该key
    128         Node node = this.getNode(key);
    129         // 如果新增的key的值和保存的键值对的key值相等,那么这个新的key不能新增
    130         // 如果没有查到这个node,说明就可以新增了
    131         if (node == null) {
    132             // 这里是在链表的头部添加元素。
    133             // 在虚拟头节点的下一个节点,就是头部节点添加这个键值对
    134             dummyHead.next = new Node(key, value, dummyHead.next);
    135             // 维护size大小
    136             size++;
    137         } else {
    138             // 如果已经保存了该键值对。那么将新增的键值对的key对应的value值替换之前的value值
    139             node.value = value;
    140         }
    141 
    142     }
    143 
    144     @Override
    145     public V remove(K key) {
    146         // 定义一个起始节点,该节点从虚拟头节点开始
    147         Node prev = dummyHead;
    148         // 循环遍历,当前节点的下一个节点不为空就一直遍历
    149         while (prev.next != null) {
    150             // 如果当前节点的key的值等于想要删除的节点的key的值
    151             // 那么,此时prev的下一个节点保存的key,就是将要删除的节点。
    152             if (prev.next.key.equals(key)) {
    153                 // 中断此循环
    154                 break;
    155             }
    156             // 如果不是想要删除的节点,继续向下遍历
    157             prev = prev.next;
    158         }
    159 
    160         // 此时,如果prev的下一个节点不为空,那么该节点就是将要被删除的节点
    161         if (prev.next != null) {
    162             // 获取到这个将要被删除的节点
    163             Node delNode = prev.next;
    164             // 把将要被删除的这个节点的下一个节点赋值给prev这个节点的下一个节点,
    165             // 就是直接让prev指向将要被删除的节点的下一个节点。
    166             prev.next = delNode.next;
    167             // 此时将被删除的节点置空
    168             delNode.next = null;
    169             // 维护size的大小
    170             size--;
    171             // 返回被删除的节点元素
    172             return delNode.value;
    173         }
    174         // 如果没有找到待删除的节点,返回空
    175         return null;
    176     }
    177 
    178     @Override
    179     public boolean contains(K key) {
    180         // 根据key判断映射key里面是否包含key
    181         Node node = this.getNode(key);
    182         // 如果获取到node不为空,说明有这个元素,返回true。
    183         if (node != null) {
    184             return true;
    185         }
    186         return false;
    187     }
    188 
    189     @Override
    190     public V get(K key) {
    191         // 根据key获取到键值对的value值
    192 //        Node node = this.getNode(key);
    193 //        // 如果查询的节点返回不为空,说明存在该节点
    194 //        if (node != null) {
    195 //            // 返回该节点的value值
    196 //            return node.value;
    197 //        }
    198 //        return null;
    199 
    200         // 根据key获取到键值对的value值
    201         Node node = this.getNode(key);
    202         return node == null ? null : node.value;
    203     }
    204 
    205     @Override
    206     public void set(K key, V value) {
    207         // 首先判断是否已经存在该key
    208         Node node = this.getNode(key);
    209         // 如果node节点等于空
    210         if (node == null) {
    211             throw new IllegalArgumentException(key + " doesn't exist!");
    212         } else {
    213             // 如果映射Map中存在了该键值对,那么进行更新操作即可
    214             node.value = value;
    215         }
    216     }
    217 
    218     public static void main(String[] args) {
    219         LinkedListMap<Integer, Integer> linkedListMap = new LinkedListMap<Integer, Integer>();
    220         // 基于链表实现的映射的新增
    221         for (int i = 0; i < 100; i++) {
    222             linkedListMap.add(i, i * i);
    223         }
    224 
    225 //        for (int i = 0; i < linkedListMap.getSize(); i++) {
    226 //            System.out.println(linkedListMap.get(i));
    227 //        }
    228 
    229 
    230         // 基于链表实现的映射的修改
    231         linkedListMap.set(0, 111);
    232 //        for (int i = 0; i < linkedListMap.getSize(); i++) {
    233 //            System.out.println(linkedListMap.get(i));
    234 //        }
    235 
    236         // 基于链表实现的映射的删除
    237         Integer remove = linkedListMap.remove(0);
    238 //        for (int i = 0; i < linkedListMap.getSize(); i++) {
    239 //            System.out.println(linkedListMap.get(i));
    240 //        }
    241 
    242         // 基于链表实现的映射的获取大小
    243         System.out.println(linkedListMap.getSize());
    244     }
    245 
    246 }

    1.2、基于二分搜索树实现的映射Map。

      1 package com.map;
      2 
      3 import com.tree.BinarySearchTree;
      4 
      5 /**
      6  * @ProjectName: dataConstruct
      7  * @Package: com.map
      8  * @ClassName: BinarySearchTreeMap
      9  * @Author: biehl
     10  * @Description: ${description}
     11  * @Date: 2020/3/14 19:57
     12  * @Version: 1.0
     13  */
     14 public class BinarySearchTreeMap<K extends Comparable<K>, V> implements Map<K, V> {
     15 
     16     // 二分搜索树的节点类,私有内部类。
     17     private class Node {
     18         private K key;// 存储元素key;
     19         private V value;// 存储元素value;
     20         private Node left;// 指向左子树,指向左孩子。
     21         private Node right;// 指向右子树,指向右孩子。
     22 
     23         /**
     24          * 含参构造函数
     25          *
     26          * @param key
     27          */
     28         public Node(K key, V value) {
     29             this.key = key;// 键值对的key。
     30             this.value = value;// 键值对的value
     31             left = null;// 左孩子初始化为空。
     32             right = null;// 右孩子初始化为空。
     33         }
     34     }
     35 
     36 
     37     private Node root;// 根节点
     38     private int size;// 映射Map存储了多少个元素
     39 
     40     /**
     41      * 无参构造函数,和默认构造函数做的事情一样的。
     42      */
     43     public BinarySearchTreeMap() {
     44         // 初始化的时候,映射Map一个元素都没有存储
     45         root = null;
     46         size = 0;// 大小初始化为0
     47     }
     48 
     49 
     50     /**
     51      * 返回以node为根节点的二分搜索树中,key所在的节点。
     52      *
     53      * @param node
     54      * @param key
     55      * @return
     56      */
     57     private Node getNode(Node node, K key) {
     58 
     59         // 如果未找到指定的键值对的键值,那么直接返回空即可。
     60         if (node == null) {
     61             return null;
     62         }
     63 
     64         // 如果查询的key值和该节点的key值相等,直接返回该节点即可
     65         if (key.compareTo(node.key) == 0) {
     66             return node;
     67         } else if (key.compareTo(key) < 0) {
     68             // 如果查询的key值小于该节点的key值,那么向该节点的左子树查询
     69             return getNode(node.left, key);
     70         } else if (key.compareTo(key) > 0) {
     71             // 如果查询的key值大于该节点的key值,那么向该节点的右子树查询
     72             return getNode(node.right, key);
     73         }
     74         return null;
     75     }
     76 
     77 
     78     /**
     79      * 返回以node为根的二分搜索树的最小值所在的节点
     80      *
     81      * @param node
     82      * @return
     83      */
     84     public Node minimum(Node node) {
     85         // 递归算法,第一部分,终止条件,如果node.left是空了,直接返回node节点
     86         if (node.left == null) {
     87             return node;
     88         }
     89         // 递归算法,第二部分,向node的左子树遍历
     90         return minimum(node.left);
     91     }
     92 
     93     /**
     94      * 删除掉以node为根的二分搜索树中的最小节点
     95      * 返回删除节点后新的二分搜索树的根
     96      *
     97      * @param node
     98      * @return
     99      */
    100     private Node removeMin(Node node) {
    101         if (node.left == null) {
    102             Node rightNode = node.right;
    103             node.right = null;
    104             size--;
    105             return rightNode;
    106         }
    107 
    108         node.left = removeMin(node.left);
    109         return node;
    110     }
    111 
    112 
    113     /**
    114      * 向映射Map中添加新的键值对。
    115      *
    116      * @param key
    117      * @param value
    118      */
    119     @Override
    120     public void add(K key, V value) {
    121         // 此时,不需要对root为空进行特殊判断。
    122         // 向root中插入元素e。如果root为空的话,直接返回一个新的节点,将元素存储到该新的节点里面。
    123         root = add(root, key, value);
    124     }
    125 
    126     /**
    127      * 向以node为根的二分搜索树中插入元素键值对key-value,递归算法
    128      * 返回以插入心节点后二分搜索树饿根
    129      *
    130      * @param node
    131      * @param key
    132      * @param value
    133      * @return
    134      */
    135     private Node add(Node node, K key, V value) {
    136         if (node == null) {
    137             // 维护size的大小。
    138             size++;
    139             // 如果此时,直接创建一个Node的话,没有和二叉树挂接起来。
    140             // 如何让此节点挂接到二叉树上呢,直接将创建的节点return返回回去即可,返回给调用的上层。
    141             return new Node(key, value);
    142         }
    143 
    144         // 递归的第二部分。递归调用的逻辑。
    145         if (key.compareTo(node.key) < 0) {
    146             // 如果待插入元素e小于node的元素e,递归调用add方法,参数一是向左子树添加左孩子。
    147             // 向左子树添加元素e。
    148             // 向左子树添加元素e的时候,为了让整颗二叉树发生改变,在node的左子树中插入元素e,
    149             // 插入的结果,有可能是变化的,所以就要让node的左子树连接住这个变化。
    150 
    151             // 注意,如果此时,node.left是空的话,这次add操作相应的就会返回一个新的Node节点,
    152             // 对于这个新的节点,我们的node.left被赋值这个新的节点,相当于我们就改变了整棵二叉树。
    153             node.left = add(node.left, key, value);
    154         } else if (key.compareTo(node.key) > 0) {
    155             // 如果待插入元素e大于node的元素e,递归调用add方法,参数一是向右子树添加右孩子。
    156             // 向右子树添加元素e。
    157             node.right = add(node.right, key, value);
    158         } else if (key.compareTo(node.key) == 0) {
    159             // 如果想要插入的值是已经存在与映射里面了,那么将value值替换就行了。
    160             node.value = value;
    161         }
    162 
    163         return node;
    164     }
    165 
    166     /**
    167      * 从二分搜索树中删除元素为key的节点
    168      *
    169      * @param key
    170      * @return
    171      */
    172     @Override
    173     public V remove(K key) {
    174         Node node = getNode(root, key);
    175         if (node != null) {
    176             root = remove(root, key);
    177             return node.value;
    178         }
    179         // 不存在该节点
    180         return null;
    181     }
    182 
    183     /**
    184      * 删除掉以node为根的二分搜索树中健为key的节点,递归算法
    185      * 返回删除节点后新的二分搜索树的根
    186      *
    187      * @param node
    188      * @param key
    189      * @return
    190      */
    191     private Node remove(Node node, K key) {
    192         if (node == null) {
    193             return null;
    194         }
    195 
    196         // 递归函数,开始近逻辑
    197         // 如果待删除元素e和当前节点的元素e进行比较,如果待删除元素e小于该节点的元素e
    198         if (key.compareTo(node.key) < 0) {
    199             // 此时,去该节点的左子树,去找到待删除元素节点
    200             // 递归调用,去node的左子树,去删除这个元素e。
    201             // 最后将删除的结果赋给该节点左子树。
    202             node.left = remove(node.left, key);
    203             return node;
    204         } else if (key.compareTo(node.key) > 0) {
    205             // 如果待删除元素e大于该节点的元素e
    206             // 去当前节点的右子树去寻找待删除元素节点
    207             // 将删除后的结果返回给当前节点的右孩子
    208             node.right = remove(node.right, key);
    209             return node;
    210         } else {
    211             // 当前节点元素e等于待删除节点元素e,即e == node.e,
    212             // 相等的时候,此时就是要删除这个节点的。
    213 
    214             // 如果当前节点node的左子树为空的时候,待删除节点左子树为空的情况
    215             if (node.left == null) {
    216                 // 保存该节点的右子树
    217                 Node rightNode = node.right;
    218                 // 将node和这棵树断开关系
    219                 node.right = null;
    220                 // 维护size的大小
    221                 size--;
    222                 // 返回原来那个node的右孩子。也就是右子树的根节点,此时就将node删除掉了
    223                 return rightNode;
    224             }
    225 
    226             // 如果当前节点的右子树为空,待删除节点右子树为空的情况。
    227             if (node.right == null) {
    228                 // 保存该节点的左子树
    229                 Node leftNode = node.left;
    230                 // 将node节点和这棵树断开关系
    231                 node.left = null;
    232                 // 维护size的大小
    233                 size--;
    234                 //返回原来那个节点node的左孩子,也就是左子树的根节点,此时就将node删除掉了。
    235                 return leftNode;
    236             }
    237 
    238             // 待删除节点左右子树均为不为空的情况。
    239             // 核心思路,找到比待删除节点大的最小节点,即待删除节点右子树的最小节点
    240             // 用这个节点顶替待删除节点的位置。
    241 
    242             // 找到当前节点node的右子树中的最小节点,找到比待删除节点大的最小节点。
    243             // 此时的successor就是node的后继。
    244             Node successor = minimum(node.right);
    245             // 此时将当前节点node的右子树中的最小节点删除掉,并将二分搜索树的根节点返回。
    246             // 将新的二分搜索树的根节点赋值给后继节点的右子树。
    247             successor.right = removeMin(node.left);
    248 
    249             // 因为removeMin操作,删除了一个节点,但是此时当前节点的右子树的最小值还未被删除
    250             // 被successor后继者指向了。所以这里做一些size加加操作,
    251             size++;
    252 
    253             // 将当前节点的左子树赋值给后继节点的左子树上。
    254             successor.left = node.left;
    255             // 将node节点没有用了,将node节点的左孩子和右孩子置空。让node节点和二分搜索树脱离关系
    256             node.left = node.right = null;
    257 
    258             // 由于此时,将当前节点node删除掉了,所以这里做一些size减减操作。
    259             size--;
    260 
    261             // 返回后继节点
    262             return successor;
    263         }
    264 
    265     }
    266 
    267     @Override
    268     public boolean contains(K key) {
    269         return this.getNode(root, key) != null;
    270     }
    271 
    272     @Override
    273     public V get(K key) {
    274         Node node = this.getNode(root, key);
    275         return node == null ? null : node.value;
    276     }
    277 
    278     @Override
    279     public void set(K key, V value) {
    280         Node node = getNode(root, key);
    281         // 如果映射中没有该节点
    282         if (node == null) {
    283             throw new IllegalArgumentException(key + " doesn't exist!");
    284         } else {
    285             node.value = value;
    286         }
    287     }
    288 
    289     /**
    290      * 获取到映射Map的大小
    291      *
    292      * @return
    293      */
    294     @Override
    295     public int getSize() {
    296         return size;
    297     }
    298 
    299     /**
    300      * 判断映射Map是否为空
    301      *
    302      * @return
    303      */
    304     @Override
    305     public boolean isEmpty() {
    306         return size == 0;
    307     }
    308 
    309     /**
    310      * @return
    311      */
    312     @Override
    313     public String toString() {
    314         StringBuilder stringBuilder = new StringBuilder();
    315         // 使用一种形式展示整个二分搜索树,可以先展现根节点,再展现左子树,再展现右子树。
    316         // 上述这种过程就是一个前序遍历的过程。
    317         // 参数一,当前遍历的二分搜索树的根节点,初始调用的时候就是root。
    318         // 参数二,当前遍历的这棵二分搜索树的它的深度是多少,根节点的深度是0。
    319         // 参数三,将字符串传入进去,为了方便生成字符串。
    320         generateBSTString(root, 0, stringBuilder);
    321 
    322         return stringBuilder.toString();
    323     }
    324 
    325     /**
    326      * 生成以node为根节点,深度为depth的描述二叉树的字符串。
    327      *
    328      * @param node          节点
    329      * @param depth         深度
    330      * @param stringBuilder 字符串
    331      */
    332     private void generateBSTString(Node node, int depth, StringBuilder stringBuilder) {
    333         // 递归的第一部分
    334         if (node == null) {
    335             // 显示的,将在字符串中追加一个空字符串null。
    336             // 为了表现出当前的空节点对应的二分搜索树的层次,封装了一个方法。
    337             stringBuilder.append(generateDepthString(depth) + "null
    ");
    338             return;
    339         }
    340 
    341 
    342         // 递归的第二部分
    343         // 当当前节点不为空的时候,就可以直接访问当前的node节点了。
    344         // 将当前节点信息放入到字符串了
    345         stringBuilder.append(generateDepthString(depth) + node.key + "
    ");
    346 
    347         // 递归进行调用
    348         generateBSTString(node.left, depth + 1, stringBuilder);
    349         generateBSTString(node.right, depth + 1, stringBuilder);
    350     }
    351 
    352     /**
    353      * 为了表现出二分搜索树的深度
    354      *
    355      * @param depth
    356      * @return
    357      */
    358     private String generateDepthString(int depth) {
    359         StringBuilder stringBuilder = new StringBuilder();
    360         for (int i = 0; i < depth; i++) {
    361             stringBuilder.append("--");
    362         }
    363         return stringBuilder.toString();
    364     }
    365 
    366     public static void main(String[] args) {
    367         BinarySearchTreeMap<Integer, Integer> binarySearchTreeMap = new BinarySearchTreeMap<Integer, Integer>();
    368         // 基于链表实现的映射的新增
    369         for (int i = 0; i < 100; i++) {
    370             binarySearchTreeMap.add(i, i * i);
    371         }
    372         System.out.println(binarySearchTreeMap.toString());
    373 
    374 
    375         // 基于链表实现的映射的修改
    376         binarySearchTreeMap.set(0, 111);
    377 //        for (int i = 0; i < linkedListMap.getSize(); i++) {
    378 //            System.out.println(linkedListMap.get(i));
    379 //        }
    380 
    381         // 基于链表实现的映射的删除
    382         Integer remove = binarySearchTreeMap.remove(0);
    383 //        for (int i = 0; i < linkedListMap.getSize(); i++) {
    384 //            System.out.println(linkedListMap.get(i));
    385 //        }
    386 
    387         // 基于链表实现的映射的获取大小
    388         System.out.println(binarySearchTreeMap.getSize());
    389     }
    390 }

    2、数据结构之映射Map,可以使用链表或者二分搜索树进行实现。它们的时间复杂度,分别如下所示:

     

  • 相关阅读:
    激活win10系统的方法(亲测)
    java使用jxl,自动导出数据excle,quartz自动发送邮件
    java对接网银支付案例
    将数字转换成Excel表头格式的字母序号
    mysql date_add日期函数的使用
    itext A4纸张横向创建PDF
    Spring 定时器 定时访问数据库并发送邮件
    mvc路由报错
    eval()和$.parseJSON()注意事项
    跨域学习笔记
  • 原文地址:https://www.cnblogs.com/biehongli/p/12493663.html
Copyright © 2011-2022 走看看