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\n"); 338 return; 339 } 340 341 342 // 遞歸的第二部分 343 // 噹噹前節點不爲空的時候,就可以直接訪問當前的node節點了。 344 // 將當前節點信息放入到字符串了 345 stringBuilder.append(generateDepthString(depth) + node.key + "\n"); 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,可以使用鏈表或者二分搜索樹進行實現。它們的時間複雜度,分別如下所示: