一、顺序表的顺序查找
1、顺序查找
由表的一端开始,逐个检测每个记录是不是要查的记录。找到则查找成功,一直找到另一端还没找到则失败。
1)顺序存储结构下的顺序查找
#include <iostream>
using namespace std;
typedef int KeyType;
struct RecType
{
KeyType key;//关键字域
};
//岗哨设在r[0]
int SeqSearch(RecType r[],KeyType k,int n)
{//顺序表为r[1~n]
int i=n;
r[0].key=k;
while(r[i].key!=r[0].key) i--;
return i;
}
//岗哨设在r[n+1]
int SeqSearch1(RecType r[],KeyType k,int n)
{
r[n+1].key=k;
int i=1;
while(r[i].key!=k) i++;
return i%(n+1);
}
int main()
{
RecType r[]={0,1,2,3,4,5,6};
int length=sizeof(r)/sizeof(int);
KeyType key=3,key1=4;
int i=SeqSearch(r,key,length-1);
int j=SeqSearch1(r,key1,length-1);
cout <<i<<' '<<j<< endl;
return 0;
}
2)以链表为存储结构的顺序查找
只能从头节点顺链表查到尾节点,逐个比较。
#include <iostream>
using namespace std;
typedef int KeyType;
struct Node
{
KeyType key;//关键字域
Node *next;//指针
};
Node *LinkSearch(Node *first,KeyType k,int &j)
{
Node *p=first->next;
j=1;
while(p)
{
if(p->key==k)
return p;
else
{
p=p->next;
j++;
}
}
j=0;//没找到则j=0
return NULL;//没找到返回空指针
}
2、时间复杂度分析
执行时间主要取决于关键字的比较次数。
平均查找长度
(Average Search Length,ASL)
需和指定key进行比较的关键字的个数的期望值,成为查找算法在查找成功时的平均查找长度。
对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。
Pi:查找表中第i个数据元素的概率。
Ci:找到第i个数据元素时已经比较过的次数。
顺序查找 查找 成功时的平均查找长度为:
(假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
当查找 不成功时,需要n次比较,时间复杂度为O(n);
对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。
Pi:查找表中第i个数据元素的概率。
Ci:找到第i个数据元素时已经比较过的次数。
顺序查找 查找 成功时的平均查找长度为:
(假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
当查找 不成功时,需要n次比较,时间复杂度为O(n);
二、有序表的折半查找(二分查找)
1、描述
给定有序表按关键字有序,先确定待查记录所在范围,然后逐步缩小范围,直到找到或找不到为止。
#include <iostream>
using namespace std;
typedef int KeyType;
struct RecType
{
KeyType key;//关键字域
};
int BinSearch(RecType r[],int n,int k)
{//r[0~n-1]
int low=0,high=n-1;
while(low<high)
{
int mid=(low+high)/2;
if(r[mid].key==k) return mid;//找到
else if(r[mid].key>k) high=mid-1;//在左半段
else low=mid+1;//在右半段
}
return 0;
}
int main()
{
RecType r[]={1,2,3,4,5,6};
int length=sizeof(r)/sizeof(int);
KeyType key=3;
int i=BinSearch(r,length,key);
cout <<"下标:"<<i<< endl;
return 0;
}
2、性能分析
把当前查找区间的中间位置上的结点作为根,左子表和右子表中的结点分别作为根节点的左子树和右子树。由此得到的二叉树,称为描述二分查找树的判定树。判定树只与表中元素总数有关,与各个元素数值无关。
在判定树中,将所有节点的空左右孩子处加一个方形节点,并用线条连接起来,陈这些方形节点为判定树的外部节点,由n个节点构成的判定树外部节点数目为n+1。二分查找中,查找不成功就是走了一条从根节点到外部节点的路径,比较次数为该路径上内部节点的个数。
例:具有10个元素的有序表的二分查找的判定树为
1 2 3 4 5 6 7 8 9 10
对于此图,我么可以得出:
查找成功的最少次数:1
查找成功最多的次数:4
查找成功的平均次数:ASL=(1*1+2*2+3*4+4*3)/10=2.9=3次;
查找不成功的最少次数:3
查找不成功的最多次数:4
查找不成功的平均次数:ASLunsucc=(3*5+4*6)/(5+6)=39/11=4次;
查找成功的最少次数:1
查找成功最多的次数:4
查找成功的平均次数:ASL=(1*1+2*2+3*4+4*3)/10=2.9=3次;
查找不成功的最少次数:3
查找不成功的最多次数:4
查找不成功的平均次数:ASLunsucc=(3*5+4*6)/(5+6)=39/11=4次;
n个节点的判定树的深度为[lo
g
2
n
]+1,故二分查找在查找不成功时,比较次数最多不超过[log2n]+1。
--->斐波那契查找
斐波那契序列F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n>=2):1,1,2,3,5,8,13,21,..........
方法:在斐波那契数列找一个等于略大于查找表中元素个数的数F[n],将原查找表扩展为长度为F[n](如果要补充元素,则补充重复最后一个元素,直到满足F[n]个元素),完成后进行斐波那契分割,即F[n]个元素分割为前半部分F[n-1]个元素,后半部分F[n-2]个元素,找出要查找的元素在哪一部分并递归,直到找到。
斐波那契查找的时间复杂度还是O(log 2 n ),但是 与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间,因此,斐波那契查找的运行时间理论上比折半查找小,但是还是得视具体情况而定。
折半查找的middle = (low + hight)/2,除法可能会影响效率,而斐波那契的middle = low + F[k-1] -1,纯加减计算,速度要快一些。
对于斐波那契数列:1、1、2、3、5、8、13、21、34、55、89……(也可以从0开始),前后两个数字的比值随着数列的增加,越来越接近黄金比值0.618。比如这里的89,把它想象成整个有序表的元素个数,而89是由前面的两个斐波那契数34和55相加之后的和,也就是说把元素个数为89的有序表分成由前55个数据元素组成的前半段和由后34个数据元素组成的后半段,那么前半段元素个数和整个有序表长度的比值就接近黄金比值0.618,假如要查找的元素在前半段,那么继续按照斐波那契数列来看,55 = 34 + 21,所以继续把前半段分成前34个数据元素的前半段和后21个元素的后半段,继续查找,如此反复,直到查找成功或失败,这样就把斐波那契数列应用到查找算法中了。
有序表序列个数n=F(k)-1,
当有序表的元素个数不是斐波那契数列中的某个数字时,需要把有序表的元素个数长度补齐,让它成为斐波那契数列中的一个数值,当然把原有序表截断肯定是不可能的,不然还怎么查找。
开始将k值与第F(k-1)位置的记录进行比较(及mid=low+F(k-1)-1),比较结果也分为三种
1)相等,mid位置的元素即为所求
2)>,low=mid+1,k-=2;
说明:low=mid+1说明待查找的元素在[mid+1,high]范围内,k-=2 说明范围[mid+1,high]内的元素个数为
n-(F(k-1))= F(k)-1-F(k-1)=F(k)-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找。
3)<,high=mid-1,k-=1。
说明:high=mid-1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1个,所以可以递归 的应用斐波那契查找。
#include <iostream>
#include <vector>
using namespace std;
const int MAX_SIZE = 20;
int a[] = { 1, 5, 15, 22, 25, 31, 39, 42, 47,49,59,68,88};
//a[0~n-1]
void Fibonacci(int F[])
{//斐波那契序列
F[0] = 0;
F[1] = 1;
for (size_t i = 2; i < MAX_SIZE; i++)
F[i] = F[i - 1] + F[i - 2];
}
int FibonacciSearch(int value)
{//斐波那契查找
int F[MAX_SIZE];//斐波那契序列F[0]~F[19]
Fibonacci(F);
int n = sizeof(a) / sizeof(int);//数组a所含元素个数
int k = 0;
while (n > F[k] - 1)//最后找到一个合适的k,使n<=F[k]-1
k++;
vector<int> temp;
temp.assign(a, a + n);//把数组a中元素赋值到当前容器temp中
for (size_t i = n; i < F[k] - 1; i++)
temp.push_back(a[n - 1]);//若n<F[k]-1,扩展数组temp,最后的元素都是a[n-1]
//temp[0~n-1]
int l = 0, r = n - 1;
while (l <= r)
{
int mid = l + F[k - 1] - 1;//mid=low+F[k-1]-1
if (temp[mid] < value){
l = mid + 1;
k = k - 2;
}
else if (temp[mid] > value){
r = mid - 1;
k = k - 1;
}
else{
if (mid < n)
return mid;
else
return n - 1;
}
}
return -1;
}
int main()
{
int index = FibonacciSearch(47);
cout << index << endl;//index为数组下标(0~n-1)
}
--->插值查找
在介绍插值查找之前,首先考虑一个新问题,为什么上述算法一定要是折半,而不是折四分之一或者折更多呢?
打个比方,在英文字典里面查“apple”,你下意识翻开字典是翻前面的书页还是后面的书页呢?如果再让你查“zoo”,你又怎么查?很显然,这里你绝对不会是从中间开始查起,而是有一定目的的往前或往后翻。
同样的,比如要在取值范围1 ~ 10000 之间 100 个元素从小到大均匀分布的数组中查找5, 我们自然会考虑从数组下标较小的开始查找。
经过以上分析,折半查找这种查找方式,不是自适应的(也就是说是傻瓜式的)。二分查找中查找点计算如下:
mid=(low+high)/2, 即mid=low+1/2*(high-low);
通过类比,我们可以将查找的点改进为如下:
mid=low+(key-a[low])/(a[high]-a[low])*(high-low),
也就是将上述的比例参数1/2改进为自适应的,根据关键字在整个有序表中所处的位置,让mid值的变化更靠近关键字key,这样也就间接地减少了比较次数。
基本思想:基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。
注:
对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。
复杂度分析:查找成功或者失败的时间复杂度均为O(log2(log2n))。
#include <iostream>
#include <vector>
using namespace std;
int InsertionSearch(int r[],int n,int k)
{//r[0~n-1]
int low=0,high=n-1;
while(low<high)
{
int p=k-r[low],q=r[high]-r[low];
int mid=low+(p/q)*(high-low);
if(r[mid]==k) return mid;//找到
else if(r[mid]>k) high=mid-1;//在左半段
else low=mid+1;//在右半段
}
return 0;
}
int main()
{
int r[]={1,2,3,4,5,6};
int length=sizeof(r)/sizeof(int);
int key=6;
int i=InsertionSearch(r,length,key);
cout <<"下标:"<<i<< endl;
return 0;
}
三、静态树表的查找
静态树表为的是解决查找概率不等的记录。一般情况下,我们都是默认各个记录等概率查找的,但是有些记录可能不是等概率的。我们可能会首先搜索一些概率大的记录。
例:
次优查找树和最优查找树的查找性能仅差1%-2%,而构造最优查找树花费时间代价较高,构造次优查找树的时间复杂度为O(nlog2n)。现构造一棵二叉树,
使得二叉树的带权内部路
径长度PH值在所有具有同样权值的二叉树中近似最小,称为次优查找树。
PH=w
1
h
1
+w
2
h
2
+.......,n个乘积的和,n为二叉树上节点个数,hi为第i个节点在二叉树上的层数,即深度,wi为节点的权。
次优查找二叉树构造过程:
代码:
#include <iostream>
#include <vector>
#include <iomanip>
#include <cmath>
#include <stdlib.h>
using namespace std;
//树节点结构
typedef struct treenode
{
struct treenode *left;//左孩子
char data;//数据
int weight;//权重
struct treenode *right;//右孩子
}Treenode,* Treep;
int low=1,high=10;//共9个节点
char *R;//字符数组
int *weight;//权重数组
int *sw;//累计权值表sw[i]=w1+w2+...+wi
//初始化二叉树
void init_tree(Treep &root)
{
root=NULL;
cout<<"初始化成功!"<<endl;
}
//创建二叉树
void SecondOptimal(Treep &rt, char R[],int sw[], int low, int high)
{//由有序表R[low....high]及其累积权值表sw(其中sw[0]==0)递归构造次优查找树T
int i=low;
int min = fabs(sw[high] - sw[low]);//ΔP1
int dw = sw[high] + sw[low-1];//dw为sw[high]
for(int j=low+1; j<=high; j++)//选择最小的ΔPi值
{
if(fabs(dw-sw[j]-sw[j-1]) < min)
{
i=j;//i记录要选择的根节点
min=fabs(dw-sw[j]-sw[j-1]);
}
}
cout<<"i="<<i<<' ';
rt=(Treep)malloc(sizeof(Treenode));
rt->data=R[i]; //生成节点
if(i==low) //左子树为空
rt->left = NULL;
else //构造左子树
SecondOptimal(rt->left, R, sw, low, i-1);
if(i==high) //右子树为空
rt->right = NULL;
else //构造右子树
SecondOptimal(rt->right, R, sw, i+1, high);
}//SecondOptimal
//前序遍历二叉树
void pre_order(Treep &rt)
{
if(rt!=NULL)
{
cout<<rt->data<<" ";
pre_order(rt->left);
pre_order(rt->right);
}
}
//查找二叉树中是否存在某元素
int seach_tree(Treep &rt,char key)
{
if(rt==NULL)
return 0;
else
{
if(rt->data==key)
{
return 1;
}
else
{
if(seach_tree(rt->left,key) || seach_tree(rt->right,key))
return 1; //如果左右子树有一个搜索到,就返回1
else
return 0; //如果左右子树都没有搜索到,返回0
}
}
}
int main()
{
Treep root;
init_tree(root); //初始化树
R=(char *)malloc( high*sizeof(char) );
for(int i=low; i<high; i++)
R[i]='A'+i-1;//R[1]~R[9]:A~I
cout<<"构造次优查找树的点R[]:"<<endl;
for(int i=low-1; i<high; i++)
cout<<setw(3)<<R[i]<<" ";//R[0]为空,
cout<<endl;
weight=(int *)malloc( high*sizeof(int) );
weight[0]=0;
weight[1]=1;
weight[2]=1;
weight[3]=2;
weight[4]=5;
weight[5]=3;
weight[6]=4;
weight[7]=4;
weight[8]=3;
weight[9]=5;
cout<<"构造次优查找树的点的权值weight[]:"<<endl;
for(int i=low-1; i<high; i++)
cout<<setw(3)<<weight[i]<<" ";
cout<<endl;
sw=(int *)malloc( high*sizeof(int) );
sw[0]=0;
for(int i=low; i<high; i++)
{
sw[i]=sw[i-1]+weight[i];//算法描述中的si
}
cout<<"构造次优查找树的点累积权值sw[]:"<<endl;
for(int i=low-1; i<high; i++)
cout<<setw(3)<<sw[i]<<" ";
cout<<endl;
//创建二叉树
SecondOptimal(root, R, sw, low, high-1);
//前序遍历二叉树
cout<<endl<<"前序遍历序列是:"<<endl;
pre_order(root);
cout<<endl;
//查找二叉树中是否存在某元素
cout<<"输入要查找的元素!"<<endl;
char ch;
cin>>ch;
if(seach_tree(root,ch)==1)
cout<<"yes!"<<endl;
else
cout<<"no!"<<endl;
return 0;
}
四、分块查找
分块查找又称索引顺序查找,它是顺序查找的一种改进方法。
方法描述:将n个数据元素"按块有序"划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须"按块有序";即第1块中任一元素的关键字都必须 小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素,……。
操作步骤:
step1 先选取各块中的最大关键字构成一个索引表;
step2 查找分两个部分:先对索引表进行二分查找或
顺序查找,以确定待查记录在哪一块中;然后,在已确定的块中用顺序法进行查找。
特点:
(1)比顺序查找快很多吗,但不如二分查找快
(2)也适合于线性链表存储的查找表
(3)即可作为静态查找法,也可作为动态查找法。因在块内无序,若块内没有查找到,则可插入,此前该块内应有空,且索引表应含每一块的上下界。
时间复杂性分析:
时间复杂度:O(log(m)+N/m),N个元素,分为N/M块,每块含M个元素。
代码:
代码:
#include <iostream>
#include <vector>
using namespace std;
typedef struct
{
int r[100];
int length;
}SSTable; //数据表,被查找表
//分块查找——索引查找
typedef struct
{
int key; //关键字域
int stadr;//起始地址
}IndexItem; //索引表中索引项的结构类型
typedef struct
{
IndexItem elem[51];
int length;
}IndexTable;//索引表
int Search_Index(SSTable &ST,IndexTable &ID,int k)
{ //索引查找关键字k
int low,high,mid;
int p=0;//p用来保存查找的关键字所属的索引中的位置
int s,t;//s,t分别用来保存查找的关键字所在块的起始和终点位置
low=0;
int found=0;
high=ID.length-1;
while(low<=high&&found!=1)
{//该循环是用对半查找的方法,对索引表进行查找,从而定位要查找的元素所在的块
mid=(low+high)/2;
if(k>ID.elem[mid-1].key&&k<=ID.elem[mid].key)
{p=mid;found=1;}//判断关键字对应哪个索引位置,就是k比前一个值大,比后一个值小,
//那个大的就是k对应的索引位置
else if(k>ID.elem[mid].key)
low=mid+1;
//else if(k<ID.elem[mid].key)
else
high=mid-1;
}
cout<<"索引表下标p="<<p<<"即在第"<<p+1<<"块中."<<endl;
s=ID.elem[p].stadr;
if(p==ID.length-1)
t=ST.length-1;//这里对p的判断很重要,p若是索引中最后一个位置,则t应该为ST的表长
else
t=ID.elem[p+1].stadr-1; //终止位置为后一块的起始地址减1
while(k!=ST.r[s]&&s<=t)//这里在块里进行顺序查找
s++;
if(s>t)
return -1;
else
return s;
}
//建立需要查找的表,和对半查找用的索引表
void CreateTable(SSTable &ST,IndexTable &ID,int n,int m)
{
int i;
cout<<"请输入待查序列表的长度:";
cin>>ST.length;
cout<<"请输入每一个元素:";
for(i=0;i<n;i++)
cin>>ST.r[i];
cout<<"请输入索引表的长度:";
cin>>ID.length;
cout<<"请输入索引表的元素(数据和起始地址):";
for(i=0;i<m;i++)
cin>>ID.elem[i].key>>ID.elem[i].stadr;
}
int main()
{
SSTable ST;
IndexTable ID;
CreateTable(ST,ID,16,4);
int i=Search_Index(ST,ID,24);
if(i==-1)
cout<<"没找到!"<<endl;
else
cout<<"ST.r["<<i<<"]="<<ST.r[i]<<endl;
return 0;
}