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