01玩转数据结构_07_集合和映射

集合和 映射:

set  & map  

前面介绍了二分搜索树的底层实现,这里介绍两个高层的数据结构:集合 和 映射

什么叫高层的数据结构呢?

类似于栈和队列,这种就像我们定义好了相应的使用接口 ,以及它本身所具有的性质,然后我们就可以使用它。

但是这种高层的数据结构的底层其实是可以多种多样的,比如说 栈和队列 的底层实现既可以是 动态数组 也可以是 链表,这里的 Set 和 Map 也是如此。

Set:

它这个容器中的元素 不能重复, 它的主要应用就是 可以帮助我们去重,

上一节的二分搜索树 中我们知道,它里面不能放重复的元素,所以 二分搜索树是个非常好的 实现 ‘集合’ 的底层数据结构!! 

Set接口(自己定义):

这里最主要的是 add() 操作,它不能添加重复元素,

使用二分搜索树作为底层结构 实现自定义集合:

容易发现,这些接口,二分搜索树都是可以实现的,我们这里是 用  二分搜索树 封装一下,就实现自己的集合了,  

  1 package zcb.demo01;
  2 import java.util.LinkedList;
  3 import java.util.Queue;
  4 import java.util.Stack;
  5 
  6 public class BinarySearchTree<T extends Comparable<T>> {
  7     //这里对树中存储的类型做了限制,它必须具有可比较性。
  8     private class Node{
  9         public T t;
 10         public Node left,right;
 11 
 12         //Node 构造函数
 13         public Node(T t){
 14             this.t = t;
 15             this.left = null;
 16             this.right = null;
 17         }
 18 
 19     }
 20 
 21     //二分搜索树 的成员变量:1,根节点,2,size
 22     private Node root;
 23     private int size;
 24     //二分搜索树的构造函数
 25     public BinarySearchTree(){
 26         //此时一个元素都没有存
 27         this.root = null ;
 28         this.size = 0;
 29     }
 30 
 31     public int getSize(){
 32         return this.size;
 33     }
 34     public boolean isEmpty(){
 35         return this.size == 0;
 36     }
 37 
 38     //向二分搜索树 中添加新的元素e
 39     public void add(T t){
 40         this.root = add(this.root,t);
 41     }
 42     //向 以node 为根节点 的 二分查找树中插入 元素
 43     //返回 插入新节点后 二分搜索树的根
 44     private Node add(Node node,T t) {
 45         if (node == null) { //上次的终止条件 太过于庞杂,其实这样就可以了。
 46             this.size ++;
 47             return new Node(t);
 48         }
 49         if (t.compareTo(node.t) < 0){
 50             node.left = add(node.left, t);
 51         }
 52         else if(t.compareTo(node.t) >0 ){
 53             node.right = add(node.right, t);
 54         }
 55         return node;
 56     }
 57 
 58 
 59     //看 二分搜索树 中是否存在某个元素 t
 60     public boolean contains(T t){
 61 
 62         return contains(this.root,t);
 63     }
 64 
 65     //看 以node 为根的二分搜索树 中是否存在某个元素 t
 66     private boolean contains(Node node,T t){
 67         if(node == null){
 68             return false;
 69         }
 70         if(t.compareTo(node.t) == 0)
 71             return true;
 72         else if(t.compareTo(node.t) < 0) //此时只可能出现在左子树。
 73             return contains(node.left,t);
 74         else                            //此时只可能出现在右子树。
 75             return contains(node.right,t);
 76     }
 77 
 78     // 二分搜索树的前序遍历
 79     public void preOrder(){
 80         preOrder(this.root);
 81 
 82     }
 83     //遍历  以 node 为根节点的二分搜索树
 84     private void preOrder(Node node){
 85         if(node == null)
 86             return;
 87         else
 88             System.out.println(node.t); //t一定是可以比较的。上面接口已经限制了。
 89 
 90         preOrder(node.left); //先递归完 左子树之后,再递归右子树。
 91         preOrder(node.right);
 92     }
 93 
 94     // 二分搜索树的前序遍历 (使用非递归 NR 实现,利用 栈 来实现!!!)
 95     public void preOrderNR(){
 96         Stack<Node> stk = new Stack<>();
 97         stk.push(this.root); //将根节点的值压入栈中。
 98 
 99         while (!stk.isEmpty()){
100             Node temp = stk.pop();
101             System.out.println(temp.t); //访问
102 
103             if(temp.right != null)
104                 stk.push(temp.right);
105             if(temp.left != null)
106                 stk.push(temp.left);
107         }
108     }
109 
110 
111     //二分搜索树的中序遍历  先左 中 然后再右
112     public void midOrder(){
113         midOrder(this.root);
114     }
115     //中序遍历 遍历以node 为根节点的树
116     private void midOrder(Node node){
117         if(node == null)
118             return;
119 
120         //先遍历该节点的左子树
121         midOrder(node.left);
122         System.out.println(node.t);
123         midOrder(node.right);
124     }
125 
126     //二分搜索树后序遍历  先左 右 然后 当前节点
127     public void lastOrder(){
128         lastOrder(this.root);
129     }
130     //后序遍历 遍历 以node 为根节点的二叉搜索树
131     private void lastOrder(Node node){
132         if(node == null)
133             return;
134 
135         //先遍历 左子树
136         lastOrder(node.left);
137         lastOrder(node.right);
138         System.out.println(node.t);
139     }
140 
141     //二分搜索树的 层序遍历
142     public void levelOrder(){
143         Queue<Node> queue = new LinkedList<>(); //因为 Queue本质是个接口,我们要选择它的一个实现类
144 
145         queue.add(this.root);
146 
147         while (!queue.isEmpty()){
148             Node temp = queue.remove();
149             System.out.println(temp.t); //访问
150 
151             if(temp.left != null)
152                 queue.add(temp.left);
153             if(temp.right != null)
154                 queue.add(temp.right);
155         }
156     }
157 
158     //寻找二分搜索树中的最小值
159     public T getMinimum(){
160         if(this.size == 0)
161             throw new IllegalArgumentException("此时,树中为空!");
162 
163         return getMinimum(this.root).t;
164     }
165     //返回以 node为根节点中的 最小值 。
166     private Node getMinimum(Node node){
167         if(node.left == null)
168             return node;
169         return getMinimum(node.left);
170     }
171 
172 
173     //寻找二分搜索树中的最大值
174     public T getMaximum(){
175         if(this.size == 0)
176             throw new IllegalArgumentException("此时,树中为空!");
177         return getMaximum(this.root).t;
178     }
179     //返回以 node为根节点中的 最大值 。
180     private Node getMaximum(Node node){
181         if(node.right == null)
182             return node;
183         return getMaximum(node.right);
184     }
185 
186     //从二分搜索树中 删除最小值所在的节点。返回最小值
187     public T removeMin(){
188         T ret = getMinimum();  //获取最小值
189 
190         //删除最小值所在的节点。
191         this.root = removeMin(this.root);
192         return ret;
193     }
194     //删除掉 以 node为根节点的树中的最小节点,
195     //并将删除节点后的  新的二分搜索树的根节点 返回出来
196     private Node removeMin(Node node){
197         if(node.left == null) {
198             Node temp = node.right; //此时node.right 可能为null ,也可能不为空,
199             node.right = null;
200             this.size --;
201             return temp;
202         }
203 
204         node.left = removeMin(node.left);
205         return node;
206     }
207 
208     //从二分搜索树中 删除最大值所在的节点。返回最大值
209     public T removeMax(){
210         T ret = getMaximum();
211 
212         this.root = removeMax(this.root);
213         return ret;
214     }
215 
216     //删除 以 node 为根节点 的二叉树,
217     //返回 删除最小值节点 之后的新的 二叉树的 根节点
218     private Node removeMax(Node node){
219         if(node.right == null){
220             Node temp = node.left; //此时node.left 可能为null ,也可能不为空,
221             node.left = null;
222             this.size --;
223             return temp;
224         }
225         node.right = removeMax(node.right);
226         return node;
227     }
228 
229     //从二分搜索树上 删除任意元素 t
230     public void remove(T t){
231         this.root = remove(this.root,t);
232     }
233     // 删除 以 Node 为根节点的二分搜索树中的任意值
234     // 返回删除之后 新的二分搜索树的 根节点
235     private Node remove(Node node,T t){
236         if(node == null)
237             return null; //没找到要删除的t ,此时什么都不干!
238         if(t.compareTo(node.t) < 0) {
239             node.left = remove(node.left, t);
240             return node;
241         }
242         else if(t.compareTo(node.t) >0) {
243             node.right = remove(node.right, t);
244             return node;
245         }else{
246             //此时就找到了要删除的元素!
247             if(node.left == null){  //此时,是只有右子树的情况 右子树也可能为null
248                 Node temp = node.right;
249                 node.right = null;
250                 this.size --;
251                 return temp;
252             }
253             if(node.right == null){  //此时,是只有左子树的情况  左子树也可能为null
254                 Node temp = node.left;
255                 node.left = null;
256                 this.size --;
257                 return temp;
258             }
259             //此时 是该节点既有左子树,又有右子树,而且左右子树都不为null
260             Node temp = getMinimum(node.right); // 找到当前节点 的右子树中的 最小节点。
261             temp.right = removeMin(node.right);  //删除 右子树的最小节点,并将右子树的根节点 连在temp。right上。
262             temp.left = node.left;
263             //此时的node 就可以被删除了。
264             node.left = node.right = null;     //或者用 node =null;  直接删除
265 //            this.size --;  它是不需要的,因为我们在 removeMin 中已经进行了 this.size-- ;
266             return temp;
267         }
268     }
269 
270 
271     @Override
272     public String toString(){
273         StringBuilder builder = new StringBuilder();
274         generateBinarySearchTreeString(this.root,0,builder); //builder是传出参数
275         return builder.toString();
276     }
277 
278     //生成以 node 为根节点,深度为depth 的 描述二叉树的 字符串
279     private void generateBinarySearchTreeString(Node node,int depth,StringBuilder builder){
280         if(node == null){
281             builder.append(generateDepthInfo(depth)+"|null \n");
282             return;
283         }
284         builder.append(generateDepthInfo(depth)+node.t+"\n"); //不要忘了!!!
285 
286         generateBinarySearchTreeString(node.left,depth+1,builder);
287         generateBinarySearchTreeString(node.right,depth+1,builder);
288     }
289     //生成 关于深度的字符串。
290     private String generateDepthInfo(int depth){
291         StringBuilder res = new StringBuilder();
292         for (int i =0;i<depth;i++ ){
293             res.append("=");  //一个= 对应一个深度。
294         }
295         return res.toString();
296     }
297 
298 }
BinarySearchTree.java
 1 package zcb.demo01;
 2 
 3 public interface MySet<T> {
 4     // 自定义集合 的接口
 5 
 6     void add(T t); // 添加元素
 7     void remove(T t); // 删除元素
 8     boolean contains(T t); // 是否包含某个元素
 9     int getSize(); // 获取size
10     boolean isEmpty(); //判断是否 为空
11 }
MySet.java

MySet.java 是我们自定义集合的 接口, 

 1 package zcb.demo01;
 2 /**
 3  * 基于二分搜索树 的集合
 4  */
 5 public class MySet_BST <T extends Comparable<T> > implements MySet<T> {
 6     //<T extends Comparable<T> >意思是里面的元素 必须是可比较的
 7     // implements MySet<T> 意思是 这个类 实现了 MySet 这个接口
 8 
 9     private BinarySearchTree<T> bst; // 这个类 主要基于 bst 这个对象  对外不可见
10 
11     //这个类的 构造函数
12     public MySet_BST(){
13         bst = new BinarySearchTree<>(); // new的时候可以不加 泛型
14     }
15 
16     // 下面就是 一 一 覆盖我们要实现的 接口中的方法了,
17     @Override
18     public int getSize(){
19         return bst.getSize();
20     }
21 
22     @Override
23     public boolean isEmpty() {
24         return bst.isEmpty();
25     }
26 
27     @Override
28     public void add(T t) {
29         bst.add(t);
30     }
31 
32     @Override
33     public void remove(T t) {
34         bst.remove(t);
35     }
36     @Override
37     public boolean contains(T t) {
38         return bst.contains(t);
39     }
40 
41     // 用于输出
42     @Override
43     public String toString() {
44         return bst.toString();
45     }
46 
47 
48 
49 }
MySet_BST.java

MySet_BST.java 实现了上面的接口, 是我们 实际要用的类, 

 1 package zcb.demo01;
 2 
 3 public class test {
 4     public static void main(String[] args) {
 5         MySet_BST<String> mySet_bst = new MySet_BST<>();
 6         mySet_bst.add("tom");
 7         mySet_bst.add("t");
 8         mySet_bst.add("tm");
 9         System.out.println(mySet_bst.getSize());
10         mySet_bst.add("tom");  
11         System.out.println(mySet_bst.getSize());
12 
13     }
14 }
test.java

使用 链表 作为底层结构  实现自定义集合:

这里之所以要用 链表再来实现 集合,是因为  二分搜索树 和 链表 同属于动态数据结构 如下:

实现完之后,我们就可以 比较二者的性能,以直观的了解到 二分搜索树的优势所在, 

 1 package zcb.demo01;
 2 
 3 public interface MySet<T> {
 4     // 自定义集合 的接口
 5 
 6     void add(T t); // 添加元素
 7     void remove(T t); // 删除元素
 8     boolean contains(T t); // 是否包含某个元素
 9     int getSize(); // 获取size
10     boolean isEmpty(); //判断是否 为空
11 }
MySet.java 集合接口
 1 package zcb.demo01;
 2 public class MySet_LL<T> implements MySet<T>{
 3     private LinkedList<T> linkedList; // 自定义集合 的底层结构
 4     public MySet_LL(){
 5         linkedList = new LinkedList();
 6     }
 7     // 下面一一 覆盖MySet 接口中的方法即可
 8     @Override
 9     public int getSize() {
10         return linkedList.getSize();
11     }
12     @Override
13     public boolean isEmpty() {
14         return linkedList.isEmpty();
15     }
16     @Override
17     public boolean contains(T t) {
18         return linkedList.contains(t);
19     }
20     // 链表 里本身是 可以包含重复元素的,
21     @Override
22     public void add(T t) {
23         if(!linkedList.contains(t))
24             linkedList.addFirst(t); // 如果不包含 该元素才添加,否则不添加
25     }
26     @Override
27     public void remove(T t) {
28         linkedList.removeByEle(t);
29     }
30 
31     // 输出
32     @Override
33     public String toString() {
34         return linkedList.toString();
35     }
36 }
MySet_LL.java

上面接口的实现类,  

实现类用到的 链表类:

  1 package zcb.demo01;
  2 public class LinkedList <T> {   // 注意 链表这种数据结构 和二分搜索树不一样,它并不要求 元素具有可比性,
  3     private class Node{
  4         public T data;//具体数据
  5         public Node next; //引用(指针)
  6 
  7         //Node 节点的构造器
  8         public Node(T data,Node next){
  9             this.data  = data;
 10             this.next = next;
 11         }
 12         public Node(T data){
 13             this(data,null);
 14         }
 15         public  Node(){
 16             this(null,null);
 17         }
 18 
 19         @Override
 20         public String toString(){
 21             return this.data.toString();
 22         }
 23 
 24     }
 25 
 26     private Node dummyHead; //虚拟头节点
 27     private int size;
 28     //LinkedList 的构造器
 29 
 30     public LinkedList (){    //此时,当链表初始化的时候就已经存在一个 虚拟的头节点了。
 31         dummyHead = new Node(null,null);
 32         this.size = 0;
 33     }
 34     //获取链表中的元素个数
 35     public int getSize(){
 36         return this.size;
 37     }
 38     //返回链表是否为空
 39     public boolean isEmpty(){
 40         return this.size ==0;
 41     }
 42     //在链表 中间 索引(idx)  添加新元素
 43     public void insertByIdx(int idx,T newData){
 44         if(idx < 0 || idx > this.size){
 45             throw new IllegalArgumentException("idx 错误!");
 46         }
 47         Node tempPtr = new Node();
 48         tempPtr = this.dummyHead;
 49         for (int i =0;i< idx;i++){
 50             tempPtr = tempPtr.next;
 51         }
 52         Node node = new Node(newData,tempPtr.next);
 53         tempPtr.next = node;
 54 
 55         this.size ++;
 56     }
 57 
 58     //在链表头添加 新的元素 newData
 59     public void addFirst(T newData){
 60         insertByIdx(0,newData);
 61     }
 62     //在链表尾部 添加新元素
 63     public void addLast(T newData){
 64         this.insertByIdx(this.size,newData);
 65     }
 66 
 67 
 68     // 获取链表中的元素
 69     public T getByIdx(int idx){
 70         if(idx <0 && idx >= this.size)
 71             throw new IllegalArgumentException("idx 索引错误!!!");
 72 
 73         Node curPtr = dummyHead.next;
 74         for (int i=0;i<idx;i++){
 75             curPtr = curPtr.next;
 76         }
 77         return curPtr.data;
 78     }
 79     //获取链表的第一个元素
 80     public T getFirst(){
 81         return getByIdx(0);
 82     }
 83     //获取 链表的最后一个元素
 84     public T getLast(){
 85         return getByIdx(this.size-1);
 86     }
 87 
 88 
 89     //修改链表的第idx 元素为 newData
 90     public void setNewData(int idx,T newData){
 91         if(idx <0 && idx>=this.size)
 92             throw  new IllegalArgumentException("索引错误");
 93 
 94         Node curPtr = dummyHead.next;
 95         for (int i=0;i<idx;i++){
 96             curPtr = curPtr.next;
 97         }
 98         curPtr.data = newData;
 99     }
100 
101     //查找 链表中是否存在 元素  data
102     public boolean contains(T data){
103         Node curPtr = dummyHead.next;
104         while (curPtr != null){
105             if(curPtr.data.equals(data))  //??????
106                 return true;
107             curPtr = curPtr.next;
108         }
109         return false;
110     }
111 
112     //删除 指定的元素 (只删第一个)
113     public void removeByEle(T t){
114         Node tempPtr = this.dummyHead;
115         // 首先是要查找到 该元素的位置
116         while ( tempPtr.next != null){
117             if(tempPtr.next.data == t){
118                 // 找到了
119                 break;
120             }
121             tempPtr = tempPtr.next;
122         }
123         // 找到之后,就要删除它了,
124         if(tempPtr.next != null){
125             // 确实找了该元素
126             Node delNode = tempPtr.next; // 待删除的节点
127             tempPtr.next = delNode.next;
128 
129             delNode.next = null;
130             this.size --; //删完之后 要 --
131         }
132     }
133 
134     //删除 指定索引的元素  返回删除的元素
135     public T removeByIdx(int idx){
136         Node tempPtr = this.dummyHead;
137         for (int i=0;i<idx;i++){
138             tempPtr = tempPtr.next;
139         }
140         Node delNode = tempPtr.next;
141         tempPtr.next = delNode.next;
142         delNode.next = null; //手动释放
143         this.size --;
144 
145         return delNode.data;
146     }
147 
148     //删除第一个元素
149     public T removeFirst(){
150         return this.removeByIdx(0);
151     }
152     //删除最后一个元素
153     public T removeLast(){
154         return this.removeByIdx(this.size-1);
155     }
156 
157     @Override
158     public String toString(){
159         StringBuilder builder = new StringBuilder();
160         builder.append(String.format("LinkList's Data is below: [size:%d] \n", this.size));
161 //        Node tempPtr = this.dummyHead.next;
162 
163 //        while (tempPtr !=null){
164 //            builder.append(tempPtr.data +"->");
165 //           tempPtr =tempPtr.next;
166 //        }
167         for (Node tempPtr = this.dummyHead.next;tempPtr != null;tempPtr = tempPtr.next)
168             builder.append(tempPtr.data +"->");
169 
170         builder.append("null");
171         return builder.toString();
172     }
173 
174 }
LinkedList.java
 1 package zcb.demo01;
 2 
 3 public class test {
 4     public static void main(String[] args) {
 5         MySet_LL<String> mySet_ll = new MySet_LL<>();
 6 
 7         mySet_ll.add("tom");
 8         mySet_ll.add("egon");
 9         mySet_ll.add("m");
10         mySet_ll.add("om");
11         System.out.println( mySet_ll.toString());
12         mySet_ll.add("tom");
13         System.out.println( mySet_ll.toString());
14         mySet_ll.remove("egon");
15         System.out.println( mySet_ll.toString());
16 
17 
18 
19     }
20 }
test.java

  

链表 为底层结构 的集合 和 二分搜索树 为底层结构 的集合 的时间复杂度分析:

如果集合 是 链表为底层结构的话

对于 增 add:   它的时间复杂度为 O(n),  本来是O(1),但是因为我们要保证集合中不能有重复元素,所以要先扫描一下全部链表,于是时间复杂度就是 On 了, 

对于 查 contains: On 

对于 删 remove: On   

下面是 二分搜索树的时间复杂度分析: 

如果集合 是 链表为底层结构的话

比如,要添加28进来,其实就是相当于 在走一个链表,只不过这个链表的最大长度 是这个树的 高度,

它每次操作都可以 抛弃一半的数据,

所以对于二分搜索树,它的时间复杂度是 O(h), 其中的h 是这棵树的高度,  

于是,

下面是 得出 h  和 n 的关系,即二分搜索树 的高度 和n 的关系, 

1,满的二分搜索树:满的二分搜索树:(满 :即除了叶子节点,所有的节点,左右孩子都不为空 )

此时: 

n =  2^0 + 2^1 + 2^2+...+ 2^(h - 1)  = 2^h - 1   

所以,此时 h = log2(n + 1)  

通常,我们称之为 O( log2(n ) )  关系 !!!  而且在log 情况下,它的底数 我们也是不关注的,所以 我们一般说 这个h 是O (log n )  关系,  

所以,二分搜索树 的时间复杂度是 O(log n ) !

log n  和 n  的差距:

就是如果100万输入规模,log n 的算法需要1s跑完的话, n 的算法要5万秒,大概14个小时

可能你会觉得二者都在同一天,那么换过来,

如果log n 的算法需要1天的话,那么 n 的算法可能要137 年 才能跑完, 

所以,二者的差距还是很大的, 要有个概念,log n 是个非常快的时间复杂度 了, 

有时,很多算法的 时间复杂度是 n logn  ,它是 比 n方 的 快了好多倍,其实,它和 log n  和 n之间是一致的, 

但是,并不是每个二分搜索树都是 满的,(满的 当然好),

需要知道 对于同样的数据,我们可以创建出不同的二分搜索树, 

例如:

如果,我们的二分搜索树 是右面的情况(也是 最坏的情况),那么它和链表 是无异的, 时间复杂度 也是很高的, 

二分搜索树 可能退化 成链表, 就是说,如果一个数据是 按照顺序进来的话,那么,它就是退化成了 链表的情况,也是最差的情况了, 

这也是 二分搜索树的局限性所在,解决这个问题的方法 就是可以创建个 平衡二叉树 (后面涉及), 

总结下:

二分搜索树 的时间复杂度 最准确的说法是:O h  ,这个 h是树的高度, 

但是,对于大多数的情况,h 都可以达到 log n 的级别,  

leetcode 集合相关问题 (804):

这里使用java 中提供的集合 TreeSet  ,它的底层是 由一个  红黑树  的,所以不会出现 上面的最差情况,

 1 package zcb.demo01;
 2 import java.util.TreeSet;
 3 
 4 public class test{
 5     public static void main(String[] args) {
 6         String [] words = {"gin", "zen", "gig", "msg"};
 7         Solution.uniqueMorseRepresentations(words);
 8     }
 9 }
10 class Solution {
11     public static int uniqueMorseRepresentations(String[] words) {
12         TreeSet <String> set = new TreeSet <String>(); // java自带的  TreeSet集合
13 
14         String [] codes = {".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."};
15         for(String item : words){  // 遍历数组
16             StringBuilder builder = new StringBuilder();
17             for(int i =0;i<item.length();i++){  // 遍历 字符串
18                 builder.append( codes[item.charAt(i) - 'a']  );
19             }
20             set.add(builder.toString());
21         }
22         return set.size();
23     }
24 }
test.java

集合分 有序集合 和 无序集合: 

上面我们用 二分搜索树为 底层结构 实现的 集合 是个有序集合,  用 链表 为底层结构 实现的集合 是个无序集合, 

通常,有序的都是基于 搜索树来实现的, 但是,有很多问题,不需要使其有序, 这时可以通过 链表实现,但是,它的效率,太低,可以采用其他结构,例如 哈希表 来实现,  

哈希表 会比 搜索树还要稍微快点,

可以这样认为,搜索树 因为插入和删除 ,和查找及其快,所以 是要付出代价的,这个代价就是 它的时间复杂度稍微 低于 哈希表, 

补充: 集合 其实还有 多重集合,集合中的元素可以重复, 

Map  映射:

一一映射:

在很多语言里面,称这种结构是 字典,在 java c++ 中称为是Map

生活中使用 映射的场景:(一个key 一个value)

所以,存储 (键 值 ) 数据对  的数据结构 ,我们称之为 映射 (Map),

我们使用 它的主要目的是 快速根据key 来找到value ,  

虽然,它是存储两个值,我们依然可以快速的通过 链表 或者二分搜索树 来实现它, 

定义Map 接口如下: 

 1 package zcb.demo02;
 2 
 3 public interface Map<K,V> {
 4     void add(K key,V val);
 5     V remove(K key);
 6     V get(K key);
 7     boolean contains(K key);
 8     void set(K key,V val);
 9     int getSize();
10     boolean isEmpty();
11 }
Map.java

使用 链表作为底层结构 实现 Map : 

  1 package zcb.demo02;
  2 
  3 public class Map_LL <K,V> implements Map<K,V> {
  4     private class Node{
  5         public K key;//具体数据
  6         public V val;
  7         public Node next; //引用(指针)
  8 
  9         //Node 节点的构造器
 10         public Node(K key,V val,Node next){
 11             this.key = key;
 12             this.val = val;
 13             this.next = next;
 14         }
 15         public Node(K key,V val){
 16             this(key,val,null);
 17         }
 18         public  Node(){
 19             this(null,null,null);
 20         }
 21 
 22         @Override
 23         public String toString(){
 24             return this.key.toString() + ": "+this.val.toString();
 25         }
 26     }
 27     private Node dummyHead; //虚拟头节点
 28     private int size;
 29 
 30     public Map_LL(){
 31         dummyHead = new Node();
 32         size = 0;
 33     }
 34 
 35     // 辅助 私有函数 getNode  指定key 可以返回相应的 Node
 36     private Node getNode(K key){
 37         Node tempPtr = this.dummyHead.next;
 38         while (tempPtr != null){
 39             if(tempPtr.key.equals(key) ){
 40                 return tempPtr;
 41             }
 42             tempPtr = tempPtr.next;
 43         }
 44         return null;
 45     }
 46 
 47     //获取映射中的元素个数
 48     public int getSize(){
 49         return this.size;
 50     }
 51     //返回映射是否为空
 52     public boolean isEmpty(){
 53         return this.size ==0;
 54     }
 55 
 56     //映射中 是否包含某个key
 57     public boolean contains(K key){
 58         Node node = this.getNode(key);
 59         return node != null;
 60     }
 61     //根据key 得到 val
 62     public V get(K key){
 63         Node node = this.getNode(key);
 64         if(node != null){
 65             return node.val;
 66         }
 67         return null;
 68     }
 69     // 添加新元素
 70     public void add(K key,V val){
 71         // 先判断是否 已经存在 该key
 72         Node node = this.getNode(key); 
 73         if(node == null){
 74             // 可以直接在链表 头添加节点,时间复杂度低
 75             // 下面是一步操作
 76             this.dummyHead.next = new Node(key,val,this.dummyHead.next);
 77             this.size ++;
 78         }else{
 79             //已经存在 ,就更新 val 
 80             node.val  = val;
 81         }
 82     }
 83 
 84     // 更新某个 val
 85     public void set(K key,V val){
 86         Node node = getNode(key);
 87         if(node == null){
 88             throw new IllegalArgumentException(key + "不存在");
 89         }else{
 90             node.val  = val;
 91         }
 92     }
 93 
 94     // 移除某个key ,返回对应的val
 95     public V remove(K key){
 96         Node tempPtr = this.dummyHead;
 97         while (tempPtr.next != null){
 98             if(tempPtr.next.key.equals(key)){
 99                 break;
100             }
101             tempPtr = tempPtr.next;
102         }
103         if(tempPtr.next != null){
104             Node delNode =  tempPtr.next;
105             tempPtr.next = delNode.next;
106             delNode.next = null;  // delNode 里面存的地址要清空
107             this.size --;
108             return delNode.val;
109         }else{
110             return null;
111         }
112     }
113 
114     // toString
115     @Override
116     public String toString(){
117         StringBuilder builder = new StringBuilder();
118         Node tempPtr = this.dummyHead.next;
119         while (tempPtr != null){
120             builder.append(tempPtr.key +":"+tempPtr.val +" | ");
121             tempPtr = tempPtr.next;
122         }
123         return builder.toString();
124     }
125 
126 
127 }
Map_LL.java
 1 package zcb.demo02;
 2 
 3 public class test {
 4     public static void main(String[] args) {
 5         Map_LL<String,String> map_ll = new Map_LL<>();
 6         
 7         map_ll.add("name","egon");
 8         map_ll.add("age","18");
 9         System.out.println(map_ll);
10         map_ll.set("name","a");
11         System.out.println(map_ll);
12         map_ll.add("school","XXX");
13         System.out.println(map_ll);
14         map_ll.remove("name");
15         System.out.println(map_ll);
16 
17         System.out.println(map_ll.contains("name"));
18 
19         System.out.println(map_ll.getSize());
20         System.out.println(map_ll.isEmpty());
21 
22     }
23 
24 
25 }
test.java

使用 二分搜索树作为底层结构 实现 Map : 

  1 package zcb.demo02;
  2 
  3 public class Map_BST<K extends Comparable<K>,V> implements Map<K,V> {
  4     private class Node {
  5         public K key;
  6         public V val;
  7         public Node left,right;
  8 
  9         // 构造器
 10         public Node(K key,V val,Node left,Node right){
 11             this.key = key;
 12             this.val = val;
 13             this.left = left;
 14             this.right = right;
 15         }
 16 
 17         @Override
 18         public String toString(){
 19             return this.key +": "+this.val;
 20         }
 21     }
 22 
 23     private Node root;
 24     private int size;
 25 
 26     public Map_BST(){
 27         root = null;
 28         size = 0;
 29     }
 30 
 31     @Override
 32     public int getSize(){
 33         return size;
 34     }
 35     @Override
 36     public boolean isEmpty(){
 37         return size == 0;
 38     }
 39 
 40     @Override
 41     public void add(K key,V val){
 42         root = add(root,key,val);
 43     }
 44     // 实际进行递归 的函数
 45     public Node add(Node node,K key,V val){ //返回值必须是 根节点
 46         if(node == null){
 47             size ++;
 48             return new Node(key,val,null,null);
 49         }
 50 
 51         if(key.compareTo(node.key) < 0 ){
 52             //向左
 53             node.left =  add(node.left,key,val);
 54         }else if(key.compareTo(node.key) >0 ){
 55             //向右
 56             node.right = add(node.right,key,val);
 57         }else{
 58             node.val = val;  // 如果key 已经存在 直接 更新val
 59         }
 60 
 61         return node;
 62     }
 63 
 64     // 辅助函数 返回 以node 为根节点, key 所在的节点
 65     private Node getNode(Node node, K key){
 66         if(node  == null){
 67             return null;
 68         }
 69         if(key.compareTo(node.key) < 0 ){
 70             // 向左
 71             return getNode(node.left,key);
 72         }else if(key.compareTo(node.key) > 0 ){
 73             // 向右
 74             return getNode(node.right,key);
 75         }else{
 76             return node;
 77         }
 78 
 79     }
 80 
 81     // 映射中 是否包含某个key
 82     @Override
 83     public boolean contains(K key){
 84         Node node = getNode(root,key);
 85         return node != null;
 86     }
 87 
 88     // 返回 映射中 key 对应的val
 89     @Override
 90     public V get(K key){
 91         Node node = getNode(root,key);
 92         return node == null ? null:node.val;
 93     }
 94 
 95     // 更新 映射中 key 对应的val
 96     @Override
 97     public void set(K key,V val){
 98         Node node = getNode(root,key);
 99         if(node == null)
100             throw new IllegalArgumentException(key + " 不存在 ");
101 //        else
102 //            node.val = val;
103         node.val = val;
104     }
105 
106     // 删除映射 中的 key, 并返回它对应的 val
107     @Override
108     public V remove(K key){
109         Node node = getNode(root,key);
110         if(node != null){
111             root = remove(root,key);
112             return node.val;
113         }
114         return null;
115     }
116     // 实际进行递归的函数
117     public Node remove(Node node,K key){
118         if(node == null){
119             return null;
120         }
121         if(node.key.compareTo(key) < 0){
122             // 向左
123             node.left = remove(node.left,key);
124             return node;
125         }else if(node.key.compareTo(key) > 0){
126             // 向右
127             node.right = remove(node.right,key);
128             return node;
129         }else{
130             // 找到了 要删除的node
131 
132             // 待删节点的左子树 为null 的情况 (此时直接将 右面子树放到待删节点的位置即可)
133             if(node.left == null){
134                 Node rightNode = node.right;
135                 node.right = null; //
136                 size --;
137                 return rightNode; //此时返回的是 node 右子树的根节点,而不返回node了
138             }else if(node.right == null){
139                 // 右子树 为null 的话,
140                 Node leftNode = node.left;
141                 node.left = null;
142                 size --;
143                 return leftNode;
144             }else{
145                 //左右 子树都不为 null
146                 // 此时在 左子树中找出最大的 或者 右子树中最小的 替换要删除的node
147                 Node min_rt = minimum(node.right); //minimum 是找出最小节点
148 
149                 min_rt.right = removeMin(node.right); //removeMin 删除最小节点 并返回 根节点
150                 min_rt.left = node.left;
151                 node.left = node.right = null;
152                 size --;
153 
154                 return min_rt;
155             }
156         }
157     }
158 
159     // 找出树中的最小节点 返回的   不是根节点
160     private Node minimum(Node node){
161         if(node == null){
162             return null;
163         }
164         if(node.left == null){
165             //node 即为最小的节点
166             return node;
167         }
168         return minimum(node.left);
169     }
170     // 删除 树中的最小 节点 并   返回根节点
171     private Node removeMin(Node node){
172         if(node == null){
173             return null;
174         }
175         if(node.left == null){
176             // 此时node 即为最小节点
177             Node rightNode = node.right;
178             node.right = null;
179             size --;
180             return rightNode;
181         }
182         node.left =  removeMin(node.left);
183         return node;
184     }
185 
186 }
Map_BST.class
 1 package zcb.demo02;
 2 
 3 public class test {
 4     public static void main(String[] args) {
 5         Map_BST<String,String> map_bst = new Map_BST<>() ;
 6 
 7 
 8         map_bst.add("name","egon");
 9         System.out.println(map_bst.getSize());
10         map_bst.add("name","a");
11         System.out.println(map_bst.getSize());
12         System.out.println("=========================================");
13 
14         map_bst.add("age","18");
15         System.out.println(map_bst.getSize());
16         System.out.println(        map_bst.contains("name"));
17         System.out.println("=========================================");
18 
19         map_bst.remove("name");
20         System.out.println(map_bst.getSize());
21         System.out.println(map_bst.contains("name"));
22         System.out.println("=========================================");
23 
24         System.out.println(map_bst.get("age"));
25         map_bst.set("age","12");
26         System.out.println(map_bst.get("age"));
27         System.out.println("=========================================");
28 
29         map_bst.remove("name");
30         map_bst.remove("age");
31         System.out.println(map_bst.isEmpty());
32 
33         System.out.println("=========================================");
34 
35         map_bst.set("name","bb"); // 此时会 抛出一个异常,告诉我们 name 不存在
36 
37     }
38 
39 
40 }
test.java

映射的时间复杂度分析:

有序映射 和 无序映射:

有序映射 指 的是映射中key 是有序的, (二分搜索树为底层实现结构)

无序映射 指 的是映射中key 是无序的, (链表为底层实现结构 ,But 因为链表的效率 太低,通常使用 哈希表来实现  )

多重映射:

映射中的 键是可以重复的, 

集合 和映射的关系: 

二者是十分类似的, 

leetcode 上 集合 和映射相关问题:

349. 两个数组的交集 (与集合 相关 ):

package zcb.demo02;

import java.util.ArrayList;
import java.util.TreeSet;

public class test {
    public static void main(String[] args) {
        int [] arr1 = {1,2,2,1};
        int [] arr2 = {2,2};
        int[] ret = Solution.intersection(arr1,arr2);
        System.out.println(ret);
    }
}
class Solution {
    public static int[] intersection(int[] nums1, int[] nums2) {
        TreeSet<Integer> set = new TreeSet<>();
        for(int item:nums1){
            set.add(item);
        }

        ArrayList<Integer> arr = new ArrayList<>();
        for (int item:nums2){
            if(set.contains(item) ){
                // set 包含item
                arr.add(item);
                set.remove(item); //一定要有,
            }
        }
        int [] res = new int []{ arr.size()};
        for (int i=0;i<arr.size();i++){
            res[i] = arr.get(i);
        }
        return res;
    }
}
test.java

350. 两个数组的交集II   (与映射 相关 ):

这个是允许有重复元素的集合,

 1 package zcb.demo02;
 2 import java.util.ArrayList;
 3 import java.util.TreeMap;
 4 public class test {
 5     public static void main(String[] args) {
 6         int [] arr1 = {1,2,2,1};
 7         int [] arr2 = {2,2};
 8         int [] res = Solution.intersect(arr1,arr2); 
 9         System.out.println(res); 
10     }
11 }
12 class Solution {
13     public static int[]  intersect(int[] nums1, int[] nums2) {
14         TreeMap<Integer,Integer> treeMap = new TreeMap<>();
15         for (int item:nums1){
16             if(!treeMap.containsKey(item)){ //如果不包含 item
17                 treeMap.put(item,1);
18             }else{
19                 treeMap.put(item,treeMap.get(item) + 1);
20             }
21         }
22 
23         ArrayList<Integer> arr = new ArrayList<>();
24         // 遍历 nums2
25         for(int item:nums2){
26             if(treeMap.containsKey(item)){
27                 arr.add(item);
28                 treeMap.put(item,treeMap.get(item) - 1);
29                 if(treeMap.get(item) ==  0){
30                     treeMap.remove(item);
31                 }
32             }
33         }
34 
35         int [] res = new int[arr.size()];
36         for (int i =0;i<arr.size();i++){
37             res[i] = arr.get(i);
38         }
39         return res;
40     }
41 }
test.java

:其实 ,与哈希表相关的很多问题都可以用TreeSet 和 TreeMap 来解决 (它们是基于平衡二叉树(更准确是红黑树) 实现的集合 和映射  ),也就是我们的集合 和 映射,  也可以使用HashSet 和 HashMap 来解决,(它们是基于Hash 表实现的集合 和映射  )

我们这里自定的集合 用的底层结构是 ArrayList (动态数组), 自定义的 映射的底层结构是  搜索二叉树,

关于哈希表  ,平衡二叉树 和 红黑树,后面有,

猜你喜欢

转载自www.cnblogs.com/zach0812/p/12344716.html