数据结构之常见查找算法

xiaoxiao2021-02-28  54

#include "stdafx.h" #include <memory> /************************************************************************/ /* 一、顺序表查找 平均时间复杂度O(N) (a为要查找的数组,n为数组元素个数,key为要查找的关键字)*/ /************************************************************************/ //1.1普通查找 int Sequential_Search(int *a, int n, int key) { for (int i=1;i<=n;i++) { if (a[i]==key) return i; } return 0; } //1.2优化(设置哨兵) 减少了每次循环的越界判断 int Sequential_Search1(int *a, int n, int key) { a[0]=key;//设置a[0]为哨兵 int i=n; while (a[i]!=key) { i--; } return i;//返回0说明没有找到 } /************************************************************************/ /* 二、有序表查找 (a为要查找的数组,n为数组元素个数,key为要查找的关键字) */ /************************************************************************/ //2.1二分查找 平均时间复杂度 O(logn) int Binary_Search(int *a, int n, int key) { int low,mid,high; low=1;//定义最低下标为记录首位 high=n;//定义最高下标为记录末尾 while (low<=high) { mid=(low+high)/2;//折半 if (a[mid]<key)//若查找值比中值大 low=mid+1;//最低下标调整到中位大一位 else if (a[mid]>key)//若查找值比中值小 high=mid-1;//最高下标调整到中位小一位 else return mid;//若相等则说明mid即为查找到的位置 } return 0;//没有找到 } //2.2插值查找 平均时间复杂度 O(logn) mid = low+(high-low)*(key-a[low])/(a[high]-a[low]) int Interpolation(int *a, int n, int key) { int low,mid,high; low=1; high=n; while(low<=high) { mid=low+(high-low)*(key-a[low])/(a[high]-a[low]);//插值 if (a[mid]<key) low=mid+1; else if(a[mid]>key) high=mid-1; else return mid; } return 0; } //2.3斐波那切查找 平均时间复杂度 O(logn) low----F[k-1]-1----mid----F[k-2]-1----high 黄金分割 int F[]={0,1,1,2,3,5,8,13,21,34,55}; int Feibonacci(int *a, int n, int key) { int low,mid,high,k; low=1; high=n; k=0; while (n>F[k]-1)//计算n位于斐波那契数列的位置 { k++; } for (int i=n;i<F[k]-1;i++)//将不满的数值补全 { a[i]=a[n]; } while(low<=high) { mid = low + F[k-1]-1;//计算当前分割的下标 if (a[mid]>key)//若查找记录小于当前分割记录 { high=mid-1;//最高下标调整到分割下标mid-1处 k=k-1;//斐波那契数列下标减一位 } else if (a[mid]<key)//若查找记录大于当前分割记录 { low=mid+1;//最低下标调整到分割下标mid+1处 k=k-2;//斐波那契数列下标减两位 } else { if (mid<=n) { return mid;//若相等则说明mid即为查找到的位置 } else return n;//若mid大于n,这说明是补全数值,返回n } } return 0;//没有找到 } /************************************************************************/ /* 三、线性索引查找 */ /************************************************************************/ //3.1稠密索引 O(logN) //索引项与数据集的记录个数相同,空间代价大 //索引项按关键码排序,由关键码可以进行折半等查找 //3.2分块索引 O(N^1/2) //块间有序,块内无序 //分块索引表的每一项内容包括最大关键码、块长、块首指针 //由最大关键码可以进行折半查找定位数据块,紧接着在块内进行顺序查找(已知块首地址和块长进行顺序遍历) //3.3倒排索引(搜索引擎:根据单词排序,次关键码为单词,记录号表为包含该单词的记录地址) //由属性值来确定记录位置 //索引项包括次关键码和记录号表 /************************************************************************/ /* 四、二叉排序树(二叉查找树) 经过中序遍历之后即可得到有序序列 */ /************************************************************************/ //二叉树的二叉链表结点结构定义 typedef struct BitNode//结点结构 { int data;//结点数据 int bf;//结点的平衡因子(左减右)(为平衡二叉树做准备) struct BitNode *lchild,*rchild;//左右孩子指针 }BitNode,*BitTree; //4.1二叉排序树查找操作 //递归查找二叉排序树中是否存在关键字key //指针f指向T的双亲,其初始调用值为NULL //若查找成功,则指针p指向该数据元素结点,并返回TRUE //否则指针p指向查找路径上最后访问的一个结点并返回FALSE bool SearchBST(BitTree T, int key, BitTree f, BitTree *p) { if (!T)//查找不成功 { *p=f; return false; } else if (T->data==key)//查找成功 { *p=T; return true; } else if (T->data>key) return SearchBST(T->lchild,key,T,p);//左子树继续查找 else return SearchBST(T->rchild,key,T,p);//右子树继续查找 } //4.2二叉排序树插入操作 //当二叉排序树T当中不存在key时,插入key并返回true,否则返回false bool InsertBST(BitTree *T,int key) { BitTree p,s; if (!SearchBST(*T,key,NULL,&p))//查找不成功 { s=(BitTree)malloc(sizeof(BitNode)); s->data=key; s->lchild=s->rchild=NULL; if (!p) *T = s;//插入s为新的根结点 else if (key>p->data) p->rchild=s;//插入s为右孩子 else p->lchild=s;//插入s为左孩子 return true; } else return false;//树中已有关键字,不再插入 } //4.3二叉排序树创建 bool CreateBST() { int i; int a[10]={62,88,23,45,67,54,77,88,33,55}; BitTree T=NULL; for (int i=0;i<10;i++) { InsertBST(&T, a[i]); } return true; } //4.4二叉排序树删除操作 bool Delete(BitTree *p) { BitTree q,s; if ((*p)->lchild==NULL)//如果要删除的结点的左子树为空 { q=*p;*p=(*p)->rchild;free(q);//重接它的右子树 } else if ((*p)->rchild==NULL)//如果要删除的结点的右子树为空 { q=*p;*p=(*p)->lchild;free(q);//重接它的左子树 } else//如果两边都不为空 { q=*p;s=(*p)->lchild;//转左 while(s->rchild) { q=s;s=s->rchild;//然后向右走到尽头(即找到要删除结点的前驱结点) } (*p)->data = s->data;//s指向被删结点的直接前驱,将要删除结点的关键字改为前驱结点的关键字 if (q!=*p) q->rchild=s->lchild;//重接q的右子树 else q->lchild = s->lchild;//重接q的左子树 free(s);//删除前驱结点 因为前驱结点的值已经转移到被删除结点位置 } return true; } //若二叉排序树当中存在关键字等于key的数据元素时,则删除该数据元素结点并返回true,否则返回false bool DeleteBST(BitTree *T, int key) { if (!*T)//不存在关键字为key的数据元素 return false; else { if ((*T)->data==key)//找到关键字等于key的数据元素 return Delete(T); else if ((*T)->data>key) return DeleteBST(&(*T)->lchild,key); else return DeleteBST(&(*T)->rchild,key); } } /************************************************************************/ /* 五、平衡二叉树(AVL树) 是一种二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1 */ // 查找O(logN) 插入删除O(logN) /************************************************************************/ //5.1 右旋 //对以p为根的二叉排序树作右旋处理,处理之后p指向新的树根结点,即旋转之前的左子树的根结点 void R_Rotate(BitTree *p) { BitTree L; L = (*p)->lchild;//L指向p的左子树根结点 (*p)->lchild = L->rchild;//L的右子树挂接为p的左子树 L->rchild = *p;//p挂接为L的右子树 *p = L;//p指向新的根结点 } //5.2左旋 //对以p为根的二叉排序树作左旋处理,处理之后p指向新的树根结点,即旋转之前的右子树的根结点 void L_Rotate(BitTree *p) { BitTree R; R = (*p)->rchild;//R指向p的右子树根结点 (*p)->rchild = R->lchild;//R的左子树挂接为p的右子树 R->lchild = *p;//p挂接为R的左子树 *p = R;//p指向新的根结点 } //5.3左平衡旋转处理 左平衡处理,就是某一根结点的左子树比右子树过高,从而失去了平衡 #define LH 1 //左高 #define EH 0 //等高 #define RH -1 //右高 //对以指针T所指结点为根的二叉树做平衡树旋转处理 //本算法结束时,指针T指向新的根结点 void LeftBalance(BitTree *T) { BitTree L,Lr; L = (*T)->lchild;//L指向T的左子树根结点 switch(L->bf)//检查T的左子树的平衡度,并作相应的平衡处理 { case LH://新加的结点在T的左孩子的左子树上,做单右旋处理 (*T)->bf=L->bf=EH;//旋转之后新的根结点L及T结点的平衡度都为EH R_Rotate(T); break; case EH://deleteAVL需要,insertAVL用不着 (删除根结点右子树中的结点从而引起左子树过高,在删除前,根结点左子树根的平衡因子是可以为EH的,此种情况同样是对根结点做单右旋处理) (*T)->bf = LH; L->bf = RH; R_Rotate(T); break; case RH://新加的结点在T的左孩子的右子树上,此时L的平衡度与T的符号相反,要做双旋转处理 Lr = L->rchild;//Lr指向T的左孩子的右子树结点 switch(Lr->bf)//根据Lr的平衡度修改T及L的平衡度 { case LH: (*T)->bf=RH; L->bf=EH; break; case EH: (*T)->bf=L->bf=EH; break; case RH: (*T)->bf=EH; L->bf = LH; break; } Lr->bf=EH; L_Rotate(&(*T)->lchild);//对T的左子树L做左旋处理 R_Rotate(T);//对T做右旋处理 } } //5.4右平衡旋转处理 右平衡处理,就是某一根结点的右子树比左子树过高,从而失去了平衡 void RightBalance(BitTree *T) { BitTree R,Rl; R = (*T)->rchild; switch (R->bf) { case RH: (*T)->bf=R->bf=EH; L_Rotate(T); break; case EH: //deleteAVL需要,insertAVL用不着 (删除根结点左子树中的结点从而引起右子树过高,在删除前,根结点右子树根的平衡因子是可以为EH的,此种情况同样是对根结点做简单左旋处理) (*T)->bf = RH; R->bf = LH; L_Rotate(T); break; case LH: Rl=R->lchild; switch (Rl->bf) { case RH: (*T)->bf=LH; R->bf=EH; break; case EH: (*T)->bf=R->bf=EH; break; case LH: (*T)->bf=EH; R->bf=RH; break; } Rl->bf=EH; R_Rotate(&(*T)->rchild); L_Rotate(T); } } //5.5二叉平衡树插入操作 //若在平衡的二叉树T中不存在与e相等的结点,则插入一个数据元素为e的结点,并返回1,否则返回0; //若插入的新结点使二叉平衡树失去平衡,则做平衡旋转处理,布尔变量taller反映T长高与否。 bool InsertAVL(BitTree *T, int e, bool *taller) { if (!*T)//如果没有找到,则插入新结点 { *T=(BitTree)malloc(sizeof(BitNode)); (*T)->data=e; (*T)->lchild=(*T)->rchild=NULL; (*T)->bf=EH; *taller=true; } else { if ((*T)->data=e)//在原来的二叉平衡树中找到,则返回false { *taller=false; return false; } else if ((*T)->data>e)//若当前结点关键字大于e,则继续在左子树寻找 { if (!InsertAVL(&(*T)->lchild,e,taller))//若在左子树中找到,则返回false return false; if (*taller)//执行到这里说明新插入了结点,并且新插入结点使树长高 { switch((*T)->bf)//查看T的本来的平衡度 { case LH://若本来是左高,新加入的结点在左子树,则需要做左平衡处理,不增高 LeftBalance(T); *taller=false; break; case EH://若本来是等高,新插入的结点在左子树,导致T左高,增高 (*T)->bf=LH; *taller=true; break; case RH://若本来是右高,新加入的结点在左子树,导致T等高,不增高 (*T)->bf=EH; *taller=false; break; } } } else//若当前结点关键字小于e,则继续在右子树寻找 { if (!InsertAVL(&(*T)->rchild,e,taller))//若在右子树中找到,则返回false return false; if (*taller)//执行到这里说明新插入了结点,并且新插入结点使树长高 { switch((*T)->bf)//查看T的本来的平衡度 { case LH://本来是左高,新插入的结点在右子树,使T等高,不增高 (*T)->bf=EH; *taller=false; break; case EH://本来是等高,新插入的结点在右子树,使T右高,增高 (*T)->bf=RH; *taller=true; break; case RH://本来是右高,新插入的结点在右子树,需要右平衡处理,不增高 RightBalance(T); *taller=false; break; } } } } return true; } //5.6创建二叉平衡树 bool CreateAVL() { int a[10]={3,2,1,4,5,6,7,10,9,8}; BitTree T=NULL; bool taller; for (int i=0;i<10;i++) { InsertAVL(&T,a[i],&taller); } return true; } //5.7二叉平衡树的删除 //若在平衡的二叉排序树T中存在和e有相同关键字的结点,则删除并返回true,否则返回false //若因删除而使二叉排序树失去平衡,则作平衡旋转处理,布尔变量lower反映T变矮与否 bool DeleteAVL(BitTree *T, int e, bool *lower) { if (!*T)//如果没有找到,则返回false { *lower=false; return false; } else { if ((*T)->data=e)//在原来的二叉平衡树中找到,则删除 { BitTree q,s; if ((*T)->lchild==NULL)//如果要删除的结点的左子树为空 { q=*T;*T=(*T)->rchild;free(q);//重接它的右子树 *lower=true; } else if ((*T)->rchild==NULL)//如果要删除的结点的右子树为空 { q=*T;*T=(*T)->lchild;free(q);//重接它的左子树 *lower=true; } else//如果两边都不为空 { q=*T;s=(*T)->lchild;//转左 while(s->rchild) { q=s;s=s->rchild;//然后向右走到尽头(即找到要删除结点的前驱结点) } (*T)->data = s->data;//s指向被删结点的直接前驱,将要删除结点的关键字改为前驱结点的关键字 DeleteAVL(&(*T)->lchild,s->data,lower);//在左子树中递归删除前驱结点 } } else if ((*T)->data>e)//若当前结点关键字大于e,则继续在左子树寻找 { if (!DeleteAVL(&(*T)->lchild,e,lower))//若在左子树中没有找到,则返回false return false; if (*lower)//执行到这里说明删除了结点,并且删除结点使树变矮 { switch((*T)->bf)//查看T的本来的平衡度 { case LH://若本来是左高,删除的结点在左子树,导致T等高,变矮 (*T)->bf=EH; *lower=true; break; case EH://若本来是等高,删除的结点在左子树,导致T右高,不变矮 (*T)->bf=RH; *lower=false; break; case RH://若本来是右高,删除的结点在左子树,需要右平衡处理 if((*T)->rchild->bf == EH)//注意这里,画图思考一下 *lower = false; else *lower = true; RightBalance(T); break; } } } else//若当前结点关键字小于e,则继续在右子树寻找 { if (!DeleteAVL(&(*T)->rchild,e,lower))//若在右子树中没有找到,则返回false return false; if (*lower)//执行到这里说明删除了结点,并且删除结点使树变矮 { switch((*T)->bf)//查看T的本来的平衡度 { case LH://本来是左高,删除的结点在右子树,需要左平衡处理 if ((*T)->lchild->bf==EH) *lower=false; else *lower=true; LeftBalance(T); break; case EH://本来是等高,删除的结点在右子树,使T左高,不变矮 (*T)->bf=LH; *lower=false; break; case RH://本来是右高,删除的结点在右子树,使T等高,变矮 (*T)->bf=EH; *lower=true; break; } } } } return true; } //5.8红黑树(RBT) //AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多; //红黑是弱平衡的,用非严格的平衡来换取增删节点时候旋转次数的降低; //所以简单说,搜索的次数远远大于插入和删除,那么选择AVL树,如果搜索,插入删除次数几乎差不多,应该选择RB树。 /************************************************************************/ /* 六、多路查找树(B树) 每一个结点的孩子数可以多于两个,每个结点的可以存储多个元素 */ /************************************************************************/ //6.1 2-3树 //其中每一个结点都具有两个孩子(2结点)或三个孩子(3结点),所有的叶子都在同一层次上 //2结点包含一个元素和两个孩子(或没有孩子) //3结点包含两个元素和三个孩子(或没有孩子) //若2-3树的插入的传播效应导致的根结点拆分,则树的高度就会增加 //6.2 2-3-4树 //在2-3树的基础上增加4结点 //4结点包含三个元素和四个孩子(或没有孩子) //6.3 B树 适合内外存交互 //B树是一种平衡的多路查找树,2-3树,2-3-4树是B树的特例 //结点最大的孩子数目叫做B树的阶 //一个m阶的B树具有如下的属性: //1.如果根结点不是叶子结点,则其至少有两颗子树 //2.每一个非根的分支结点都有k-1个元素和k个子树(不小于m/2的最小整数<=k<=m) //3.每一个叶子结点都有k-1个元素(不小于m/2的最小整数<=k<=m) //4.所有的叶子结点都位于同一层次 //6.4 B+树 //适合带范围的查找 //在B+树中,出现在分支结点的的元素会被当做它们在该分支结点位置的中序后继者(叶子结点)中再次列出,每个 //叶子结点都会保存一个指向后一个叶子结点的指针 //6.5 B*树 //是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针 //B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2) //B*树分配新结点的概率比B+树要低,空间使用率更高 //6.6小结 //B树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点 // 所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中 //B+树:在B树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引 // B+树总是到叶子结点才命中; //B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3 /************************************************************************/ /* 七.哈希表(散列表)查找 */ /************************************************************************/ #define HASHSIZE 12 #define NULLKEY -32768 int m1 = 0;//哈希表表长 //定义数据类型 typedef struct { int *elem;//数据元素存储基地址,动态分配数组 int count;//当前数据元素 }HashTable; //哈希表初始化 int InitHashTable(HashTable *H) { int i; m1=HASHSIZE; H->count=m1; H->elem=(int *)malloc(m1*sizeof(int)); for (i=0;i<m1;i++) { H->elem[i]=NULLKEY; } return 1; } //定义哈希函数 int Hash(int key) { return key % m1;//除留取余法 } //哈希表插入关键字 void InsertHash(HashTable* H, int key) { int addr = Hash(key);//求散列地址 while (H->elem[addr]!=NULLKEY)//如果不为空则冲突 { addr = (addr+1) % m1;//开放定址法线性探测 } H->elem[addr]=key;//直到有空位之后插入关键字 } //哈希表查找关键字 int SearchHash(HashTable* H, int key, int *addr) { *addr = Hash(key);//求散列地址 while (H->elem[*addr]!=key)//有冲突 { *addr = (*addr+1) % m1;//开放定址法线性探测 if (H->elem[*addr]==NULLKEY || *addr==Hash(key))//如果循环回到原点 { return 0;//关键字不存在 } } return 1; }
转载请注明原文地址: https://www.6miu.com/read-79574.html

最新回复(0)