各种查找算法效率比较
一、需求分析
1.问题描述:
给定一个已经排好序的N个整数的序列(数据从1到N),在该序列中查找指定的整数,并观察不同算法的运行时间。考查3类查找算法:折半查找,平衡二叉排序树的查找,B-树的查找。
2.基本要求:
(1)分析最坏情况下,三种搜索算法的复杂度;
(2)测量并比较三种算法在N=100,500,1000,2000,4000,6000,8000,10000时的性能,要求完成以下三个方面:
对每个测试数据集,统计计算每种查找算法的ASL;
对每个测试数据集运行多次获得运行时间的平均值;
绘制算法实际运行结果(ASL和运行时间)的曲线图,验证和理论分析的时间复杂度的吻合性。
#include<iostream> #include<stdio.h> #include <time.h> #include <malloc.h> using namespace std; #define CLOCKS_PER_SEC ((clock_t)1000) #define MaxSize 10002 #define M 100 int Step; int Bjishu; using namespace std; typedef struct { int key; }DataType; typedef struct { DataType list[MaxSize]; int size; }SeqList; typedef struct node { DataType data; struct node *LeftChild;// struct node *RightChild;// struct node *Parent; int i;//height }BITreeNode,BTnode,*Tree;//二叉平衡树结构体 typedef struct Node { struct Node *parent; /*指向双亲结点*/ int key[M]; /*关键字向量,0号单元未用(M-1阶)*/ struct Node *ptr[M]; /*子树指针向量*/ }B_TNode;//B_树结构体 void ListInitiate(SeqList *L) { L->size=0; } int ListLength(SeqList L) { return L.size; } int ListInsert(SeqList *L,int x) { // int j; if(L->size>=MaxSize) { printf("顺序表已满\n"); return 0; } else { //for(j=L->size;j>i;j--)L->list[j]=L->list[j-1]; L->list[L->size].key=x; L->size++; return 1; } } int BInarySearch(SeqList S,DataType x)//折半查找 { int js=1; //次数记录 int low=0; int high=S.size-1; int mid; while(low<=high) { mid=(low+high)/2; //中间位置 if(S.list[mid].key==x.key)return js; else if(S.list[mid].key<x.key)low=mid+1; else if(S.list[mid].key>x.key)high=mid-1; js++; } return -1; } int Hetree(BTnode *Root) { if(Root==NULL)return 0; return Hetree(Root->LeftChild)>Hetree(Root->RightChild)?Hetree(Root->LeftChild)+1:Hetree(Root->RightChild)+1; } int IsBlance(BTnode *Root)//判断二叉树的平衡) { int bf; if(Root!=NULL) { bf=Hetree(Root->LeftChild)-Hetree(Root->RightChild); if((bf<-1)||(bf>1) ) return 0;//不平衡 else { if(IsBlance(Root->LeftChild)&&IsBlance(Root->RightChild)) return 1; else return 0; } } return 1; } BTnode *R_Rotate(BTnode *Root,BTnode *p)//LL型调平衡(右旋) { BTnode *b, *q, *c, *d; q=p->Parent; b=p->LeftChild; c=b->LeftChild; d=b->RightChild; p->LeftChild=d; if(d!=NULL) d->Parent=p; b->RightChild=p; p->Parent=b; if(q==NULL) { Root=b; b->Parent=NULL; //b的父结点为空,即b就是根结点 } else if(q->LeftChild==p) //如果a是父结点的左孩子 { q->LeftChild=b; //将b赋值给q的左孩子 b->Parent=q; //b的父结点是q } else if(q->RightChild==p) //如果a是父结点的右孩子 { q->RightChild=b; //将b赋值给q的右孩子 b->Parent=q; //b的父结点是q } return Root; } BTnode *L_Rotate(BTnode *Root, BTnode *p)//RR型调平衡 { BTnode *b, *q, *c, *d; q=p->Parent; b=p->RightChild; c=b->RightChild; d=b->LeftChild; p->RightChild=d; if(d!=NULL) d->Parent=p; b->LeftChild=p; p->Parent=b; if(q==NULL) { Root=b; //二叉树的根结点就是b,把b赋值给树Root b->Parent=NULL; //b的父结点为空,即b就是根结点 } else if(q->LeftChild==p) //如果p是父结点的左孩子 { q->LeftChild=b; //将b赋值给q的左孩子 b->Parent=q; //b的父结点是q } else if(q->RightChild==p) //如果p是父结点的右孩子 { q->RightChild=b; //将b赋值给q的右孩子 b->Parent=q; //b的父结点是q } return Root; } BTnode *LR_Rotate(BTnode *Root,BTnode *p) { BTnode *b, *q, *c, *d; q=p->Parent; b=p->LeftChild; c=b->LeftChild; d=b->RightChild; p->LeftChild=d; if(d!=NULL) d->Parent=p; b->RightChild=p; p->Parent=b; if(q==NULL) { Root=b; b->Parent=NULL; //b的父结点为空,即b就是根结点 } else if(q->LeftChild==p) //如果a是父结点的右孩子 { q->LeftChild=b; //将b赋值给q的左孩子 b->Parent=q; //b的父结点是q } else if(q->RightChild==p) //如果a是父结点的左孩子 { q->RightChild=b; //将b赋值给q的右孩子 b->Parent=q; //b的父结点是q } return Root; } BTnode *RL_Rotate(BTnode *Root,BTnode *p) { BTnode *b, *q, *c, *d; q=p->Parent; b=p->RightChild; c=b->RightChild; d=b->LeftChild; p->RightChild=d; if(d!=NULL) d->Parent=p; b->LeftChild=p; p->Parent=b; if(q==NULL) { Root=b; //二叉树的根结点就是b,把b赋值给树Root b->Parent=NULL; //b的父结点为空,即b就是根结点 } else if(q->LeftChild==p) //如果p是父结点的右孩子 { q->LeftChild=b; //将b赋值给q的左孩子 b->Parent=q; //b的父结点是q } else if(q->RightChild==p) //如果p是父结点的左孩子 { q->RightChild=b; //将b赋值给q的右孩子 b->Parent=q; //b的父结点是q } return Root; } int blancebinarytreesearch(BTnode *Root,int x)//查找平衡二叉排序树 { BTnode *p; int count=0; if(Root!=NULL) { p=Root; while(p!=NULL) { count++; if(p->i==x)return count; else if(x<p->i)p=p->LeftChild; else if(x>p->i)p=p->RightChild; } } return 0; } int InPEtree(BTnode **Root,int x)//创建平衡二叉排序树的函数 { BTnode *cur,*parent=NULL,*p,*q; cur=*Root; while(cur!=NULL) { if(x==cur->i)return 0; parent=cur; if(x<cur->i)cur=cur->LeftChild; else if(x>cur->i)cur=cur->RightChild; } p=(BTnode *)malloc(sizeof(BTnode)); p->i=x; p->LeftChild=NULL; p->RightChild=NULL; p->Parent=NULL; if(parent==NULL)*Root=p; else if(x<parent->i) { parent->LeftChild=p; p->Parent=parent; } else if(x>parent->i) { parent->RightChild=p; p->Parent=parent; } int bf; if(IsBlance(*Root)==0) //如果二叉树是不平衡的 { bf=Hetree(parent->LeftChild)-Hetree(parent->RightChild); while((bf>=-1)&&(bf<=1)) { parent=parent->Parent; bf=Hetree(parent->LeftChild)-Hetree(parent->RightChild); } q=parent;///找到离插入点最近的不平衡点 if(p->i>q->i&&p->i>q->RightChild->i)//新结点插入在q结点的右孩子的右子树中。 { *Root=L_Rotate(*Root,q); } else if(p->i>q->i&&p->i<q->RightChild->i)//新结点插入在A结点的右孩子的左子树中 { *Root=RL_Rotate(*Root,q); } else if(p->i<q->i&&p->i>q->LeftChild->i)//新结点插入在q结点的左孩子的右子树中 { *Root=LR_Rotate(*Root,q); } else //结点插入在A结点的左孩子的左子树中 { *Root=R_Rotate(*Root,q); } } return 1; } void PrintBiTree(BTnode *root,int n) { int i; if(root==NULL)return ; PrintBiTree(root->RightChild,n+1); for(i=0;i<n-1;i++)printf(" "); if(n>0) { printf("-----"); printf("%d\n",root->i); } if(n==0) { //printf("--"); printf("%d\n",root->i); } PrintBiTree(root->LeftChild,n+1); } int B_treetreesearch(B_TNode *Root,int x)//查找B-树 { int i; if(Root==NULL)return 0; for(i=1;i<=Root->key[0];i++) { Bjishu++; if(x<Root->key[i])break; if(x==Root->key[i])return Bjishu; } return B_treetreesearch(Root->ptr[i-1],x); } B_TNode* B_treetreeinsert(B_TNode *Root,int x)//3.B-树的插入函数 { int i; if(Root==NULL)//B_树为空 { Root=(B_TNode *)malloc(sizeof(B_TNode)); Root->key[0]=1; Root->key[1]=x; for(i=0;i<M;i++) Root->ptr[i]=NULL; Root->parent=NULL; return Root; } B_TNode *p=Root,*q,*s; q=p; while(p!=NULL) { for(i=1;i<=p->key[0];i++) if(x<p->key[i])break; q=p; p=p->ptr[i-1]; } int j; q->key[i]=x; for(j=q->key[0];j>=i;j--) { q->key[j+1]=q->key[j]; q->ptr[j+1]=q->ptr[j]; } q->key[0]++; int temp; i=q->key[0]; int m,num=0; while(q->key[0]==Step+1-1) { num++; temp=q->key[(i-1)/2+1]; p=q->parent; q->key[0]=(i-1)/2;//分裂左树 m=(i-1)/2+1; //加到双亲结点 if(p==NULL) { p=(B_TNode *)malloc(sizeof(B_TNode)); for(i=0;i<M;i++) p->ptr[i]=NULL; Root=p; Root->parent=NULL; p->key[0]=1; p->key[1]=temp; p->ptr[0]=q; p->parent=NULL; } else { for(i=1;i<=p->key[0];i++) if(temp<p->key[i])break; for(j=p->key[0];j>=i;j--) { p->key[j+1]=p->key[j]; p->ptr[j+1]=p->ptr[j]; } p->key[i]=temp;// p->ptr[i-1]=q;// p->key[0]++; } //分配右树 s=(B_TNode *)malloc(sizeof(B_TNode)); for(i=0;i<M;i++) s->ptr[i]=NULL; p->ptr[p->key[0]]=s; p->ptr[p->key[0]-1]=q; s->key[0]=Step+1-1-m; s->parent=p; q->parent=p; for(i=1;i<=s->key[0];i++) { s->key[i]=q->key[i+m]; s->ptr[i-1]=q->ptr[i+m-1]; //////////////// } if(num>1)s->ptr[i-1]=q->ptr[i+m-1]; for(i=0;i<=s->key[0];i++) { if(s->ptr[i]!=NULL)s->ptr[i]->parent=s;//////////////// } q=p; i=q->key[0]; } return Root; } int B_treetreeprint(B_TNode *Root,int n) { int i,j; if(Root==NULL)return 0; for(i=Root->key[0];i>0;i--) { B_treetreeprint(Root->ptr[i],n+1); for(j=0;j<n;j++) printf("----"); cout<<Root->key[i]<<endl; } B_treetreeprint(Root->ptr[0],n+1); } /* int B_treetreeprint(B_TNode *Root,int n) { int i; if(Root==NULL)return 0; if(Root->key[0]>=2) { B_treetreeprint(Root->ptr[2],n+1); for(i=0;i<n;i++) printf("--"); printf("%d ",Root->key[2]); printf("\n"); } if(Root->key[0]>=1) { B_treetreeprint(Root->ptr[1],n+1); for(i=0;i<n;i++) printf("--"); printf("%d ",Root->key[1]); printf("\n"); //printf("\n"); } if(Root->ptr[0]!=NULL) { B_treetreeprint(Root->ptr[0],n+1); // for(i=0;i<n;i++) // printf("---"); } } int B_treetreeprint(B_TNode *Root,int n) { int i; if(Root==NULL)return 0; if(Root->key[0]>=2) { B_treetreeprint(Root->ptr[2],n+1); for(i=0;i<n;i++) printf("--"); printf("%d ",Root->key[2]); printf("\n"); } if(Root->key[0]>=1) { B_treetreeprint(Root->ptr[1],n+1); for(i=0;i<n;i++) printf("--"); printf("%d ",Root->key[1]); printf("\n"); //printf("\n"); } if(Root->ptr[0]!=NULL) { B_treetreeprint(Root->ptr[0],n+1); // for(i=0;i<n;i++) // printf("---"); } } */ int main() { //system( "graftabl 936 "); int i; int s,j,k,k1,k2,k3,Size,Sizex; //int N[8]={100,500,1000,2000,4000,6000,8000,10000}; int x; SeqList L; DataType Me; double runtime; double Start,End; printf("请输入序列中数的个数Size:"); while(scanf("%d",&Size)!=EOF&&Size!=0) { ListInitiate(&L); //初始化线性表 Tree t = NULL; //初始化二叉平衡树 Tree zt=NULL; //初始化折半查找树 B_TNode *Root=NULL; s=0; printf("请输入B_树的阶:"); scanf("%d",&Step); for( i = 0;i<Size;i++) { InPEtree(&t,i+1); //Insert (&t,i+1);创建二叉平衡树 ListInsert(&L,i+1);//创建线性表 Root=B_treetreeinsert(Root,i+1);//创建B_树 } printf("平衡二叉树\n"); PrintBiTree(t,0);//打印二叉平衡树PrintfTree(t,1); printf("\nB_树\n"); B_treetreeprint(Root,0); //printf("请输入需要查找的数(1~Size):"); Sizex=Size; while(Sizex>=1) { k1=0; k2=0; k3=0; //记次数 k1=blancebinarytreesearch(t,Sizex); printf("平衡二叉树查询%d用了%d次\n",Sizex,k1); Me.key=Sizex; k2=BInarySearch(L,Me); printf("折半查找法查询%d用了%d次\n",Sizex,k2); Bjishu=0; printf("B_树查找%d用了%d次\n",Sizex,B_treetreesearch(Root,Sizex)); //printf("请输入需要查找的数(1~Size):"); Sizex--; } k=0; Start=clock();//开始计时 for( i = 0;i<Size;i++) { k+=blancebinarytreesearch(t,i+1);//SearchBST(t,i+1,s); } End=clock();//结束计时】 runtime=(double)(End-Start)/ (CLOCKS_PER_SEC*Size); printf("%d个数的数组 平衡二叉排序树ASL= %.2f \t平均时间:%lf ms\n",Size,(float)k/Size,runtime); k=0; Start=clock(); for( i = 0;i<Size;i++) { Me.key=i+1; k+=BInarySearch(L,Me); } End=clock(); runtime=(double)(End-Start)/(CLOCKS_PER_SEC*Size); printf("\t\t 折半查找法ASL= %.2f \t 平均时间:%lf ms\n",(float)k/Size,runtime); Bjishu=0; Start=clock(); for( i = 0;i<Size;i++) { B_treetreesearch(Root,i+1); } End=clock(); runtime=(double)(End-Start)/(CLOCKS_PER_SEC*Size); printf("\t\tB_树查找ASL= %.2f \t 平均时间:%lf ms\n",(float)Bjishu/Size,runtime); printf("\n请输入序列中数的个数Size:"); } return 0; }
9.各种查找算法效率比较
一、需求分析
1.问题描述:
给定一个已经排好序的N个整数的序列(数据从1到N),在该序列中查找指定的整数,并观察不同算法的运行时间。考查3类查找算法:折半查找,平衡二叉排序树的查找,B-树的查找。
2.基本要求:
(1)分析最坏情况下,三种搜索算法的复杂度;
(2)测量并比较三种算法在N=100,500,1000,2000,4000,6000,8000,10000时的性能,要求完成以下三个方面:
对每个测试数据集,统计计算每种查找算法的ASL;
对每个测试数据集运行多次获得运行时间的平均值;
绘制算法实际运行结果(ASL和运行时间)的曲线图,验证和理论分析的时间复杂度的吻合性。
二、设计
1. 设计思想
(1)存储结构
以线性表折半查找序列,用顺序结构实现。
采用二叉链存储平衡二叉排序树。
采用多叉链存储B-树。
(2)主要算法基本思想
先初始化折半查找的线性表,二叉树和B-树,再将已经排好序的N个整数的序列(数据从1到N)依次插入到平衡查找的线性表中,边插入二叉树中边调平衡,按B-树的特点插入到B-树中,分别在这三种结构中查找指定的元素,输出查找的次数,并输出查找所有元素的平均次数(打印出二叉树和B-树用于验证查找次数)。
|
2. 设计表示
(1)函数调用关系图
(2)函数接口规格说明
void ListInitiate(SeqList *L)//初始化线性表L
int ListLength(SeqList L) //线性表L的长度
int ListInsert(SeqList *L,int x)//将x插入线性表L
int BInarySearch(SeqList S,DataType x)//在线性表L中折半查找元素x
int Hetree(BTnode *Root) //求二叉树Root的平衡因子
int IsBlance(BTnode *Root) //判断二叉树Root是否平衡)
BTnode *R_Rotate(BTnode *Root,BTnode *p)//LL型调平衡(右旋)
BTnode *L_Rotate(BTnode *Root, BTnode *p)//RR型调平衡
BTnode *LR_Rotate(BTnode *Root,BTnode *p)//LR型调平衡
BTnode *RL_Rotate(BTnode *Root,BTnode *p)//RL型调平衡
int blancebinarytreesearch(BTnode *Root,int x)//查找平衡二叉排序树Root
int InPEtree(BTnode **Root,int x)//在平衡二叉排序树Root中插入x
void PrintBiTree(BTnode *root,int n)//从第n层开始打印平衡二叉树Root
int B_treetreesearch(B_TNode *Root,int x)//在B-树Root中查找x
B_TNode* B_treetreeinsert(B_TNode *Root,int x)//在B-树Root中插入x
int B_treetreeprint(B_TNode *Root,int n)// 从第n层开始打印B-树Root
3. 详细设计(主要函数)
【1】InPEtree
【2】B_treetreeinsert
从空树起逐个插入关键字便可生成B-树。
将关键字k插入到B-树中的过程分两步完成:
(1)利用B-树的查找算法找出该关键字的插入结点(注意B-树的插入结点一定是叶子结点)。
(2)若该结点关键字个数n<m-1,说明该结点还有空位置,直接把关键字k插入到该结点的合适位置上;若该结点关键字个数n=m-1,说明该结点已没有空位置,插入后需按以下方法把该结点分裂成两个结点:
分配一新结点,把需分裂结点上的关键字从中间位置(即m/2处)分成两部分,左部分所含关键字放在旧结点中,右部分所含关键字放在新结点中,中间位置的关键字连同新结点的存储位置插入到父亲结点中。如果父结点的关键字个数也超过M-1,则要再分裂,再往上插,直至这个过程传到根结点为止。
【3】B_treetreesearch
在B-树中查找关键字k的算法为:
将k与根结点中的key[i](1≤i≤n)进行比较:
(1)若k=key[i],则查找成功;
(2)若k<key[1],则沿着指针ptr[0]所指的子树继续查找;
(3)若key[i]<k<key[i+1],则沿着指针ptr[i]所指的子树继续查找;
(4)若k>key[n],则沿着指针ptr[n]所指的子树继续查找;
三、调试分析
1.调试过程中遇到的主要问题是如何解决的;
该题有三大难点,一是 二叉树的调平
二是 B-树的建立
三是 B-树的打印
在做本题之前温习了老师上课时的PPT课件,对三种算法有进一步的认识和理解,能自主独立的写折半查找算法和二叉树,对写二叉树的调平过程中出现多次错误,最后参考了老师上课的课件代码终于解决,B-树的建立过程很熟悉,代码很快就能写好,只是出现了几处指针的引用不当,看了多遍才最终Debug,花的时间也不少,经过老师的第二次检查,老师提出要打印出两种树用来验证查找次数的问题,递归的思路实现平衡二叉树的打印比较简单,只是对B-树的递归打印,在B-树的阶又不确定的前提下,修改了几次才成功。
2.对设计和编码的回顾讨论和分析;
多动手编程,才能熟练灵活的掌握C语言基础知识,才能更好的理解掌握数据结构的精髓。从而避免基础语法错误,让代码变得更简洁高效。如此才能准确高效的解决问题。在今后的编程过程中要更注重代码整体设计的提纲记录,用更多的注释。
3.时间和空间复杂度的分析;
【1】:折半查找法 时间O(lbn)
【2】:平衡二叉树查找 时间O(lbn),
4.改进设想;
程序能实现预期功能,有改进空间
【1】:更简洁简练的平衡二叉树的建立算法
5.经验和体会等。
多参加有益的比赛,科研,动手编程,才能熟练灵活的掌握C语言基础知识,才能更好的理解掌握数据结构的精髓。从而快熟的敲好代码,不能让整个工程由于一个小小的错误而修改半天。
四、用户手册(即使用说明)
仅需按照提示的输入即可。若出错,则重新来过。
五、运行结果
运行环境:codeblocks
测试用表:10 3
100 3
运行后的界面
测试数据:10 3
打印出平衡二叉树和B-树
三种算法中所有元素的查找次数及SAL
测试数据:100 3
三种算法的SAL值
六、源程序清单
所有函数均在一个 算法效率12.cpp 文件中