顺序查找--二分查找--静态树表的查找--分块查找
2017-10-13 13:46
513 查看
一、顺序表的顺序查找
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);
二、有序表的折半查找(二分查找)
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次;
n个节点的判定树的深度为[log2n]+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
,将原查找表扩展为长度为F
(如果要补充元素,则补充重复最后一个元素,直到满足F
个元素),完成后进行斐波那契分割,即F
个元素分割为前半部分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=w1h1+w2h2+.......,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; }
相关文章推荐
- 三种静态查找算法:顺序、二分/折半、索引/分块查找
- 算法:静态查找表(Static Search Table)(顺序查找、二分查找、插值查找、斐波纳契查找)
- 查找算法总结之顺序查找、二分查找、静态树查找
- 静态表查找(顺序查找,二分查找,斐波那契查找)
- (五)数据结构之静态查找的简单实现:顺序查找和二分查找
- 查找算法总结之顺序查找、二分查找、静态树查找
- 静态表查找(顺序查找,二分查找,斐波那契查找)
- 数据结构与算法专题之查找与排序——静态查找(顺序、二分、哈希)
- 静态查找表 - 顺序查找、二分查找、插值查找、斐波纳契查找
- 查找算法总结(顺序查找、二分查找、二叉树、平衡二叉树、红黑树、散列表hash)
- Java程序语言 顺序查找、二分查找
- C语言查找算法之顺序查找、二分查找(折半查找)
- 查找算法集:顺序查找、二分查找、插值查找、动态查找(数组实现、链表实现)
- 顺序查找与二分查找的算法实现
- java实现顺序查找、二分查找、哈希表查找、二叉排序树查找
- 静态查找表:顺序查找、折半查找、分块查找
- 算法易错点:二分代码/奇偶调顺序/查找第一次出现的字符
- 顺序表二分查找递归算法
- 查找(顺序、二分、斐波那契和插值)算法的实现和测试
- 查找算法集:顺序查找、二分查找、插值查找、动态查找(数组实现、链表实现)