数据结构与算法实战 6.查找与散列

  1 *****查找与散列*****
  2 常用查找:顺序查找、二分查找
  3 查找树:<delete>BST</delete>(这玩意其实并不适合查找,因为可能会长斜了逐渐靠近一维)
  4         AVL树、红黑树
  5         外存查找:B+树 B树
  6 查找树的效率一般能达到O(logN),以k为底,因为B树不一定是二叉树,是多叉树
  7 
  8 ***散列***
  9 目的:为了更快查找 O(1) 
 10 注意 不一定能达到O(1),当hash算出来的地址就是这个元素的地址的时候是O(1),当需要遍历的时候就O(n)了
 11 拿空间换时间,提高时间效率
 12 实际上散列是一个(key-value pair)的集合,例如map也可以用散列实现.
 13 根据一个值算空间位置
 14 
 15 散列函数有两个特点:第一,计算位置要尽量快 第二,元素要尽量散,否则会发生大量位置冲突
 16 因为空间不能过分浪费,散列分配到的空间就有限,那么冲突是不可避免的。
 17 那么冲突如何解决呢?
 18 解决冲突一共有三个方法:
 19 
 20 开放寻址法(Open Addressing) 允许一个元素在它本应在的位置被占之后去其他位置(一般是线性探测,往下一个去找)
 21 注意,如果采用开放寻址方式,删除是lazy deletion,例如如果%11存进散列,
 22 输入11,1,12,那么就是11,1,12(本来应该在1的位置,但是被1给占了),当我们查找余数为1的数时候,
 23 我们先找到了1,然后继续往下找到了12,假设我们删除了1,我们找余数为1的数,就会先找到个空格,表示不存在
 24 但是12还在啊,所以我们采用开放寻址的时候不能直接把元素给删除,只能把它标记上删除
 25 
 26 分离链(Seperate Chaining) 一个位置上用一个链连接数据 可以想象每个位置上都是一条垂直往下的链
 27 
 28 其它(公共溢出区等)
 29 公共溢出区:指定一块公共的地方去储存那些本来位置被占用的元素
 30 
 31 #include<cstdio>
 32 #include<cstdlib>
 33 
 34 enum GridStatus{Active, Removed, Empty};
 35 typedef enum GridStatus Status;
 36 
 37 struct HashTable{
 38     int *key;//因为是C语言 简单用int表示key就完事了,C++和JAVA可以用模板/泛型
 39     Status *status;//存格子的状态,单纯用int,还得规定哪个数字表示什么状态,不好使
 40     int size;
 41     //为了方便 我们可以用一个变量记录表满不满 来判断是否能插入
 42     int remains;
 43 };
 44 
 45 typedef struct HashTable* HT;
 46 
 47 HT initHashTable(int size){
 48     HT h;
 49     h = (HT)malloc(sizeof(struct HashTable));
 50     if(!h) return NULL;//空间不足 分配失败 为NULL
 51     h->size = h->remains = size;
 52     h->key = (int*)malloc(sizeof(int) * size);
 53     if(!h->key){//可用空间不足,分配失败,为NULL
 54         free(h);
 55         return NULL;
 56     }
 57     h->status = (Status*)malloc(sizeof(Status) * size);
 58     if(!h->status){//可用空间不足,分配失败,为NULL
 59         free(h->key);
 60         return NULL;
 61     }
 62     //初始化状态 为空,不然你不初始化的话,你创建之后你怎么判断能不能插入?
 63     for(int i = 0; i < size; i ++){
 64         h->status[i] = Empty;
 65     }
 66 
 67     return h;
 68 }
 69 
 70 int isFull(const HT h){
 71     return h->remains == 0;
 72 }
 73 
 74 int hash(int x, int p){//prime素数
 75     return x % p;
 76 }
 77 
 78 int insertX(int x, HT h){
 79     if(isFull(h)) return 0;//满了 没法插入 同时也保证了下面找位置一定能找得到
 80     //如果没有isFull,我们也能判断满了与否,比如找到本应在的位置被占之后,用临时变量记录下来
 81     //接着用这个位置继续往下找,找了一圈之后等于临时变量,那么就是满了,但是这种写法比较麻烦.
 82     int pos = hash(x, h->size);
 83     //有可能出现这样的情况 一个散列的存储位置除了第一个,后面都满了,那么我们找位置要循环回至0
 84     while(h->status[pos] == Active){
 85         //被占了
 86         //找下一个
 87         pos = (pos + 1) % h->size;
 88     }
 89     h->key[pos] = x;
 90     h->status[pos] = Active;
 91     h->remains --;
 92     return 1;
 93 }
 94 
 95 void printHT(const HT h){
 96     //打印下标 打印元素 打印标记
 97     for(int i = 0; i < h->size; i ++){
 98         printf("%4d",i);
 99     }
100 
101     printf("\n");
102 
103     for(int i = 0; i < h->size; i ++){
104         if(h->status[i] == Active) printf("%4d",h->key[i]);
105         else if(h->status[i] == Removed) printf("   X");//删除了
106         else printf("   -");//待插入
107     }
108 
109     printf("\n");
110 }
111 
112 int findX(int x, HT h){
113     //现在是线性探测,如果找本来x应该在的那个位置被其他元素占据了的话就继续一个个往下找
114     //当找了一圈,即又回到最初的七点,都没有找到 就是失败了
115     //另一种情况,既然前面没有这个元素的话,那么后面应该有这个元素,但是
116     //如果再在往前找的过程中碰到了空的格子,却没有这个元素,显然,这个元素不存在
117     int pos, index;//pos用来往后找 index用来标记刚开始的位置
118     index = pos = hash(x, h->size);
119     //碰到空格 or 找了一圈都算失败
120     while(h->status[pos] != Empty){
121         if(h->key[pos] == x && h->status[pos] == Active) return pos;
122         pos = (pos + 1) % h->size;
123         if(pos == index) break;//转了一圈
124     }
125     return -1;
126 }
127 
128 int removeX(int x,HT h){
129     //删除一个不存在的元素的时候失败
130     int pos = findX(x, h);
131     if(pos == -1) return 0;
132     //不必要把那个位置清零,只要标记上就好了
133     //这个道理和计算机删除文件的道理是一致的
134     //为什么拷贝文件比删除文件要慢的多?因为删除文件只是操作系统把这些文件占用的空间标记为释放
135     h->status[pos] = Removed; 
136     h->remains ++;
137     return 1;
138 }
139 
140 int main(){
141     /*
142     补充一点:一个数据结构创建好之后,肯定是存在有东西的,不存在真空的情况
143             所以我们要添加一个标记,告诉使用者,位置是空的,可以存放key
144     */
145     //参数应该要有表的长度;另外哈希表长度数据为一个素数的时候,元素分布得较均匀
146     //但是用户调用的时候,传进来的不一定是一个素数,我们可以内部实现求用户输入的数后面的第一个素数就完事了
147     //mod 一个素数(用户输入的数的后面自然数里面的第一个素数)
148     //为了讲课方便,专注于哈希,代码不做素数求解
149     HT h = initHashTable(11);//在这里,简单把哈希函数H(x) = x mod 11
150     insertX(5, h);
151     insertX(16, h);
152     insertX(10, h);
153     insertX(21, h);
154     insertX(9, h);
155     insertX(20, h);
156     printHT(h);
157     removeX(21, h);
158     printHT(h);//测试这个是因为 删除 可能会 影响查找的操作
159     //查找
160     int i;
161     i = findX(21, h);//可能也会失败,规定返回-1失败
162     printf("%d\n",i);
163     removeX(21,h);//在h里面删除21 
164     return 0;
165 }
166 
167 ***散列在C++和JAVA中的使用***
168 要注意散列只是一个数据的组织形式,并不真的是一个数据结构
169 另外,map和set在底层源码使用树实现,会排序;
170 如果我们对数据的顺序没有要求,只是对大负荷数据查找有要求,可以使用unordered_set unordered_map,是使用散列实现的
171 
172 #include<iostream>
173 #include<map>
174 #include<set>
175 #include<unordered_map>
176 #include<unordered_set>
177 
178 using namespace std;
179 
180 int main(){
181     unordered_set<int> s;
182     unordered_map<int,int> m;
183     //要注意下面方框的并不是下标 而是value
184     m[101] = 33;
185     m[-10] = 2223;
186     return 0;
187 }
188 
189 import java.util.Map;
190 import java.util.Set;
191 import java.util.HashMap;
192 import java.util.TreeMap;
193 import java.util.HashSet;
194 import java.util.TreeSet;
195 //大量查找的时候hashmap是O(N),treemap是O(logN) 因为是后者是树的组织形式
196 //Map和Set都是JAVA的接口,要用下面的那些类去实体化
197 //注意JAVA里面没有m[value] = key的用法,只有put

 

posted @ 2021-07-26 17:38  WriteOnce_layForever  阅读(51)  评论(0编辑  收藏  举报