1. 查找的概念
查找表:由一组记录组成的表或者文件,而每个记录由若干个数据项组成,并假设每个记录都有一个唯一标识该记录的关键字。
查找:查找关键字为k的记录,若找到,成功,返回该记录的信息;找不到,失败,返回相关的指示信息。
内查找:整个查找过程都在内存进行;外查找:查找过程中需要访问外存。
查找的数据组织:采用何种存储结构?(1)顺序表(2)链表(3)其他。
若在查找的同时对表做修改操作(如插入和删除),则相应的表称之为动态查找表;否则称之为静态查找表。
影响查找的因素:(1)使用哪种数据结构表示“表”,即表中记录是按何种方式组织的;(2)表中关键字的次序,无序集合查找还是有序集合查找。
查找方法的性能指标:查找运算时间主要花费在关键字比较上,通常把查找过程中执行的关键字平均比较次数(平均查找长度)作为衡量一个查找算法效率优劣的标准。
平均查找长度:
A
S
L
=
∑
i
=
1
n
p
i
c
i
ASL=\sum_{i=1}^{n}p_ic_i
ASL=∑i=1npici,其中n是查找表中记录的个数,
p
i
p_i
pi是查找第i个记录的概率,一般为1/n,
c
i
c_i
ci是找到第i个记录所需进行的比较次数。
A
S
L
成
功
ASL_{成功}
ASL成功是指找到查找表中任一记录平均需要的关键字比较次数。
A
S
L
不
成
功
ASL_{不成功}
ASL不成功是指查找失败平均需要的关键字比较次数。
2. 线性表的查找
线性表的存储结构:顺序表–静态查找表;链表–动态查找表。
主要方法:(1)顺序查找;(2)二分查找;(3)分块查找。
顺序查找
从表的一端开始,顺序扫描线性表,依次将扫描到的关键字和给定值k相比较。
若当前扫描的关键字与k相等,则查找成功;若扫描结束后,仍未找到关键字等于k的记录,则查找失败。
(1)成功情况下的ASL
查找到表中第i个记录时,需要比较i次。
A
S
L
=
1
/
n
∑
i
=
1
n
i
=
(
n
+
1
)
/
2
ASL=1/n\sum_{i=1}^{n}i=(n+1)/2
ASL=1/n∑i=1ni=(n+1)/2,为表长的一半;
(2)不成功情况下的ASL
失败时,每次查找都需要和表中所有元素比较一次,
A
S
L
=
n
ASL=n
ASL=n.
时间复杂度为
O
(
n
)
O(n)
O(n)。
#include <iostream>
using namespace std;
#define MaxL 100
typedef int KeyType;
typedef int InfoType;
struct NodeType{//顺序表的节点类型
KeyType key;//关键字
InfoType data;//其他数据项
};
typedef NodeType SeqList[MaxL];//查找顺序表类型
int SeqSearch(SeqList R, int n, KeyType k){//顺序查找
int i = 0;
while(i < n && R[i].key != k)
i++;
if (i >= n) return 0;//未找到
else return i+1;//找到并返回逻辑序号i+1
}
折半(二分)查找
将k与R[mid].key比较,然后在左区间或者右区间进行折半查找。
因为折半查找需要方便地定位查找区域,所以它要求线性表必须具有随机存取的特性。因此,该方法进适合于顺序存储结构,不适用于链式存储结构,且要求线性表中的记录必须按照关键字有序(递增或者递减)排列。
二分查找的过程可用二叉树来描述:(1)把当前查找区间的中间位置的记录记为根;(2)左子表和右子表中的记录分别作为根的左子树和右子树。
时间复杂度分析
查找成功:恰好是走了一条从根到被查找记录的路径,经历的关键字比较次数恰为该记录在树中的层数;查找失败:比较过程经历了一条从根到某个外部节点的路径,所需的关键字比较次数是该路径上内部节点的总数。
当n较大时,将判定树看成内部节点总数
n
=
2
h
−
1
n=2^h-1
n=2h−1高度为
h
=
l
o
g
2
(
n
+
1
)
h=log_2(n+1)
h=log2(n+1)的满二叉树。用折半查找方法查找到给定值的比较次数最多不会超过树的高度。
A
S
L
成
功
=
1
/
n
∑
j
=
1
h
j
∗
2
j
−
1
=
(
n
+
1
)
/
n
l
o
g
2
(
n
+
1
)
−
1
≈
l
o
g
2
(
n
+
1
)
−
1
ASL_{成功}=1/n\sum_{j=1}^{h}j*2^{j-1}=(n+1)/nlog_2(n+1)-1 \approx log_2(n+1)-1
ASL成功=1/n∑j=1hj∗2j−1=(n+1)/nlog2(n+1)−1≈log2(n+1)−1。
不成功时,比较次数为树的高度,为
l
o
g
2
(
n
+
1
)
log_2(n+1)
log2(n+1)。所以,折半查找的时间复杂度为
l
o
g
2
(
n
)
log_2(n)
log2(n)。
int BinSearch(SeqList R, int n, KeyType k){//折半查找
int left = 0, right = n -1, mid;
while (left <= right) {//当前区间存在元素时循环
mid = (right + left) / 2;
if (R[mid].key == k)
return mid + 1;//查找成功
else if (k < R[mid].key)
right = mid - 1;//从前半部分继续查找
else
left = mid + 1;//从后半部分继续查找
}
return -1;//查找失败
}
int main()
{
SeqList R;
for (int i=0; i < 10; i++){//创建一个简单的查找表
R[i].key = i;
R[i].data = -i;
}
cout << "关键字为5的数据位置为:" << BinSearch(R,10,5) << endl;
return 0;
}
分块查找
基本思想:将查找表分为若干子块。块内的元素可以无序,但块之间是有序的,即前一个块中的最大关键字小于后一个块中的所有记录的关键字。索引表中的每个元素含有各块的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排列。
查找的过程:第一步是在索引表中确定待查记录所在的块,可以顺序查找或折半查找;第二步是在块内顺序查找。
分块查找的性能介于顺序查找和二分查找之间。