//查找表的数据结构——顺序表
typedef struct{
ElemType *elem; //动态数组的基址
int TableLen; //表的长度
}SSTable;
//顺序查找
//法一:找到数组长度末尾为止(从头到尾找,查找表第一个元素下标为0)
int Search_Seq(SSTable ST, ElemType key)
{
for(int i = 0; i < ST.TableLen && ST.elem[i] != key; i++)
{ //查找成功或者没找到时跳出循环
}
return (i==ST.TableLen? -1 : i);//查找成功时返回元素下标,没找到时返回-1
}
//法二:找到哨兵为止((从尾到头找查找表第一个元素下标为1,0存放哨兵)
int Search_Seq(SSTable ST, ElemType key)
{
ST.elem[0]=key; //在数组第一个(下标为0)位置放置哨兵
for(int i = ST.TableLen; ST.elem[i] != key; i--)
{ //查找成功(找到对应元素)或者没找到(找到哨兵)时跳出循环
}
return i; //查找成功时返回对应次序,没找到时返回0
}




//查找表的数据结构——顺序表
typedef struct{
ElemType *elem; //动态数组的基址
int TableLen; //表的长度
}SSTable;
//折半查找——增序序列(成功返回数组下标,失败返回-1)
int Binary_Search(SSTable L, ElemType key)
{ //准备指针(下标)
int low = 0;
int high = L.TableLen - 1;
int mid;
while(low<=high)
{mid = (low + high)/2;
if(key == L.elem[mid])
{return mid;}
else if(key < L.elem[mid])
{high = mid - 1;}
else
{low = mid + 1;}
return -1;
}
}

分块查找又称为索引顺序查找,又有动态结构,有适合于快速查找。
基本思想:将查找表分为若干个子块,块内的元素可以无序,但块之间是有序的,即第一个块中的最大关键字小于第二个块中的所有记录的关键字,第二个快中的最大关键字小于第三个快中的所有记录的关键字。在建立一个索引表,索引表中的每个元素含有各块的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排列。也就是:块内无序,块间有序。
索引表中保存每个块的最大关键字和分块的存储区间。
过程:
①在索引表中确定待查记录所属的分块(可顺序、可折半)
②在块内顺序查找
例如:

① 使用指针p,现在索引表中使用顺序查找算法,聚焦到p为2,值为30
② 在2的块内进行顺序查找或者二分查找
③如果p在索引表中查找超出索引长度或者在块内查找,超出块内长度,则认为查找失败
代码:
//索引表——保存每个分块的最⼤关键字和分块的存储区间
typedef struct {
ElemType maxValue;
int low, high;
}Index;
//顺序表——存储实际元素
ElemType List[100];
使用顺序查找的方法


使用折半查找:
30 :4次,第一轮就发现在索引表中mid就是所需的30,则会进入30这个块查找三次,一共1+3=4次。
27:第一轮,会查找mid为三十不是27,且大于27,则会让high指向20,low仍然是10;第二轮会让mid指向10,27>10,则low指向mid+1,为20,high仍然是20;第三轮,20<27,则low指向30,high指向20,此时会进入30这个块,然后第一个发现27,查找成功,因此27一共查找了四次。
算出所有元素的次数,然后相乘概率1/n,然后相加,得到ASL。
以上都是查找成功的ASL,失败的ASL的复杂,则跳过。
1.假设,⻓度为n的查找表被均匀地分为b块,每块s个元素,设索引查找和块内查找的平均查找⻓度分别为LI、LS,则分块查找的平均查找⻓度为ASL=LI + LS
(1)⽤顺序查找查索引表,

(2)使用折半查找索引表

若查找表是动态查找表,插入新的元素会让表整体移动,会付出很大的代价,因此使用链式存储方式,增加元素在链表尾部加入新结点即可。
(我考的学校不考)
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| 值 | 14/1 | 19 |
如表:19/13=6,存在6下标的位置,但是14和1形成了冲突
5. 用拉链法(又称为链接法、链地址法)处理冲突:把所有“同义词”存储在一个链表中。
6. 理想情况下,对散列表查找长度为0,时间复杂度为O(1),与表中的元素无关。
7. 例题:


查找失败是:存储单元格,没有元素的查找记为0次,拥有单元格的元素记为元素个数次,相加,除以单元格总数。
① 装填因子:表中记录数/散列表的长度
② 装填因子越小,说明空缺越大,会影响效率。
上述的冲突过大,所以我们要重新设计平均查找长度去提高查找效率。
散列查找实质“用空间换时间”
di=0,1,2,3,4…m-1,即发生冲突时,每次往后探测相邻的下一个单元是否为空。
(1)填入表格
例: 数组:{19,14,23,1,68,20,84,27,55,11,10,79},散列函数H(key)=key%13。
①
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 值 | 14 | 19 | 23 | |||||||||||||
| ② |
1 H(1)=(1)%13=1,与14 冲突,则进行di=1,使用递推公式:H(1)=(1+1)%16=2,则存入2中
后续H(68)=(68)%13=3 H(20)=(20)%13=7
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 值 | 14 | 1 | 68 | 19 | 20 | 23 |
③
84 H(84)=(84)%13=6 与19发生冲突,则使用递推公式H(84)=(6+0)%16=6,还冲突,di=1 H(84)=(6+1)%16=7,还冲突,di=2,H(84)=(7+2)%16=8 。则存入8位置
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 值 | 14 | 1 | 68 | 19 | 20 | 84 | 23 |
④ 最终结果
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 值 | 14 | 1 | 68 | 27 | 55 | 19 | 20 | 84 | 79 | 23 | 11 | 10 | 25 |
发现使用散列函数的位置为0~12,加入递推公式以后,会出现0 ~15的位置
(2)算法查找
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 值 | 14 | 1 | 68 | 27 | 55 | 19 | 20 | 84 | 79 | 23 | 11 | 10 | 25 |
查找27
① H(27)=(27)%13=1,b[1]=14!=27
② 往后移动探测后面位置,2,3,4,得到27,查找长度为4
规律:
① 既可以与同义词(14)查找对比,又可以和非同义词(84)进行对比。
② 找不到元素时,需要将空位置看成一个非合法数据,也算一次查找次数。
③ 因为查找是找到第一个空位置,则算查找失败就不会查找后序,因此越早遇到空位置,查找效率越早得到提升。
(3)删除操作
注意:不可以简单的将被删节点的位置设为空,否则会截断它之后填入散列表的同义词结点的查找路径,可以做一个删除标记,进行逻辑删除。
(4)缺点:
如果删除的结点过多,会造成大量空间的浪费,并且在查找时,还是需要对比那些删除标记的地址,算作一次查找次数。
(5)查找效率分析(ASL)


问:为什么失败这么高?
答:线性探测法很容易造成同义词、⾮同义词的“聚集(堆积)”现象,严重影响查找效率,产⽣原因——冲突后再探测⼀定是放在某个连续的位置
散列表长度必须是一个可以表示成4k+3的素数,才可以检测到所有位置,此方法可以避免堆积问题,但是不能探测到所有的单元,至少能探测到一半单元。
(1)实现方法:di=0,1,-1,2^ 2,-2 ^ 2,3 ^ 2,-3^2……k ^2,-k ^2.
比如 6 19 32 45 58 71在散列函数H(key)=key%13,的条件下,都与6发生冲突,表长为27
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 值 | 58 | 32 | 6 | 19 | 45 | 71 |
① 存入19 使用递推公式:h1=(6+1)%27=7,存入7位置
② 存入32,先于6发生冲突,h1=(6+1)%27=7,与19发生冲突,h2=(7+(-1))%27=5,存入5位置
③ 存入 45,探测6、7、5都冲突,则h3=(5+2^2)%27=10,不冲突,存入10位置
④ 最终结果如上述
(2)查找过程:上述一样的过程
di是一个伪随机序列,指定的一个数据,比如di=0,5,24,11
例如数据为6,19,32,45,在19与6冲突以后,则往右寻找5个得到自己的位置11
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 值 | 32 | 6 | 19 | 45 |
再散列法(再哈希法):除了原始的散列函数 H(key) 之外,多准备⼏个散列函数,当散列函数冲突时,⽤下⼀个散列函数计算⼀个新地址,直到不冲突。
