數據結構之映射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\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,可以使用鏈表或者二分搜索樹進行實現。它們的時間複雜度,分別如下所示:

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章