|
| 1 | +# 线性表的查找 |
| 2 | + |
| 3 | +## 概念 |
| 4 | + |
| 5 | +### 什么是查找? |
| 6 | + |
| 7 | +**查找**是根据给定的某个值,在表中确定一个关键字的值等于给定值的记录或数据元素。 |
| 8 | + |
| 9 | +### 查找算法的分类 |
| 10 | + |
| 11 | +若在查找的同时对表记录做修改操作(如插入和删除),则相应的表称之为**动态查找表**; |
| 12 | + |
| 13 | +否则,称之为**静态查找表**。 |
| 14 | + |
| 15 | +此外,如果查找的全过程都在内存中进行,称之为**内查找**; |
| 16 | + |
| 17 | +反之,如果查找过程中需要访问外存,称之为**外查找**。 |
| 18 | + |
| 19 | +### 查找算法性能比较的标准 |
| 20 | + |
| 21 | +**——平均查找长度ASL(Average Search Length)** |
| 22 | + |
| 23 | +由于查找算法的主要运算是关键字的比较过程,所以通常把查找过程中对关键字需要执行的**平均比较长度**(也称为**平均比较次数**)作为衡量一个查找算法效率优劣的比较标准。 |
| 24 | + |
| 25 | + |
| 26 | + |
| 27 | +**选取查找算法的因素** |
| 28 | + |
| 29 | +(1) 使用什么数据存储结构(如线性表、树形表等)。 |
| 30 | + |
| 31 | +(2) 表中的次序,即对无序表还是有序表进行查找。 |
| 32 | + |
| 33 | +## 顺序查找 |
| 34 | + |
| 35 | +**要点** |
| 36 | + |
| 37 | +它是一种最简单的查找算法,效率也很低下。 |
| 38 | + |
| 39 | +**存储结构** |
| 40 | + |
| 41 | +没有存储结构要求,可以无序,也可以有序。 |
| 42 | + |
| 43 | +**基本思想** |
| 44 | + |
| 45 | +从数据结构线形表的**一端**开始,**顺序扫描**,**依次**将扫描到的结点关键字与给定值k相**比较**,若相等则表示查找成功; |
| 46 | + |
| 47 | +若扫描结束仍没有找到关键字等于k的结点,表示查找失败。 |
| 48 | + |
| 49 | +**核心代码** |
| 50 | + |
| 51 | +```java |
| 52 | +public int orderSearch(int[] list, int length, int key) { |
| 53 | + // 从前往后扫描list数组,如果有元素的值与key相等,直接返回其位置 |
| 54 | + for (int i = 0; i < length; i++) { |
| 55 | + if (key == list[i]) { |
| 56 | + return i; |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + // 如果扫描完,说明没有元素的值匹配key,返回-1,表示查找失败 |
| 61 | + return -1; |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +**算法分析** |
| 66 | + |
| 67 | +顺序查找算法**最好的情况**是,第一个记录即匹配关键字,则需要比较 **1** 次; |
| 68 | + |
| 69 | +**最坏的情况**是,最后一个记录匹配关键字,则需要比较 **N** 次。 |
| 70 | + |
| 71 | +所以,顺序查找算法的平均查找长度为 |
| 72 | + |
| 73 | +ASL = (N + N-1 + ... + 2 + 1) / N = (N+1) / 2 |
| 74 | + |
| 75 | +顺序查找的**平均时间复杂度**为**O(N)**。 |
| 76 | + |
| 77 | +## 二分查找 |
| 78 | + |
| 79 | +**要点** |
| 80 | + |
| 81 | +二分查找又称**折半查找**,它是一种效率较高的查找方法。 |
| 82 | + |
| 83 | +**存储结构** |
| 84 | + |
| 85 | +使用二分查找需要两个前提: |
| 86 | + |
| 87 | +(1) 必须是**顺序**存储结构。 |
| 88 | + |
| 89 | +(2) 必须是**有序**的表。 |
| 90 | + |
| 91 | +**基本思想** |
| 92 | + |
| 93 | +首先,将表**中间位置**记录的关键字与查找关键字比较,如果两者相等,则查找成功; |
| 94 | + |
| 95 | +否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。 |
| 96 | +重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。 |
| 97 | + |
| 98 | +**核心代码** |
| 99 | + |
| 100 | +```java |
| 101 | +public int binarySearch(int[] list, int length, int key) { |
| 102 | + int low = 0, mid = 0, high = length - 1; |
| 103 | + while (low <= high) { |
| 104 | + mid = (low + high) / 2; |
| 105 | + if (list[mid] == key) { |
| 106 | + return mid; // 查找成功,直接返回位置 |
| 107 | + } else if (list[mid] < key) { |
| 108 | + low = mid + 1; // 关键字大于中间位置的值,则在大值区间[mid+1, high]继续查找 |
| 109 | + } else { |
| 110 | + high = mid - 1; // 关键字小于中间位置的值,则在小值区间[low, mid-1]继续查找 |
| 111 | + } |
| 112 | + } |
| 113 | + return -1; |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +**算法分析** |
| 118 | + |
| 119 | +**二分查找的过程可看成一个二叉树**。 |
| 120 | + |
| 121 | +把查找区间的中间位置视为树的根,左区间和右区间视为根的左子树和右子树。 |
| 122 | + |
| 123 | +由此得到的二叉树,称为二分查找的判定树或比较树。 |
| 124 | + |
| 125 | +由此可知,二分查找的**平均查找长度**实际上就是树的高度**O(log<sub>2</sub>N)**。 |
| 126 | + |
| 127 | +## 分块查找 |
| 128 | + |
| 129 | +**要点** |
| 130 | + |
| 131 | +分块查找(Blocking Search)又称**索引顺序查找**。它是一种性能介于顺序查找和二分查找之间的查找方法。 |
| 132 | + |
| 133 | +分块查找由于只要求索引表是有序的,对块内节点没有排序要求,因此特别适合于节点动态变化的情况。 |
| 134 | + |
| 135 | +**存储结构** |
| 136 | + |
| 137 | +分块查找表是由**“分块有序”的线性表**和**索引表**两部分构成的。 |
| 138 | + |
| 139 | +所谓**“分块有序”的线性表**,是指: |
| 140 | + |
| 141 | +假设要排序的表为R[0...N-1],**将表均匀分成b块**,前b-1块中记录个数为s=N/b,最后一块记录数小于等于s; |
| 142 | + |
| 143 | +每一块中的关键字不一定有序,但**前一块中的最大关键字必须小于后一块中的最小关键字**。 |
| 144 | + |
| 145 | +***注:这是使用分块查找的前提条件。*** |
| 146 | + |
| 147 | +如上将表均匀分成b块后,抽取各块中的**最大关键字**和**起始位置**构成一个索引表IDX[0...b-1]。 |
| 148 | + |
| 149 | +由于表R是分块有序的,所以**索引表是一个递增有序表**。 |
| 150 | + |
| 151 | +下图就是一个分块查找表的存储结构示意图 |
| 152 | + |
| 153 | + |
| 154 | + |
| 155 | +**基本思想** |
| 156 | + |
| 157 | +分块查找算法有两个处理步骤: |
| 158 | + |
| 159 | +**(1) 首先查找索引表** |
| 160 | + |
| 161 | +因为分块查找表是“分块有序”的,所以我们可以通过索引表来锁定关键字所在的区间。 |
| 162 | + |
| 163 | +又因为索引表是递增有序的,所以查找索引可以使用顺序查找或二分查找。 |
| 164 | + |
| 165 | +**(2) 然后在已确定的块中进行顺序查找** |
| 166 | + |
| 167 | +因为块中不一定是有序的,所以只能使用顺序查找。 |
| 168 | + |
| 169 | +**代码范例** |
| 170 | + |
| 171 | + |
| 172 | + |
| 173 | +```java |
| 174 | +class BlockSearch { |
| 175 | + |
| 176 | + class IndexType { |
| 177 | + public int key; // 分块中的最大值 |
| 178 | + public int link; // 分块的起始位置 |
| 179 | + } |
| 180 | + |
| 181 | + // 建立索引方法,n 是线性表最大长度,gap是分块的最大长度 |
| 182 | + public IndexType[] createIndex(int[] list, int n, int gap) { |
| 183 | + int i = 0, j = 0, max = 0; |
| 184 | + int num = n / gap; |
| 185 | + IndexType[] idxGroup = new IndexType[num]; // 根据步长数分配索引数组大小 |
| 186 | + |
| 187 | + while (i < num) { |
| 188 | + j = 0; |
| 189 | + idxGroup[i] = new IndexType(); |
| 190 | + idxGroup[i].link = gap * i; // 确定当前索引组的第一个元素位置 |
| 191 | + max = list[gap * i]; // 每次假设当前组的第一个数为最大值 |
| 192 | + // 遍历这个分块,找到最大值 |
| 193 | + while (j < gap) { |
| 194 | + if (max < list[gap * i + j]) { |
| 195 | + max = list[gap * i + j]; |
| 196 | + } |
| 197 | + j++; |
| 198 | + } |
| 199 | + idxGroup[i].key = max; |
| 200 | + i++; |
| 201 | + } |
| 202 | + |
| 203 | + return idxGroup; |
| 204 | + } |
| 205 | + |
| 206 | + // 分块查找算法 |
| 207 | + public int blockSearch(IndexType[] idxGroup, int m, int[] list, int n, int key) { |
| 208 | + int mid = 0; |
| 209 | + int low = 0; |
| 210 | + int high = m -1; |
| 211 | + int gap = n / m; // 分块大小等于线性表长度除以组数 |
| 212 | + |
| 213 | + // 先在索引表中进行二分查找,找到的位置存放在 low 中 |
| 214 | + while (low <= high) { |
| 215 | + mid = (low + high) / 2; |
| 216 | + if (idxGroup[mid].key >= key) { |
| 217 | + high = mid - 1; |
| 218 | + } else { |
| 219 | + low = mid + 1; |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + // 在索引表中查找成功后,再在线性表的指定块中进行顺序查找 |
| 224 | + if (low < m) { |
| 225 | + for (int i = idxGroup[low].link; i < idxGroup[low].link + gap; i++) { |
| 226 | + if (list[i] == key) |
| 227 | + return i; |
| 228 | + } |
| 229 | + } |
| 230 | + |
| 231 | + return -1; |
| 232 | + } |
| 233 | + |
| 234 | + // 打印完整序列 |
| 235 | + public void printAll(int[] list) { |
| 236 | + for (int value : list) { |
| 237 | + System.out.print(value + " "); |
| 238 | + } |
| 239 | + System.out.println(); |
| 240 | + } |
| 241 | + |
| 242 | + // 打印索引表 |
| 243 | + public void printIDX(IndexType[] list) { |
| 244 | + System.out.println("构造索引表如下:"); |
| 245 | + for (IndexType elem : list) { |
| 246 | + System.out.format("key = %d, link = %d\n", elem.key, elem.link); |
| 247 | + } |
| 248 | + System.out.println(); |
| 249 | + } |
| 250 | + |
| 251 | + public static void main(String[] args) { |
| 252 | + int key = 85; |
| 253 | + int array[] = { 8, 14, 6, 9, 10, 22, 34, 18, 19, 31, 40, 38, 54, 66, 46, 71, 78, 68, 80, 85 }; |
| 254 | + BlockSearch search = new BlockSearch(); |
| 255 | + |
| 256 | + System.out.print("线性表: "); |
| 257 | + search.printAll(array); |
| 258 | + |
| 259 | + IndexType[] idxGroup = search.createIndex(array, array.length, 5); |
| 260 | + search.printIDX(idxGroup); |
| 261 | + int pos = search.blockSearch(idxGroup, idxGroup.length, array, |
| 262 | + array.length, key); |
| 263 | + if (-1 == pos) { |
| 264 | + System.out.format("查找key = %d失败", key); |
| 265 | + } else { |
| 266 | + System.out.format("查找key = %d成功,位置为%d", key, pos); |
| 267 | + } |
| 268 | + } |
| 269 | + |
| 270 | +} |
| 271 | +``` |
| 272 | + |
| 273 | +**运行结果** |
| 274 | + |
| 275 | +``` |
| 276 | +线性表: 8 14 6 9 10 22 34 18 19 31 40 38 54 66 46 71 78 68 80 85 |
| 277 | +构造索引表如下: |
| 278 | +key = 14, link = 0 |
| 279 | +key = 34, link = 5 |
| 280 | +key = 66, link = 10 |
| 281 | +key = 85, link = 15 |
| 282 | +
|
| 283 | +查找key = 85成功,位置为19 |
| 284 | +``` |
| 285 | + |
| 286 | +**算法分析** |
| 287 | + |
| 288 | +因为分块查找实际上是两次查找过程之和。若以二分查找来确定块,显然它的查找效率介于顺序查找和二分查找之间。 |
| 289 | + |
| 290 | +## 三种线性查找的PK |
| 291 | + |
| 292 | +(1) 以平均查找长度而言,二分查找 > 分块查找 > 顺序查找。 |
| 293 | + |
| 294 | +(2) 从适用性而言,顺序查找无限制条件,二分查找仅适用于有序表,分块查找要求“分块有序”。 |
| 295 | + |
| 296 | +(3) 从存储结构而言,顺序查找和分块查找既可用于顺序表也可用于链表;而二分查找只适用于顺序表。 |
| 297 | + |
| 298 | +(4) 分块查找综合了顺序查找和二分查找的优点,既可以较为快速,也能使用动态变化的要求。 |
| 299 | + |
| 300 | +## 资源 |
| 301 | + |
| 302 | +《数据结构习题与解析》(B级第3版) |
0 commit comments