数据结构的查找总结
本文为原创,转载使用请注明来至:http://blog.csdn.net/j903829182/article/details/38036873
/*
1.查找是在一个数据元素集合中查找keyword等于某个给定keyword数据元素的过程。
2.查找主要有静态查找,动态查找和哈希查找。
3.静态查找是指在数据元素集合中查找是否存在keyword等于某个给定keyword的数据元素。
4.动态查找除包含静态查找的要求,还包含在查找过程中同一时候插入数据元素集合中不存在的数据元素,或者从数据元素集合中
删除已存在的某个数据元素的要求。即假设在某个数据元素集合中进行了动态查找,则该数据元素集合可能会被改变。
5.哈希表是一种既适用于静态查找问题。又适用于动态查找问题,而且查找效率很高的解决查找问题的存储结构。
*/
#include<stdio.h>
#include<malloc.h>
#define MaxSize 100
#define true 1
#define false 0
typedef int KeyType;
typedef struct{
KeyType key;
}DataType;
typedef struct{
DataType list[MaxSize];
int size;
}SeqList;
//初始化
void ListInitiate(SeqList *L){
L->size=0;//设置初始元素的个数为0
}
//求当前数据元素个数
int GetListLength(SeqList L){
return L.size;//返回元素的个数
}
//插入数据元素
int ListInsert(SeqList *L,int i,DataType data){
int j;//定义变量j
if(L->size>=MaxSize){
printf("顺序表已满。无法插入。。\n");
return false;//返回
}
if(i<0||i>L->size+1){
printf("输入的參数不合法。不能进行插入!
!
\n");
return false;//返回
}else{
for(j=L->size;j>=i;j--){
L->list[j]=L->list[j-1];//移动元素
}
L->list[i-1]=data;//赋值
L->size++;//元素个数加一
return true;
}
}
//删除数据元素
int DeleteList(SeqList *L,int i,DataType *data){
int j=0;
if(L->size==0){
printf("线性表为空,不能执行删除操作!!\n");
return false;
}
if(i<0||i>L->size){
printf("删除的位置i不对。不可以进行删除!!\n");
return false;
}else{
*data=L->list[i-1];
for(j=i;j<L->size;j++){
L->list[j-1]=L->list[j];
}
L->size--;
return true;
}
}
//取数据元素
int ListGet(SeqList *L,int i,DataType *data){
if(i<0||i>L->size){
printf("取的位置不对,不能进行取值操作!!\n");
return false;
}else{
*data=L->list[i-1];
return true;
}
}
//推断是否为空的操作
int Empty(SeqList L){
if(L.size==0){
return true;
}else{
return false;
}
}
//打印全部输出的函数
void displayData(SeqList L){
int i;
for(i=0;i<L.size;i++){
printf("%d ",L.list[i]);
}
}
/*
1.静态查找的存储结构主要有顺序表。有序表和索引顺序表三种存储结构
2.顺序表上查找的基本思想:从顺序表的一端開始,用给定数据元素的keyword逐个与顺序表中个数据元素的关键
字进行比較。若在顺序表中查找到要查找的数据元素。则查找成功,函数返回该数据元素在顺序表中的位置;否则查找失败,
函数返回-1;
3.顺序表的查找查找平均查找长度为(n+1)/2
*/
int SeqSearch(SeqList s,DataType x){
//顺序表s中依次查找数据元素x
//查找成功,则返回该数据元素的位置,否则返回-1
int i=0;
while(i<s.size&&s.list[i].key!=x.key){
i++;
}
if(s.list[i].key==x.key){
return i;
}else{
return -1;
}
}
/*
1.有序顺序表的查找算法主要有顺序查找和折半查找
*/
//顺序查找(有序顺序表上的查找算法平均查找长度为(n+1)/2)
int OrderSeqSearch(SeqList s,DataType x){
//在有序顺序表s中顺序查找数据元素x
//查找成功。则返回该数据元素的位置。否则返回-1
int i=0;
while(i<s.size&&s.list[i].key<x.key){
i++;
}
if(s.list[i].key==x.key){
return i;
}else{
return -1;
}
}
/*
1.有序顺序表上折半查找算法的基本思想:在一个查找区间中,确定出查找区间的中心位置。用待查找数据元素的keyword与中心
位置上数据元素的keyword进行比較。若两者相等,则查找成功;否则,若前者小于后者,则把查找区间定为原查找区间的前半段继续这种
过程。否若前者大于后者。则把查找区间定为原查找区间的后半段继续这种过程。这种查找过程一直进行到查找区间的上界小于查找区间的
下界为止。
2.平均查找长度为lbn
*/
//有序顺序表上折半查找算法
int BinarySearch(SeqList s,DataType x){
//在有序顺序表s中折半查找数据元素x
//查找成功。则返回该数据元素的位置。否则返回-1
int low=0,high=s.size-1;//确定初始查找区间上下界
int mid;//
while(low<=high){
mid=(low+high)/2;//确定查找区间的中心位置
if(s.list[mid].key==x.key){
return mid;//查找成功
}else if(s.list[mid].key<x.key){
low=mid+1;
}else if(s.list[mid].key>x.key){
high=mid-1;
}
}
return -1;//查找失败
}
/*
1.动态查找的存储结构主要有二叉树结构和树结构两种类型。二叉树结构又分为二叉排序树,平衡二叉树等。
树结构又分为B_树和B+树等。
2.二叉排序树的基本概念:二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
a.若左子树不空,则左子树上全部结点的keyword值均小于根结点的keyword值。
b.若右子树不空。则右子树上的全部结点的keyword均大于等于根结点的keyword值。
c.左右子树也均为二叉排序树
3.二叉排序树通常採用二叉链存储结构。二叉排序树中结点的结构体定义例如以下:
typedef struct node{
DataType data;
struct node *leftChild;
struct node *rightChild;
}BiTreeNode;
4.在最坏情况下,二叉排序树的平均查找长度为O(n),在普通情况下,二叉排序树平均查找长度为O(lbn)
*/
//二叉排序树的查找算法。就是遍历二叉排序树,并在遍历过程中寻找要查找的数据元素是否存在。
typedef struct Node{
DataType data;//数据域
struct Node *leftChild;//左子树指针
struct Node *rightChild;//右子树指针
}BiTreeNode;//节点的结构体定义
//初始化
void Initiate(BiTreeNode **root){
*root=(BiTreeNode *)malloc(sizeof(BiTreeNode));
(*root)->leftChild=NULL;
(*root)->rightChild=NULL;
}
//左插入节点
//若当前节点curr非空,则在curr的左子树插入元素值为x的新节点
//原curr所指节点的左子树成为新插入节点的左子树
//若插入成功,则返回新插入节点的指针。否则返回空指针
BiTreeNode *InsertLeftNode(BiTreeNode *curr,DataType x){
BiTreeNode *s,*t;
if(curr==NULL){//推断当前节点是否为空
return NULL;//是空则返回NULL
}
t=curr->leftChild;//保存原curr所指节点的左子树
s=(BiTreeNode *)malloc(sizeof(BiTreeNode));//创建节点空间
s->data=x;//赋值
s->leftChild=t;//新插入节点的左子树为原curr的左子树
s->rightChild=NULL;//右子树为空
curr->leftChild=s;//新节点成为curr的左子树
return curr->leftChild;//返回新插入节点的指针
}
//右插入节点
//若当前节点curr非空,则在curr的右子树插入元素值为x的新节点
//原curr所指节点的右子树成为新插入节点的右子树
//若插入成功,则返回新插入节点的指针,否则返回空指针
BiTreeNode *InsertRightNode(BiTreeNode *curr,DataType x){
BiTreeNode *s,*t;
if(curr==NULL){//推断当前节点是否为空
return NULL;//是空则返回NULL
}
t=curr->rightChild;//保存原curr所指节点的右子树
s=(BiTreeNode *)malloc(sizeof(BiTreeNode));//创建节点空间
s->data=x;//赋值
s->rightChild=t;//新插入节点的右子树为原curr的右子树
s->leftChild=NULL;//右子树为空
curr->rightChild=s;//新节点成为curr的右子树
return curr->rightChild;//返回新插入节点的指针
}
//左删除子树
//若curr非空,则删除curr所指节点的左子树
//若删除成功,则返回删除节点的双亲节点,否则返回空指针
BiTreeNode *DeleteLeftTree(BiTreeNode *curr){
//假设当前节点为空或者左子树为空则返回NULL
if(curr==NULL||curr->leftChild==NULL){
return NULL;
}
//释放节点
//Destroy(&curr->leftChild);
curr->leftChild=NULL;//删除后。当前节点的左子树为NULL
return curr;//返回删除节点的双亲节点
}
//右删除子树
//若curr非空,则删除curr所指节点的右子树
//若删除成功,则返回删除节点的双亲节点,否则返回空指针
BiTreeNode *DeleteRightTree(BiTreeNode *curr){
//假设当前节点为空或者右子树为空则返回NULL
if(curr==NULL||curr->rightChild==NULL){
return NULL;
}
//释放节点
// Destroy(&curr->rightChild);
curr->rightChild=NULL;//删除后,当前节点的右子树为NULL
return curr;//返回删除节点的双亲节点
}
void Visit(DataType item){
printf("%c ",item);
}
//前序遍历
/*
1.訪问根节点
2.前序遍历根节点的左子树
3.前序遍历根节点的右子树
*/
void PreOrder(BiTreeNode *root,void Visit(DataType item)){
//前序遍历二叉树root,訪问操作为Visit()函数
if(root!=NULL){
Visit(root->data);//訪问数据
PreOrder(root->leftChild,Visit);//訪问左子树
PreOrder(root->rightChild,Visit);//反问右子树
}
}
//中序遍历
/*
1.中序遍历根节点的左子树
2.訪问根节点
3.中序遍历根节点的右子树
*/
void InOrder(BiTreeNode *root,void Visit(DataType item)){
//中序遍历二叉树root,訪问操作为Visit()函数
if(root!=NULL){
InOrder(root->leftChild,Visit);//訪问左子树
Visit(root->data);//訪问数据
InOrder(root->rightChild,Visit);//訪问右子树
}
}
//后序遍历
/*
1.后序遍历根节点的左子树
2.后序遍历根节点的右子树
3.訪问根节点
*/
void PostOrder(BiTreeNode *root,void Visit(DataType item)){
//中序遍历二叉树root,訪问操作为Visit()函数
if(root!=NULL){
PostOrder(root->leftChild,Visit);//訪问左子树
PostOrder(root->rightChild,Visit);//訪问右子树
Visit(root->data);//訪问根节点数据
}
}
//撤销二叉树操作
void Destroy(BiTreeNode **root){
if((*root)!=NULL&&(*root)->leftChild!=NULL){
Destroy(&(*root)->leftChild);
}
if((*root)!=NULL&&(*root)->rightChild!=NULL){
Destroy(&(*root)->rightChild);
}
free(*root);
}
void PrintBiTree(BiTreeNode *root,int n){
//逆时针旋转90度,打印二叉树root。n为缩进层数。初始值为0
int i;
if(root==NULL){
return ;//递归出口
}
PrintBiTree(root->rightChild,n+1);//遍历打印右子树
//訪问根节点
for(i=0;i<n-1;i++){
printf(" ");
}
if(n>0){
printf("---");
printf("%c\n",root->data);
}
PrintBiTree(root->leftChild,n+1);//遍历打印右子树
}
/*
//查找数据元素
BiTreeNode *Search(BiTreeNode *root,DataType x){
//查找数据元素x是否在二叉树root中
//查找到则返回该节点指针,未查找到则返回空指针
BiTreeNode *find=NULL;
if(root!=NULL){
if(root->data==x){
find=root;
}else{
find=Search(root->leftChild,x);//在左子树中找
if(find==NULL){
find=Search(root->rightChild,x);//在右子树中找
}
}
}
return find;//返回查找标志
}
*/
int Search(BiTreeNode *root,DataType item){
//在二叉排序树root上查找数据元素item是否存在
//查找成功,则返回1。否则返回0
BiTreeNode *p;
if(root!=NULL){
p=root;
while(p!=NULL){
if(p->data.key==item.key){
return 1;//查找成功
}
if(item.key>p->data.key){
p=p->rightChild;
}else{
p=p->leftChild;
}
}
}
return 0;//查找失败
}
//二叉树的插入算法
//二叉树的插入操作。要求首先查找数据元素是否已在二叉排序树中存在。若一存在,则不插入。
//若不存在,则把该数据元素插入到二叉排序树上查找失败时结点的左孩子或右孩子
int Insert(BiTreeNode **root,DataType item){
//在二叉排序树root中查找数据元素item是否存在,若存在,则返回0
//否则。把item结点插入到当前结点左孩子指针或右孩子指针上并返回1
BiTreeNode *current,*parent=NULL,*p;
current=*root;
while(current!=NULL){
if(current->data.key==item.key){
return 0;//数据元素已存在
}
parent=current;
if(current->data.key<item.key){
current=current->rightChild;
}else{
current=current->leftChild;
}
}
p=(BiTreeNode *)malloc(sizeof(BiTreeNode));
//生成新结点
p->data=item;
p->leftChild=NULL;
p->rightChild=NULL;
if(parent==NULL){
*root=p;//新结点成为根结点
}else if(item.key<parent->data.key){
parent->leftChild=p;//新结点成为该结点的孩子结点
}else{
parent->rightChild=p;//新结点成为该结点的右孩子结点
}
return 1;
}
void InTraverse(BiTreeNode *root){
//中序遍历二叉树root,并在遍历的过程中输出结点数据元素值
//结点数据元素设定为int类型
if(root==NULL){
return ;
}
if(root->leftChild!=NULL){
InTraverse(root->leftChild);
}
printf("%d ",root->data.key);
if(root->rightChild!=NULL){
InTraverse(root->rightChild);
}
}
/*
1.平衡二叉树的基本概念:平衡二叉树或者是一棵空树,或者是具有这样性质的二叉排序树:它的左子树和右子树都是平衡二叉树,
而且左子树和右子树的深度之差的绝对值不超过1.
2.构造平衡二叉树的基本方法是。在构造二叉排序树的基础上,假设插入了一个新结点后。使二叉树中某个结点的左子树和右子树
的深度之差的绝对值超过1。则调整对应的二叉树,使二叉树中该结点的左子树和右子树的深度之差的绝对值不超过1.
3.B_树是一种平衡多叉排序树。平衡是指全部叶结点都在同一层上,从而可避免出现二叉排序树那样的分支退货现象。多叉事指多于
二叉的排序树将降低二叉树高度,从而降低查找数据元素的比較次数。因此。B_树是一种动态查找效率较二叉排序树更高的树形结构。
*/
/*
1.哈希表是一种既适合用于静态查找问题。有适用于动态查找问题,而且查找效率很高的解决查找问题的存储结构。
2.构造哈希表的方法:设要存储的数据元素个数为n。设置一个长度为m(m>=n)的连续存储内存单元。分别以每一个数据元素的keywordki
为(0<=i<=n-1)为自变量,通过一个称为哈希函数的函数h(ki),把ki映射为内存单元的某个地址h(ki),并把该数据元素存储在这个内存单元中。从
数学角度的观点看,哈希函数h(ki)实际上是keywordki到内存单元的映射,因此,h(ki)也称为散列表,哈希表也称散列表。
3.哈希函数构造方法:
1.除留余数法:
哈希函数h(k)=kmodm,k数据元素keyword,m哈希表长度,装填因子=n/m(取值范围为0.6--0.9之间),m=1.1n-1.7n之间的素数。
2.直接取地址法:
h(k)=k+c,c为某个数值常量
3.数字分析法
4.哈希冲突解决方法
1.开发地址法
(1)线性探索查法(easy产生堆积问题)
线性探查法的数学递推公式为:
d0=h(k)
di=(d(i-1)+1)mod m(1<=i<=m-1)
(2)平方探查法(探查跨步比較大。可避免出现堆积问题)
平方探查法的数形递推公式为:
d0=h(k)
di=(d(i-1)+2^(i-1))mod m (a<=i<=m-1)
(3)伪随机数法(探查跨步是随机的。可避免出现堆积问题)
伪随机数法的递推公式为:
d0=h(k)
di=(d(i-1)+R)mod m(1<=i<=m-1) ,R可以取一个伪随机序列
2.链表法
链表法解决哈希冲突的基本思想是:假设没有发生哈希冲突,则直接存放该数据元素;假设
发生了哈希冲突,则把发生哈希冲突的数据元素另外存放在某个单链表中。
用链表法解决哈希冲突通常有两种方法:第一种方法是为发生哈希冲突的不同的同义词建立不同的单链表。
另外一种方法是为发生哈希冲突的全部同义词建立单链表。
*/
/*
1.哈希表设计要求:
1.哈希函数採用除留余数法,解决哈希冲突採用开放地址法的线性探查法。
2.设计函数表构造头文件。
头文件包含了结点结构体定义。以及哈希表初始化,哈希表元素插入。哈希表元素删除
,哈希表查找和哈希表撤销函数。
*/
typedef enum{
Empty2,Active2,Deleted2
}KindOfItem;//表项状态的枚举类型
typedef struct{
DataType data;
KindOfItem info;
}HashItem;//表项结构体
typedef struct{
HashItem *ht;//哈希表数组
int tableSize;//数组最大个数
int currentSize;//当前表项个数
}HashTable;//哈希表结构体
//哈希表操作初始化
int Initiate(HashTable *hash,int mSize){
//初始化函数
hash->tableSize=mSize;
hash->ht=(HashItem*)malloc(sizeof(HashItem)*mSize);
if(hash->ht==NULL){
return 0;
}else{
hash->currentSize=0;return 1;
}
}
//查找函数
int Find(HashTable *hash,DataType x){
//返回数据元素x的哈希地址
//查找成功,则返回大于等于0,其返回值为数据元素x在哈希表中的位置
//查找失败。则返回小于0。其返回值为数据元素x的哈希地址的负值
int i=x.key%hash->tableSize;
int j=i;
while(hash->ht[j].info==Active2&&hash->ht[j].data.key!=x.key){
//说明存在冲突
j=(j+1)%hash->tableSize;//哈希冲突函数继续查找
if(j==i){//说明已遍历整个哈希表未找到且表已满
return -hash->tableSize;
}
}
if(hash->ht[j].info==Active2){
return j;//找到,返回正值
}else{
return -j;//未找到。返回负值
}
}
//插入函数
int Insert(HashTable *hash,DataType x){
//把数据元素x插入到哈希表hash中
int i=Find(hash,x);//调用Find函数
if(i>=0){
return 0;//数据元素x已存在
}else if(i!=-hash->tableSize){//数据元素不存在且哈希表未满
hash->ht[-i].data=x;//数据元素赋值
hash->ht[-i].info=Active2;//置活动标志
hash->currentSize++;//当前表项个数加1
return 1;//返回插入成功
}else{
return 0;//返回插入失败
}
}
//删除函数
int Delete(HashTable *hash,DataType x){
//删除哈希表hash中的数据元素x
int i=Find(hash,x);//调用Find函数
if(i>=0){//查找到
hash->ht[i].info=Deleted2;//置删除标志
hash->currentSize--;//当前表项个数减1
return 1;//返回删除成功
}else{
return 0;//返回删除失败
}
}
//撤销函数
void Destroy(HashTable *hash){
//释放哈希表hash占用的动态存储空间
free(hash->ht);
}
//主函数
int main(){
SeqList myS={{710,342,45,686,6,841,429,134,68,264},10};
DataType x={686};
int i;
if((i=SeqSearch(myS,x))!=-1){
printf("该数据元素位置为%d\n",i);
}else{
printf("查找失败!!!\n");
}
//二叉排序树查找
DataType test[]={4,5,7,2,1,9,8,11,3},x2={9};
int n=9,s;
BiTreeNode *root=NULL;
for(i=0;i<n;i++){
Insert(&root,test[i]);
}
InTraverse(root);
printf("\n");
s=Search(root,x2);
if(s==1){
printf("\n数据元素%d存在\n",x2.key);
}else{
printf("\n数据元素不存在\n");
}
//哈希查找操作
printf("\n哈希操作:\n");
HashTable myhash;
DataType a[]={180,750,600,430,541,900,460},item={430};
int j,k,m=13;
Initiate(&myhash,m);
for(i=0;i<7;i++){
Insert(&myhash,a[i]);
}
for(i=0;i<7;i++){
j=Find(&myhash,a[i]);
printf("%d ht[]=%d\n",j,myhash.ht[j].data.key);
}
k=Find(&myhash,item);
if(k>=0){
printf("查找成功。元素%d的哈希地址为%d\n",item.key,k);
}else{
printf("查找失败!!!\n");
}
Delete(&myhash,item);
k=Find(&myhash,item);
if(k>=0){
printf("查找成功,元素%d的哈希地址为%d\n",item.key,k);
}else{
printf("查找失败!!!\n");
}
Destroy(&myhash);
return 0;
}
执行结果:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架