Skip to content

Commit 682da98

Browse files
committed
📝 Writing docs.
1 parent 25fea1b commit 682da98

File tree

1 file changed

+302
-0
lines changed

1 file changed

+302
-0
lines changed

docs/search/linear-list-search.md

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
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+
![image.gif](https://upload-images.jianshu.io/upload_images/3101171-a38f84148d091364.gif?imageMogr2/auto-orient/strip)
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+
![image.png](https://upload-images.jianshu.io/upload_images/3101171-b7ad44c68d0c3c75.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
154+
155+
**基本思想**
156+
157+
分块查找算法有两个处理步骤:
158+
159+
**(1) 首先查找索引表**
160+
161+
因为分块查找表是“分块有序”的,所以我们可以通过索引表来锁定关键字所在的区间。
162+
163+
又因为索引表是递增有序的,所以查找索引可以使用顺序查找或二分查找。
164+
165+
**(2) 然后在已确定的块中进行顺序查找**
166+
167+
因为块中不一定是有序的,所以只能使用顺序查找。
168+
169+
**代码范例**
170+
171+
![image](http://upload-images.jianshu.io/upload_images/3101171-2737612c781e66e8.gif?imageMogr2/auto-orient/strip)
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

Comments
 (0)