7.玩转数据结构——集合(Set)与映射(Map)

﹥>﹥吖頭↗ 提交于 2020-11-30 00:46:22

---恢复内容开始---

1.. 集合的应用

  • 集合可以用来 去重
  • 集合可以用于进行客户的统计
  • 集合可以用于文本词汇量的统计

 

 

 

2.. 集合的实现
  • 定义集合的接口,需要实现如下操作
1 Set<E>
2 ·void add(E)   // 不能添加重复元素
3 ·void remove(E)
4 ·boolean contains(E)
5 ·int getSize()
6 ·boolean isEmpty()

以上接口,二分搜索树都是支持的,只要把二分搜索树包装一下。

本节主要关注以二分搜索树为底层的实现,实现集合set(用二分搜索树作为集合的底层实现)

 

  • 集合接口的业务逻辑如下:
1 public interface  Set<E> {    //接口叫set,对于集合接口定义来说,是支持泛型的
2 
3     void add(E e);
4     boolean contains(E e);
5     void remove(E e);
6     int getSize();
7     boolean isEmpty();
8 }

 

  • 用二分搜索树作为集合的底层实现
 1 public class BSTSet<E extends Comparable<E>> implements Set<E> {//类BSTSe:基于二分搜索树实现的集合的一个类
 2                                                                       //支持泛型,对于泛型类型E,还必须是可以比较的,满足Comparable这个接口。
 3                                                                        //extends是继承类,implements是实现接口。
 4     private BST<E> bst;
 5 
 6     public BSTSet(){
 7         bst = new BST<>();
 8     }
 9 
10     @Override
11     public int getSize(){
12         return bst.size();
13     }
14 
15     @Override
16     public boolean isEmpty(){
17         return bst.isEmpty();
18     }
19 
20     @Override
21     public void add(E e){
22         bst.add(e);
23     }
24 
25     @Override
26     public boolean contains(E e){
27         return bst.contains(e);
28     }
29 
30     @Override
31     public void remove(E e){
32         bst.remove(e);
33     }
34 }

 

7-2 基于链表的集合实现

对于集合,我们设计的是一个接口,所以可以采用不同的底层数据结构来实现这个接口,本节用链表来实现集合

 

二分搜索树和链表都是动态结构

基于链表的集合实现

 1 public class LinkedListSet<E> implements Set<E> {   //基于链表的集合类支持泛型,存储的对象类型并不要求具有可比性,这是线性数据的一个特点
 2 
 3     private LinkedList<E> list;//成员变量
 4 
 5     public LinkedListSet(){  //构造函数
 6         
 7         list = new LinkedList<>();
 8     }
 9 
10     @Override
11     public int getSize(){
12         return list.getSize();
13     }
14 
15     @Override
16     public boolean isEmpty(){
17         return list.isEmpty();
18     }
19 
20     @Override
21     public void add(E e){  //链表中允许添加重复元素,这里需要组合逻辑以保证不能把重复的元素添加进去
22         if(!list.contains(e))//只需要:查一下当前的list中是否包含元素e,如果采用list的addFirst()方法
23             list.addFirst(e);
24     }
25 
26     @Override
27     public boolean contains(E e){
28         return list.contains(e);
29     }
30 
31     @Override
32     public void remove(E e){
33         list.removeElement(e);
34     }
  • 用二分搜索树实现的集合与用链表实现的集合的性能比较
 1 public class Main {
 2 
 3     public static void main(String[] args) {
 4 
 5         System.out.println("Pride and Prejudice");
 6 
 7         ArrayList<String> words1 = new ArrayList<>();
 8         if(FileOperation.readFile("pride-and-prejudice.txt", words1)) {//将pride-and-prejudice.txt存入words1
 9             System.out.println("Total words: " + words1.size());//打印输出word1的大小
10 
11             BSTSet<String> set1 = new BSTSet<>();//类BSTSe:基于二分搜索树实现的集合的一个类
12             for (String word : words1)
13                 set1.add(word);//把words1中的每一个单词都添加进set1中,在这个过程中由于底层的二分搜索树会忽略重复的元素,所以重复的单词就相当于忽略了
14             System.out.println("Total different words: " + set1.getSize());
15         }
16 
17         System.out.println();
}
}
18 19 //************************************// 20 public static void main(String[] args) { 21 22 System.out.println("Pride and Prejudice"); 23 24 ArrayList<String> words1 = new ArrayList<>(); 25 if(FileOperation.readFile("pride-and-prejudice.txt", words1)) { 26 System.out.println("Total words: " + words1.size()); 27 28 LinkedListSet<String> set1 = new LinkedListSet<>(); 29 for (String word : words1) 30 set1.add(word); 31 System.out.println("Total different words: " + set1.getSize()); 32 } 33 34 System.out.println();48 } 49 }

 

h:二分搜索树的高度

n:节点数

 

 

 

二分搜索树实现的集合比用链表实现的集合的性能好。

 

7-4 _Leetcode中的集合问题和更多集合相关问题

 

 7-5 映射基础

 

 

 

 

 

 映射(Map)
  • 映射是存储(键,值)数据对的数据结构(Key, Value)
  • 根据键(Key),寻找值(Value)

映射的实现

  • 定义映射的接口

 

1 Map<K, V>
2 ·void add(K, V)
3 ·V remove(K)
4 ·boolean contains(K)
5 ·V get(K)
6 ·void set(K, V)
7 ·int getSize()
8 ·boolean isEmpty()
  • 映射接口的业务逻辑如下
 1 public interface Map<K, V> {
 2 
 3     void add(K key, V value);
 4     V remove(K key);   //删除操作:键充当索引的作用,设计返回值是Value类型(V),告诉用户删除的K对应的value什么吗
 5     boolean contains(K key);  //如查字典时,判断是否存在单词K
 6     V get(K key);   //如查字典时,获取单词K的释义
 7     void set(K key, V newValue);  //修改,将K所对应的value值修改为传进来的新的Value值(newValue)
 8     int getSize();
 9     boolean isEmpty();
10 }

 

以链表为数据底层,来实现映射Map(K,V)这样一个数据结构。

  1 public class LinkedListMap<K, V> implements Map<K, V> {
  2  //由于之前实现的链表只能承载一个元素e,所以对于映射类要从头开始实现
  3        //定义节点内部类,基于链表实现的映射响应的节点实现的方法
  4     private class Node{
  5         public K key;
  6         public V value;
  7         public Node next;
  8 
  9         public Node(K key, V value, Node next){
 10             this.key = key;
 11             this.value = value;
 12             this.next = next;
 13         }
 14 
 15         public Node(K key, V value){
 16 
 17             this(key, value, null);
 18         }
 19 
 20         public Node(){
 21 
 22             this(null, null, null);
 23         }
 24 
 25         @Override
 26         public String toString(){
 27 
 28             return key.toString() + " : " + value.toString();
 29         }
 30     }
 31 
 32     private Node dummyHead;  //虚拟头指针
 33     private int size;
 34 
 35     public LinkedListMap(){    //构造函数,和单链表是一样的
 36         dummyHead = new Node();
 37         size = 0;
 38     }
 39 
 40     @Override
 41     public int getSize(){
 42 
 43         return size;
 44     }
 45 
 46     @Override
 47     public boolean isEmpty(){
 48         return size == 0;
 49     }
 50 
 51     private Node getNode(K key){  //执行下面几个方法的基础
 52         Node cur = dummyHead.next;
 53         while(cur != null){
 54             if(cur.key.equals(key))
 55                 return cur;
 56             cur = cur.next;
 57         }
 58         return null;//while(cur == null)
 59     }
 60 
 61     @Override
 62     public boolean contains(K key){
 63         return getNode(key) != null;
 64     }
 65 
 66     @Override
 67     public V get(K key){
 68         Node node = getNode(key);
 69         return node == null ? null : node.value;
 70     }
 71 
 72     @Override
 73     public void add(K key, V value){
 74         Node node = getNode(key);
 75         if(node == null){  //没有相应数据
 76             dummyHead.next = new Node(key, value, dummyHead.next);
 77             size ++;
 78         }
 79         else
 80             node.value = value;
 81     }
 82 
 83     @Override
 84     public void set(K key, V newValue){
 85         Node node = getNode(key);
 86         if(node == null)
 87             throw new IllegalArgumentException(key + " doesn't exist!");
 88 
 89         node.value = newValue;
 90     }
 91 
 92     @Override    //删除操作:键充当索引的作用,设计返回值是Value类型(V),告诉用户删除的K对应的value什么吗
 93     public V remove(K key){ //这一段所做的事情其实就是在单链表中删除元素e所做的事情
 94         Node prev = dummyHead;
 95         while(prev.next != null){
 96             if(prev.next.key.equals(key))//如果满足if条件,说明prev.next是我们要删除的节点,跳到 if(prev.next != null)执行代码段
 97                 break;  //break是结束整个循环体(这里是结束while循环),continue是结束单次循环
 98             prev = prev.next;
 99         }
100 
101         if(prev.next != null){
102             Node delNode = prev.next;//给prec的下一个节点取名delNode,delNode是我们想要删除的
103             prev.next = delNode.next;
104             delNode.next = null;
105             size --; //删除元素,要--
106             return delNode.value;
107         }
108 
109         return null;//如果prev.next == null,执行这行,告诉用户当前映射中没有key对应的元素,即不满足上面的while、if循环
110     }

 

 

7-7 基于二分搜索树的映射实现

  • 用二分搜索树作为映射的底层实现
  1 import java.util.ArrayList;
  2 
  3 public class BSTMap<K extends Comparable<K>, V> implements Map<K, V> {//二分搜索树对于k来说,是可以比较的,实现 Comparable<K>这样一个接口
  4 
  5     private class Node{
  6         public K key;
  7         public V value;
  8         public Node left, right;
  9 
 10         public Node(K key, V value){
 11             this.key = key;
 12             this.value = value;
 13             left = null;
 14             right = null;
 15         }
 16     }
 17 
 18     private Node root;
 19     private int size;
 20 
 21     public BSTMap(){
 22         root = null;
 23         size = 0;
 24     }
 25 
 26     @Override
 27     public int getSize(){
 28 
 29         return size;
 30     }
 31 
 32     @Override
 33     public boolean isEmpty() {
 34         return size == 0;
 35     }
 36 
 37     // 向二分搜索树中添加新的元素(key, value)
 38     @Override
 39     public void add(K key, V value){
 40         root = add(root, key, value);
 41     }
 42 
 43     // 向以node为根的二分搜索树中插入元素(key, value),递归算法
 44     // 返回插入新节点后二分搜索树的根
 45     private Node add(Node node, K key, V value){
 46             //递归终止条件
 47         if(node == null){
 48             size ++;
 49             return new Node(key, value);
 50         }
 51                 //递归组成逻辑
 52         if(key.compareTo(node.key) < 0)
 53             node.left = add(node.left, key, value);  //向左子树中插入新的元素
 54         else if(key.compareTo(node.key) > 0)
 55             node.right = add(node.right, key, value);  //向右子树中插入新的元素
 56         else // key.compareTo(node.key) == 0
 57             node.value = value;
 58 
 59         return node;
 60     }
 61 
 62     // 返回以node为根节点的二分搜索树中,key所在的节点
 63     private Node getNode(Node node, K key){  //辅助函数,下面contains等函数用到
 64 
 65         if(node == null)
 66             return null;
 67 
 68         if(key.equals(node.key))
 69             return node;
 70         else if(key.compareTo(node.key) < 0)
 71             return getNode(node.left, key);
 72         else // if(key.compareTo(node.key) > 0)
 73             return getNode(node.right, key);
 74     }
 75 
 76     @Override
 77     public boolean contains(K key){
 78         return getNode(root, key) != null;
 79     }
 80 
 81     @Override
 82     public V get(K key){
 83 
 84         Node node = getNode(root, key);
 85         return node == null ? null : node.value;
 86     }
 87 
 88     @Override
 89     public void set(K key, V newValue){
 90         Node node = getNode(root, key);
 91         if(node == null)
 92             throw new IllegalArgumentException(key + " doesn't exist!");
 93 
 94         node.value = newValue;
 95     }
 96 
 97     // 返回以node为根的二分搜索树的最小值所在的节点
 98     private Node minimum(Node node){
 99         if(node.left == null)
100             return node;
101         return minimum(node.left);
102     }
103 
104     // 删除掉以node为根的二分搜索树中的最小节点
105     // 返回删除节点后新的二分搜索树的根
106     private Node removeMin(Node node){
107 
108         if(node.left == null){
109             Node rightNode = node.right;
110             node.right = null;
111             size --;
112             return rightNode;
113         }
114 
115         node.left = removeMin(node.left);
116         return node;
117     }
118 
119     // 从二分搜索树中删除键为key的节点
120     @Override
121     public V remove(K key){
122 
123         Node node = getNode(root, key);
124         if(node != null){
125             root = remove(root, key);
126             return node.value;
127         }
128         return null;
129     }
130 
131     private Node remove(Node node, K key){
132 
133         if( node == null )
134             return null;
135 
136         if( key.compareTo(node.key) < 0 ){
137             node.left = remove(node.left , key);//去左子树删除节点
138             return node;
139         }
140         else if(key.compareTo(node.key) > 0 ){
141             node.right = remove(node.right, key);
142             return node;
143         }
144         else{   // key.compareTo(node.key) == 0
145 
146             // 待删除节点左子树为空的情况
147             if(node.left == null){
148                 Node rightNode = node.right;
149                 node.right = null;
150                 size --;
151                 return rightNode;
152             }
153 
154             // 待删除节点右子树为空的情况
155             if(node.right == null){
156                 Node leftNode = node.left;
157                 node.left = null;
158                 size --;
159                 return leftNode;
160             }
161 
162             // 待删除节点左右子树均不为空的情况
163 
164             // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
165             // 用这个节点顶替待删除节点的位置
166             Node successor = minimum(node.right);
167             successor.right = removeMin(node.right);
168             successor.left = node.left;
169 
170             node.left = node.right = null;
171 
172             return successor;
173         }
174     }

7-8 映射的复杂度分析和更多映射相关问题

  • 通过比较结果,我们发现,用二分搜索树实现的映射的比用链表实现的映射更加高效

 映射的时间复杂度

"h"是二分搜索树的高度 

 

 

  • 当二分搜索树"满"的时候,性能是最佳的,时间复杂度为O(logn);当二分搜索树退化为链表的时候,性能是最差的,时间复杂度为O(n)

 

 后续介绍

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!