
8-3內插搜尋法
內插搜尋法(Interpolation Search)又叫做插補循法,是二分搜尋法的改進版。它是依照資料的分布,利用公式預測資料的所在位置,再以二分法的方式漸漸逼近。使用內插法是假設資料平均分布在陣列中,而每一筆資料的差距是相當接近或有一定的距離比例。其內插搜尋法的公式為:

其中key是要尋找的鍵,data[high]、data[low]是剩餘待尋找記錄中的最大值及最小值,對資料筆數為n,其內插搜尋法的步驟如下:


JS interpolation.js
var Interpolation=(data, val)=> {
low=0;
high=49;
console.log('搜尋處理中......');
while (low<=high && val!=-1) {
const rangeDelta=data[high]-data[low];
const indexDelta=high-low;
const valueDelta=val-data[low];
if (valueDelta<0) {
return -1;
}
if (!rangeDelta) {
return data[low] === val ? low: -1;
}
const mid=low+Math.floor((valueDelta*indexDelta)/ rangeDelta);
//內插法公式
if (val==data[mid])
return mid;
else if (val < data[mid]) {
console.log(val,'介於位置',low+1,'[',data[low],']及中間值'
,mid+1,'[',data[mid],'],找左半邊"');
high=mid+1;
}
else {
console.log(val,'介於中間值位置',mid+1,'[',data[mid],']及'
,(high+1),'[',data[high],']找右半邊');
low=mid+1;
}
}
return -1;
}
val=1;
var data=new Array();
for (i=0; i<50; i++) {
data[i]=val;
val=val+Math.floor(Math.random()*5)+1;
}
while (true) {
num=0;
const prompt=require('prompt-sync')();
val = prompt('請輸入搜尋鍵值(1-150),輸入-1離開:');
if (val ==-1) break;
num=Interpolation(data, val);
if (num==-1) console.log('##### 沒有找到[',val,']#####');
else console.log('在第',num+1,'個位置找到 [',data[num],']');
}
console.log('資料內容:');
for (i=0; i<5; i++) {
for (j=0; j<10; j++) {
process.stdout.write((i*10+j+1)+'-'+data[i*10+j]+' ');
}
console.log();
}

$data=random_num_arr(1,150,50);
$data=sort_select($data); //排序
$_SESSION['data']=$data; //存入
//print_arr($data);
if ($_POST['submit']!='送出' ){
echo "
<center>
<form method='post' action='{$_SERVER['[PHP_SELF]']}'>
這是內插搜尋法(interpolation_serach)
<br>請輸入欲搜索的數字(1-150)
<input name='number' type='text'>
<input type='submit' name='submit' value='送出''>
</form>
</center>
";
}
else
{
$number=$_POST['number'];
$val=$number;
//echo $number;
echo "您輸入的數字:".$number."<br><br>";
$data = $_SESSION['data']; //取出
$show=0;
while (true) {
$num=interpolation_serach($data, $val);
//echo "<br>".$num."<br>";
if ($num==-1){
echo $val."<br>********沒有找到********";
$show=1;
break;
}else{
$num++;
echo "<br>在{$num}位置找到".$val."OOOOOOOO找到了OOOOOOOO";
$show=1;
break;
}
}
if($show==1){
echo "[<a href=".$_SERVER['PHP_SELF'].">再找一次</a>]";
echo "<br>亂數陣列為:<br>";
print_arr($data);
unset($_SESSION['data']); //刪除
}
}
function interpolation_serach ($arr, $val) {
$low=0;
$high=count($arr)-1;
echo "搜尋中......";
while ($low<=$high) {
$range_data=$arr[$high]-$arr[$low];
$index_data=$high-$low;
$value_data=$val-$arr[$low];
if ($value_data<0) {
return -1;
}
if (!$range_data) {
return $data[$low] === $val ? $low:-1;
}
$mid=$low+intval(($value_data*$index_data)/$range_data);
//內插法公式
if ($val==$arr[$mid])
return $mid;
elseif ($val<$arr[$mid]) {
echo "<br>值:{$val}介於位置:".$low."值:{$arr[$low]}及中間值 位置:".$mid."值:{$arr[$mid]}找左半邊";
$high=$mid-1;
}
else {
echo "<br>值:{$val}介於中間值 位置:".$mid."值:{$arr[$mid]}及 位置:".$high."
值:{$arr[$high]}找右半邊";
$low=$mid+1;
}
}
return -1;
}
function random_num_arr($a, $b, $num) {
//$a起始數字、$b結束數字、$num取出數字數量
mt_srand((double)microtime()*1000000);
$data =array();
for ($i=0; $i<$num; $i++) {
$randval=mt_rand(1,150);
if (in_array($randval, $data)){
$i--;
}else{
$data[]=$randval;
}
}
return $data;
}
function sort_select ($arr) {
//json_decode($arr);
$count=count($arr);
for ($i=0; $i<$count-1; $i++) {
$smallest=$arr[$i];
$index=$i;
for ($j=$i+1; $j<$count; $j++) {
//由i+1比較起
if($smallest>$arr[$j]){
//找出最小元素
$smallest=$arr[$j];
$index=$j;
}
}
$tmp=$arr[$i];
$arr[$i]=$arr[$index];
//兩個交換
$arr[$index]=$tmp;
}
return $arr;
}
function print_arr($arr){
$i=0;
foreach ($arr as $key =>$value){
$key=$key+1;
echo $key."[".$value."]";
echo ($i%8==0 && $i!=0) ? "<br>":" ";
$i++;
}
}
8-4費氏搜尋法
費氏搜尋法(Fibonacci Seach)又稱費伯那搜尋法,此法和二分搜尋法一樣都是以切割範圍來進行搜尋,不同的是費氏搜尋法不以對半切割,而是以費氏級數的方式切割。

費氏級數:0,1,1,2,3,5,8,13,21,34,55,89,….。也就是除了第0及第1個元素外,每個值都是前兩個值得加總。
費氏搜尋法的好處是只用到加減運算而不需用到乘法及除法,這是以電腦運算的過程來看效率會高於前兩種搜尋法。在尚未介紹費氏搜尋法之前,我們先來認識費氏搜尋樹。所謂費氏搜尋樹是以費氏級數的特性所建立的二元樹,其建立的原則如下:
1.費氏樹的左右子樹均亦為費氏樹。
2.當資料個數n決定,若想決定費氏樹的階層k值為何,我們必須找到一個最小的k值,使的費氏級數的Fib(k+1)>=n+1。
3.費氏樹的樹根為一費氏樹,且子節點與父節點的差值絕對值為費氏樹。
4.當K>=2時,費氏樹的樹根為Fib(k),左子樹為(k-1)階費氏樹(其樹根為Fib(k-1)),右子樹為(k-2)階費氏樹(其樹根為Fib(k)+Fib(k-2))。
5.若n+1值不為費氏樹的值,則可以找出存在一個m使用Fib(k+)-m=n+1,m=Fib(k+1)-(n+1),再依費氏樹的建立原則完成費氏樹的建立,最後費氏樹的各節點再減去差值m即可,並把小於1的節點去掉即可。

也就是說當資料個數為n,且我們找到一個最小的費氏數Fib(k+1)使得Fib(k+1)>=n+1。則Fib(k)就是這棵費氏樹的樹根,而Fib(k-2)則是與左右子樹開始的差值,左子樹用減的;右子樹用加的。以下來實際求取n=33的費氏樹:
由於n=33,且n+1=34為一費氏數,且我們知道費氏數列的三項特性:
Fib(0)=0
Fib(1)=1
Fib(k)=Fib(k-1)+Fib(k-2)
得知Fib(0)=0、Fib(1)=1、Fib(2)=1、Fib(3)=2、Fib(4)=3、Fib(5)=5、Fib(6)=8、Fib(7)=13、Fib(8)=21、Fib(9)=34
由上式可得知Fib(k+1)=34->k=8,建立二元樹的樹根為Fib(8)=21
左子樹樹根為Fib(8-1)=Fib(7)=13
右子樹樹根為Fib(8)+Fib(8-2)=21+8=29
依此原則我們可以建立如下的費氏樹:

費氏搜尋法是以費氏樹來找尋資料,如果資料的個數為n,而且n比某一費氏數小,且滿足如下的運算式:
Fib(k+1)>=n+1
此時Fib(k)就是這棵費氏樹的樹根,而Fib(k-2)則是與左右子樹開始的插值,若我們要尋找的鍵值為key,首先比較陣列索引Fib(k)和鍵值key,此時可以有下列三種比較情況:
1.當key值比較小,表示所找的鍵值key落在1到Fib(k)-1之間,故繼續尋找1到Fib(k)-1之間的資料。
2.如果鍵值與陣列索引Fib(k)的值相等,表示成功搜尋到所要的資料。
3.當key值比較大,表示所找的鍵值key落在Fib(k)+1到Fib(k+1)-1之間,故繼續尋找Fib(k)+1到Fib(k+1)-1之間的資料。
費氏搜尋法分析
1.平均而言,費氏搜尋法的比較次數會少於二元搜尋法,但在最壞的情況下則二元搜尋法較快。其平均時間複雜度為O(log2N)
2.費氏搜尋演算法較為複雜,需額外產生費氏樹。

JS fib_search.js
const MAX=20;
var fib=(n)=> {
if (n==1 || n==0)
return n;
else {
return fib(n-1)+fib(n-2);
}
}
var fib_search=(data, SearchKey)=> {
index=2;
//費氏數列的搜尋
while (fib(index)<=MAX) index+=1;
index-=1;
//index >=2
//起始的費氏數
RootNode=fib(index);
//上一個費氏樹
diff1=fib(index-1);
//上二個費氏樹即diff2=fib(index-2)
diff2=RootNode-diff1;
RootNode -=1;
//這列運算是配合陣列的索引是從0開始儲存資料
while (true) {
if (SearchKey==data[RootNode])
return RootNode;
else
if (index==2)
return MAX;
//沒有找到
if (SearchKey<data[RootNode]) {
RootNode=RootNode-diff2;
//左子樹的新費氏數
temp=diff1;
diff1=diff2;
//上一個費氏數
diff2=temp-diff2;
//上二個費氏數
index=index-1;
}
else {
if (index==3) return MAX;
RootNode=RootNode+diff2;
//右子樹的新費氏數
diff1=diff1-diff2;
//上一個費氏數
diff2=diff2-diff1;
//上二個費氏數
index=index-2;
}
}
}
data =[5,7,23,37,48,54,68,77,91,99,102,110,118,120,130,136,150];
i=0;
j=0;
while (true) {
const prompt=require('prompt-sync')();
val=prompt('請輸入搜尋值(1-150),輸入-1離開:');
if (val==-1)
break;
RootNode=fib_search(data, val);
if (RootNode==MAX)
console.log('//////////沒有找到',val,'//////////');
else {
console.log('在第 ',RootNode+1,'個位置找到',data[RootNode]);
}
}
console.log('資料內容:');
for (i=0; i<2; i++) {
for (j=0; j<10; j++) {
process.stdout.write((i*10+j+1)+'-'+data[i*10+j]+' ');
}
process.stdout.write('\n');
}

PHP fib_search.php
$num_arr=array();
$num_arr=random_num_arr(1,150,60);
$max=count($num_arr);
$num_arr=sort_select($num_arr);
//print_arr($num_arr);
$val=1; //起始值
if ($_POST['submit']!='送出' ){
echo "
<center>
<form method='post' action='{$_SERVER['[PHP_SELF]']}'>
這是費氏搜尋法(fib_serach)
<br>請輸入欲搜索的數字(1-150)
<input name='number' type='text'>
<input type='submit' name='submit' value='送出''>
</form>
</center>
";
}
else
{
$number=$_POST['number'];
$val=$number;
//echo "<br>輸入數值:{$number}<br>";
while (true) {
$rootnode=fib_search($num_arr,$val);
if ($rootnode==$max){
echo "<br>///////////沒有找到{$val}///////////<br>";
break;
}
else {
echo "<br>在第{$rootnode},個位置找到{$num_arr[$rootnode]}<br>";
break;
}
}
echo "<br>數字陣列為:<br>";
print_arr($num_arr);
}
function fib_search($data, $searchkey) {
$index=2;
$max=count($data);
//費氏數列的搜尋
while (fib($index)<=$max) $index++;
$index--;
$rootnode=fib($index);
$diff1=fib($index-1);
$diff2=$rootnode-$diff1;
$rootnode--;
while (true) {
if ($searchkey==$data[$rootnode])
return $rootnode;
else {
if ($index==2)
return $max; //沒有找到
if ($searchkey<$data[$rootnode]) {
$rootnode=$rootnode-$diff2;
//左子樹的新費氏數
$temp=$diff1;
$diff1=$diff2;
//上一個費氏數
$diff2=$temp-$diff2;
//上二個費氏數
//echo "<br>左子樹<br>";
$index=$index-1;
}
else {
if ($index==3) return $max;
$rootnode=$rootnode+$diff2;
//右子樹的新費氏數
$diff1=$diff1-$diff2;
//上一個費氏數
$diff2=$diff2-$diff1;
//上二個費氏數
//echo "<br>右子樹<br>";
$index=$index-2;
}
}
}
}
function fib($n) {
if ($n==1 || $n==0)
return $n;
else {
return fib($n-1)+fib($n-2);
}
}
function sort_select ($arr) {
//select排序法
$count=count($arr);
for ($i=0; $i<$count-1; $i++) {
$smallest=$arr[$i];
$index=$i;
for ($j=$i+1; $j<$count; $j++) {
//由i+1比較起
if($smallest>$arr[$j]){
//找出最小元素
$smallest=$arr[$j];
$index=$j;
}
}
$tmp=$arr[$i];
$arr[$i]=$arr[$index];
//兩個交換
$arr[$index]=$tmp;
}
return $arr;
}
function random_num_arr($a, $b, $num) {
//$a起始數字、$b結束數字、$num取出數字數量
mt_srand((double)microtime()*1000000);
$data =array();
for ($i=0; $i<$num; $i++) {
$randval=mt_rand(1,150);
if (in_array($randval, $data)){
$i--;
}else{
$data[]=$randval;
}
}
return $data;
}
function print_arr($arr){
$i=0;
foreach ($arr as $key =>$value){
$key=$key+1;
echo $key."[".$value."]";
echo ($i%8==0 && $i!=0) ? "<br>":" ";
$i++;
}
}