查找

遍历比对查找每一项,找到匹配的哪一个数据元素(记录)。

一、概念术语

查找表

把它理解为是一个名词,不要理解为动词哦,其实就是一个表,同一类型数据元素(或记录)的集合。

关键字

指的是数据元素中的某一个值,可以用它来唯一表示这个数据元素。(分为主关键字和次关键字)

查找

根据一个值,确定这个值在查找表中位置。

动态查找表与静态查找表

如果在查找表的时候,如果表中没有这个目标值,我们就插入关键字等于给定值,这样就称为是动态查找表,而否则就是静态查找表。

平均查找长度

ASL = Σ(Pi)(Ci) Pi为查找表中第i个记录的概率。Ci表示找到关键字的时候,和给定值比较的关键字的个数。(比较的次数)

二、线性表的查找

2.1 顺序查找

顺序查找适合与线性表的顺序存储结构以及链式存储结构
  • 线性表顺序存储结构

#include <stdio.h>
#include <stdlib.h>

#define LIST_INT_SIZE 100 // 线性表的初始空间
#define LIST_INCREAMENT 10 // 增量
typedef int KeyType ; // 关键字 
typedef int infoType ; // 其他信息

#define OK 1
#define ERROR 2
#define TRUE 1
#define FALSE 0
#define LIST_INCREAMENT 10 // 增量
#define OVERFLOW -1
typedef struct {
    KeyType key;             // 关键字 
    infoType otherInfo;        // 其他信息
}ElementData;

typedef struct {
    ElementData *el;
    int length; // 当前数据的量
    int listsize; // 当前空间的最大容量
}SStable;

// 初始化 
int create(SStable &q) {
    q.el = (ElementData *)malloc(sizeof(ElementData)*LIST_INT_SIZE);
    if(!q.el) {
        exit(OVERFLOW);
    }
    q.listsize = LIST_INT_SIZE;
    q.length = 0;
    return OK;
}

// 插入 
int Listinsert(SStable &L, int i, KeyType e)     // 插入 
 {
     // 判断i值是否正确 
     if(i<1 || i>L.listsize+1) {
         return ERROR;
    }
    // 如果空间已满 
    if(L.length == L.listsize) {
        ElementData *newList = (ElementData*)realloc(L.el,(L.listsize+LIST_INCREAMENT)*sizeof(ElementData));  
        if(!newList) {
            exit(OVERFLOW);
        }
        L.el = newList;
        L.listsize += LIST_INCREAMENT;     
    }
    // 插入
    
    ElementData *q = &L.el[i-1];
    q->key = e;
    L.length++;
    return OK; 
 } 
  
void ListTraverse_Sq(SStable &L)  // 遍历 
{      
    for(int i = 0; i < L.length; i++)  
    {  
        printf("第%d个:\t%d\n",i+1,*(L.el + i));
    }
    
}   

// 执行查找 
int search_List(SStable &L, KeyType e) {
    int i ;
    for(i = L.length; i>=1; i--){
        if(L.el[i].key == e) {
            return i;
        }
    }
    return 0;
}
// 优化之后的算法 
int search_List_New(SStable &L, KeyType e) {
    L.el[0].key = e;
    int i;
    for(i = L.length; L.el[i].key != e; i--);
    return i;
    
}

int main() {
    SStable p;
    create(p);
    for(int i=1;i<=10;i++) 
    {
        Listinsert(p,i, i+1);//插值 
    }
    ListTraverse_Sq(p);
    
    // 开始查找喽
    int index = search_List_New(p, 4);
    printf("位置索引为%d",index);
    return 0; 
}
#include <iostream>
#include <stdlib.h>
using namespace std;

#define LIST_INT_SIZE 100 // 线性表的初始空间
#define LIST_INCREAMENT 10 // 增量

#define OK 1
#define ERROR 2
#define TRUE 1
#define FALSE 0
#define LIST_INCREAMENT 10 // 增量
#define OVERFLOW -1
 
 typedef int Status; // 返回结果 
 typedef int Bool;
 typedef int ElemType; // 数据类型
 
 typedef struct SQLIST{
     ElemType *elm;
     int length;    // 长度 
     int listsize;    // 当前分配的空间 
 }Sqlist; 
 
 /**
 * 【0】初始化 
 * 参数: 传入一个结构体的地址 
 * 使用: Sqlist L; // 声明一个结构体    initList(L);
 **/ 
  
 Status initList(Sqlist &L) 
 {
     // 创建一串空间,将首地址赋值给elm
      
     L.elm = (ElemType *)malloc(sizeof(ElemType)*LIST_INT_SIZE);
     if(!L.elm) {
         exit(OVERFLOW);
    }
    L.length = 0;
    L.listsize = LIST_INT_SIZE;
    return OK;
 }
 
 /**
 * 【1】销毁 
 * 参数:  传入一个结构体的地址
 * 使用:  Sqlist L; // 声明一个结构体    destroyList(L);
 **/ 
 Status destroyList(Sqlist &L)        // 销毁 
 {
     L.length = 0;
     L.listsize = 0;
     free(L.elm);
     L.elm = NULL;
     return OK;
 }
 
 /**
 * 【2】插值 
 * 参数:  (结构体的地址, 第i个位置插入, 插入的元素 e) 
 * 使用:   Listinsert(L,i, i+1);
 **/ 
 Status Listinsert(Sqlist &L, int i, ElemType e)     // 插入 
 {
     // 判断i值是否正确 
     if(i<1 || i>L.length+1) {
         return ERROR;
    }
    // 如果空间已满 
    if(L.length == L.listsize) {
        ElemType *newList = (ElemType*)realloc(L.elm,(L.listsize+LIST_INCREAMENT)*sizeof(ElemType));  
        if(!newList) {
            exit(OVERFLOW);
        }
        L.elm = newList;
        L.listsize += LIST_INCREAMENT;     
    }
    // 插入
    
    ElemType *q = &L.elm[i-1];
    *q = e;
    L.length++;
    return OK; 
 } 
 
 /**
 *  【3】遍历 
 *  参数:  结构体地址 
 *  
 **/ 
  
 void ListTraverse_Sq(Sqlist &L)  // 遍历 
{      
    cout<<"---------------遍历开始----------------------\n";
    for(int i = 0; i < L.length; i++)  
    {  
        cout<<"第"<< i +1 <<"个:\t"<<*(L.elm + i)<<"\n";  
    }
    cout<<"-----------------遍历结束--------------------\n";
}  

/**
*    【4】取值 
*   参数:  (结构体的地址, 第i个位置, 取出的元素 e)
*/ 
 
void getEleList(Sqlist &L, int i, ElemType &e) 
{
    if(i<1 || i>L.length) {
        return ;
    }
    e = L.elm[i-1];
} 

/**
*     【5】删除 
*    参数:  (结构体的地址, 第i个位置, 删除的元素 e)
*     
**/ 
Status ListDelete(Sqlist &L, int i, ElemType &e)
{
    ElemType *q = &L.elm[i-1];// 取得要删除的元素的地址
    e = *q;
    q = q+1;
    for(;q<=&L.elm[L.length-1]; ++q) {
        *(q-1) = *q;
    }
    L.length-=1;
    return OK;
} 

// 查找某一项,返回其索引 
Status search_List(Sqlist &L, ElemType e) {
    int i;
    for(i = L.length; i>=1; i--) {
        if(L.elm[i] == e) {
            return i;
        }
    }
    return 0;
}

 int main() {
     Sqlist L;
     initList(L);
    //destroyList(L);
    for(int i=1;i<=10;i++) 
    {
        Listinsert(L,i, i+1);//插值 
    }
    ListTraverse_Sq(L); // 遍历 
    Status index = search_List(L, 3);
    cout<<index<<endl;
//    int e1;
//    getEleList(L, 1, e1);// 取第一个值 
//    cout<<e1<<endl;
//    int e2;
//    ListDelete(L, 2, e2);
//    ListTraverse_Sq(L); // 遍历 
 }
在程序中初始化创建查找表时,由于是顺序存储,所以将所有的数据元素存储在数组中,但是把第一个位置留给了用户用于查找的关键字。例如,在顺序表{1,2,3,4,5,6}中查找数据元素值为 7 的元素,则添加后的顺序表为:

  • 链式结构的顺序查找
// 查找某一给值的位置
int  search(Link head, ElemType e) {
    int count = 0;
    Link p;
    p = head->next;
    if(p == NULL) {
        cout<<"链表为空"<<endl;
        return 0;
    }
    while(p!=NULL) {
        count++;
        if(p->data == e) {
            return count;
        }
        p = p->next;
    }
}
  • 性能分析

Pi 为第 i 个数据元素被查找的概率,所有元素被查找的概率的和为 1;Ci 表示在查找到第 i 个数据元素之前已进行过比较的次数。若表中有 n 个数据元素,查找第一个元素时需要比较 n 次;查找最后一个元素时需要比较 1 次,所以有 Ci = n – i + 1。

优点: 算法简单、对结构无任何要求,即使用与顺序存储结构,也适用于链式存储结构。
缺点: 平均查找长度有点长,耗时、当n值比较大的时候,查找速度会降低。

2.2 折半查找(二分查找)

限制条件: 必须采用顺序存储结构,并且数据必须是有序排列,递增或者递减。

例如,在{5,21,13,19,37,75,56,64,88 ,80,92}这个查找表使用折半查找算法查找数据之前,需要首先对该表中的数据按照所查的关键字进行排序:{5,13,19,21,37,56,64,75,80,88,92}

int search(SStable &L, KeyType key) {
    int low = 1;
    int high = L.length;
    int middle;
    
    while(low <= high) {
        middle = (low + high) / 2;
        if(L.el[middle-1].key == key) {
            return middle;
        } else if(L.el[middle-1].key < key) {
            low = middle + 1;
        } else {
            high = middle -1;
        }
    }
    return 0;
}

折半查找的运行过程可以用二叉树来描述,这棵树通常称为“判定树”。例如图 1 中的静态查找表中做折半查找的过程,

在判定树中可以看到,如果想在查找表中查找 21 的位置,只需要进行 3 次比较,依次和 56、19、21 进行比较,而比较的次数恰好是该关键字所在判定树中的层次(关键字 21 在判定树中的第 3 层)。

对于具有 n 个结点(查找表中含有 n 个关键字)的判定树,它的层次数至多为:log2n + 1(如果结果不是整数,则做取整操作,例如: log211 +1 = 3 + 1 = 4 )。

同时,在查找表中各个关键字被查找概率相同的情况下,折半查找的平均查找长度为:ASL = log2(n+1) – 1。

性能分析

优点:

查找次数少、查找效率高。

缺点:

对结构要求高,查找前需要排序,限制因素多。




Meils
1.6k 声望157 粉丝

前端开发实践者