数据结构是程序设计的重要基础,它所讨论的内容和技术对从事软件项目的开发有重要作用。学习数据结构要达到的目标是学会从问题出发,分析和研究计算机加工的数据的特性,以便为应用所涉及的数据选择适当的逻辑结构、存储结构及其相应的操作方法,为提高利用计算机解决问题的效率服务。
1、顺序查找
顺序查找的基本思想是:从表的一端开始,逐个将记录的关键字和给定值比较,若找到一个记录的关键字与给定值相等,则查找成功;若整个表中的记录均比较过,仍未找到关键字等于给定值的记录,则查找失败。
顺序查找的方法对于顺序存储方式和链式存储方式的查找表都适用。
从顺序查找的过程可知,Ci 取决于所查记录在表中的位置。若需查找的记录正好是表中的第一个记录,仅需比较一次; 若查找成功时找到的是表中的最后一个记录,则需比较 n 次。从表尾开始查找时正好相反。一般情况下,Ci=n-i+1,因此在等概率情况下,顺序查找成功的平均查找长度为
A
S
L
s
s
=
Σ
i
=
1
n
P
i
C
i
=
1
n
Σ
i
=
1
n
(
n
−
i
+
1
)
=
n
+
1
2
ASL_{ss}={\huge\Sigma}^n_{i=1}{\large P}_i {\large C}_i =\frac1n {\huge\Sigma}^n_{i=1}(n-i+1)=\frac{n+1}2
ASLss=Σi=1nPiCi=n1Σi=1n(n−i+1)=2n+1
也就是说,成功查找的平均比较次数约为表长的一半。若所查记录不在表中,则必须进行n次(不设监视哨,设置监视哨时为 n+1 次) 比较才能确定失败。监视哨是指查找表用一维数组存储时,将待查找的记录放置在查找表的第一个记录之前或最后一个记录之后,从而在查找过程中不需要对数组元素的下标进行合法性检查。
与其他查找方法相比,顺序查找方法在 n 值较大时,其平均查找长度较大,查找效率较低。但这种方法也有优点,那就是算法简单且适应面广,对查找表的结构没有要求,无论记录是否按关键字有序排列均可应用。
2、折半查找
设查找表的元素存储在一维数组 r[1,···,n] 中,在表中的元素已经按关键字递增方式排序的情况下,进行折半查找的方法是:先将待查元素的关键字 (Key)值与表 r 中间位置上(下标为 mid) 记录的关键字进行比较,若相等,则查找成功;若 key>r[mid].key,则说明待查记录只可能在后半个子表 r[mid+1,···,n] 中,下一步应在后半个子表中进行查找:若 key<r(mid].key,说明待查记录只可能在前半个子表 r[1,···,mid-1] 中,下一步应在 r 的前半个子表中进行查找,这样逐步缩小范围,直到查找成功或子表为空时失败为止。
【函数】 设有一个整型数组中的元素是按非递减的方式排列的,在其中进行折半查找的算法为:
int Bsearch(int r[], int low, int high, int key)
/*元素存储在数组 r[low..high],用折半查找的方法在数组r中找值为 key 的元素*/
/*若找到返回该元素的下标,否则返回-1*/
{
int mid;
while(low <= high) {
mid = (low+high)/2;
if(key == r[mid]) return mid;
else if (key<r[mid]) high = mid-l;
else low = mid+1;
}/*while*/
return -1;
}/*Bsearch*/
【函数】设有一个整型数组中的元素是按非递减的方式排列的,在其中进行折半查找的递归算法如下:
int Bsearch_rec(int r[], int low, int high, int key)
/*元素存储在数组 r[low..high],用折半查找的方法在数组r中找值为 key 的元素*/
/*若找到返回该元素的下标,否则返回-1*/
{
int mid;
if (low <= high){
mid = (low+high)/2;
if (key == r[mid])
return mid;
else if (key<r[mid])
return Bsearch_rec(r, low, mid-1, key);
else
retumn Bsearch_rec(r, mid+1, high, key);
}/*if*/
retum -1;
}/*Bsearch_rec*/
折半查找的性能分析如下。
折半查找的过程可以用一棵二叉树描述,方法是以当前查找区间的中间位置序号作为根,左半个子表和右半个子表中的记录序号分别作为根的左子树和右子树上的结点,这样构造的二叉树称为折半查找判定树。例如,具有 11 个结点的折半查找判定树如下图所示。
从折半查找判定树可以看出,查找成功时,折半查找的过程恰好走了一条从根结点到被查找结点的路径,与关键字进行比较的次数即为被查找结点在树中的层数。因此,折半查找在查找成功时进行比较的关键字个数最多不超过树的深度,而具有 n 个结点的判定树的深度为「log2n」+1,所以折半查找在查找成功时和给定值进行比较的关键字个数最多为「log2n」+1。
给判定树中所有结点的空指针域加一个指向方形结点的指针,称这些方形结点为判定树的外部结点(与之相对,称那些圆形结点为内部结点),如下图所示。那么折半查找不成功的过程就是走了一条从根结点到外部结点的路径。与给定值进行比较的关键字个数等于该路径上内部结点个数,因此折半查找在查找不成功时和给定值进行比较的关键字个数最多也不会超「log2n」+1。
那么折半查找的平均查找长度是多少呢?为了方便起见,不妨设结点总数为 n=2h-1,则判定树是深度为h=log2(n+1)的满二叉树。在等概率情况下,折半查找的平均查找长度为
A
S
L
b
s
=
Σ
j
=
1
n
P
i
C
i
=
1
n
Σ
j
=
1
n
j
×
2
j
−
1
=
n
+
1
n
㏒
2
(
n
+
1
)
−
1
ASL_{bs}={\huge\Sigma}^n_{j=1}{\large P}_i {\large C}_i =\frac1n {\huge\Sigma}^n_{j=1}j×2^{j-1}=\frac{n+1}n㏒_2(n+1)-1
ASLbs=Σj=1nPiCi=n1Σj=1nj×2j−1=nn+1㏒2(n+1)−1
当n值较大时,ASLbs ≈ log2(n+I)-1。
折半查找比顺序查找的效率要高,但它要求查找表进行顺序存储并且按关键字有序排列。因此,当需要对表进行插入或删除操作时,需要移动大量的元素。所以折半查找适用于表不易变动,且又经常进行查找的情况。
3、分块查找
分块查找又称索引顺序查找,是对顺序查找方法的一种改进,其效率介于顺序查找与折半查找之间。
在分块查找过程中,首先将表分成若干块,每一块的关键字不一定有序,但块之间是有序的,即后一块中所有记录的关键字均大于前一个块中最大的关键字。此外,还建立了一个“索引表”,索引表按关键字有序,如下图 所示。
因此,分块查找过程分为两步:第一步在索引表中确定待查记录所在的块;第二步在块内顺序查找。
由于分块查找实际上是两次查找的过程,因此其平均查找长度应该是两次查找的平均查找长度(索引查找与块内查找)之和,即
A
S
L
b
s
=
L
b
+
L
w
ASL_{bs} = L_b +L_w
ASLbs=Lb+Lw
其中,Lb为查找索引表的平均查找长度,Lw为块内查找时的平均查找长度。
分块查找时,可将长度为 n 的表均匀地分成 b 块,每块含有 s 个记录,即有
b
=
n
s
b=\frac ns
b=sn。在等概率查找的情况下,块内查找的概率为
b
=
1
s
b=\frac 1s
b=s1,每块的查找概率为
b
=
1
b
b=\frac 1b
b=b1,则分块查找的平均查找长度为
A
S
L
b
s
=
L
b
+
L
w
=
1
b
Σ
j
=
1
n
j
+
1
s
Σ
i
=
1
s
i
=
b
+
1
2
+
s
+
1
2
=
1
2
{
n
s
+
s
}
+
1
ASL_{bs}= L_b +L_w = \frac1b{\huge\Sigma}^n_{j=1}j+\frac1s {\huge\Sigma}^s_{i=1}i=\frac{b+1}2 +\frac{s+1}2 = \frac12{\huge\{} \frac ns+s{\huge\}}+1
ASLbs=Lb+Lw=b1Σj=1nj+s1Σi=1si=2b+1+2s+1=21{sn+s}+1
可见,其平均查找长度不仅与表长n有关,而且与每一块的记录数 s 有关。可以证明,当s取√n 时,ASLbs取最小值√n+1,此时的查找效率较顺序查找要好得多,但远不及折半查找。
考虑到索引表是一个有序表,因此可以用折半查找确定元素所在的块。