学习笔记---检索与排序

41 篇文章 0 订阅
16 篇文章 0 订阅

排序


定义:使一组数据有序化的过程


分类:比较类算法(冒泡排序法、快速排序法、堆排序法)、非比较类算法(简单计数排序等)


比较类算法


快速排序法:


简述:

1.取序列中的一个数(一般取首元素data[0]作为基数

2.首先确定基数在排好序的数组中的位置,将其保存到该位置上保证比基数小的数都在基数左边,而比基数大的数都在基数右边。则基数就在其该在的位置上了

3.对于确定完位置的基数,将其左边作为一个新的数组,右边作为另一个新的数组。对这两个数组使用快速排序,如此反复。

4.当所有数都在其该在的位置上时,排序就完成了。


代码示例:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用来测试快速排序算法*/
void quicksort(int[],int,int);

int main()
{
    int i,data[]={10,55,90,1,4,6,3,9,100,45,31,46};
    quicksort(data,0,11);
    for(i=0;i<12;i++)
        printf("%d ",data[i]);
    //printf("Hello world!\n");
    return 0;
}

void quicksort(int data[],int first,int last)
{
    //i从左向右移动,j从右向左移动。t作为元素交换的中介。base存储当前基数的值
    int i,j,t,base;
    if(first>last)//如果first>last说明当前数组段已完成排序,直接返回
        return;
    base=data[first];//当前数组段的首元素作为当前基数
    i=first;//i从first开始右移
    j=last;//j从last开始左移
    /*将基数置于合适的位置*/
    while(i!=j)//如果i和j还未相遇,则继续循环
    {
        //j从右往左移,直到遇到比基数小的数或者与i相遇
        while(data[j]>=base&&i<j)
            j--;
        //i从左往右移,直到遇到比基数大的数或者与j相遇
        while(data[i]<=base&&i<j)
            i++;
        /*
        如果当前的情况不是i和j相遇的状况,
        则将i对应的比基数大的数,
        以及j对应的比基数小的数交换。
        */
        if(i<j)
        {
            t=data[i];
            data[i]=data[j];
            data[j]=t;
        }
    }
    /*
    以上过程的核心是使i从左往右移,并使之途经的元素都小于基数,
    而j从右往左移,并使之途经的元素都大于基数。
    这样,当以上循环结束,即i、j相遇时。就能保证i(j)此刻所在的位置的左边都是
    小于基数的数,而右边都是大于基数的数。
    且i(j)此刻所在是位置上的数将小于基数。
    则i(j)此刻所在的位置就是基数应当在的位置。
    */
    data[first]=data[i];
    data[i]=base;//将i所在位置的数和基数交换

    quicksort(data,first,i-1);//对基数左边的数组进行快速排序
    quicksort(data,i+1,last);//对基数右边的数组进行快速排序
}

结果:


解析:

1.ij相遇时,他们所在是位置必定是小于等于基数(而不是大于等于)。假定当前进入了最后一轮循环,j会先左移,直到遇到小于等于基数的数停下。此时i开始右移时,会在遇到大于基数的数之前遇到j,于是停止循环。则,ij的位置都停在了小于基数的值上。

2.快速排序和冒泡排序(冒泡排序法在之前的博文中已做解析,不再赘述)有类似的特点,即:一次循环能对多个元素的位置进行调整。这使其往往不需要进行完所有循环就能完成数组的排序,是十分实用高效的排序算法。


非比较类算法


简单计数排序:


简述:

假如有一个a数组:1,5,8,2,1,3,4,6,10,4,3,1,2

创建一个b数组大小为0~10共11个元素位

例遍一次待排序数组a,将每个数的出现次数记录至b数组中相应数组的位置中。

a1出现了3次,则b[1]中应当存储3

这样,只要对a数组做一次循环,就能得到一个b数组:0,3,2,2,2,1,1,01,0,1

只要循序输出b代表的信息,就能得到排序完成的数组a


代码示例:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用来测试简单计数排序算法*/
int main()
{
    int a[10]={6,1,12,6,18,1,18,7,0,6};//待排序数组
    int c[21]={0};//计数数组
    int i;
    /*为每个待排序的数计数*/
    for(i=0;i<10;i++)
        c[a[i]]++;
    /*输出排序完成的数组*/
    for(i=0;i<21;i++)
        while(--c[i]>=0)
            printf("%d ",i);
    return 0;
}
结果:


解析:

1.计数数组的长度应当比待排序数组的最大值大一。

2.此种排序方法不需要对元素进行比较,只需要对待排序数组进行一次循环,即可完成数组的排序,可谓效率惊人。

3.高效率伴随着的是高局限性:当待排序数组的最大值过大时,使用这种方法对内存空间的消耗也是惊人的(需要建立足够大的计数数组)。这种排序方法实际上是以空间换时间。



检索(查找、搜索)


定义:用户提供“关键字”,由计算机在特定的数据集合中找出相关信息。


检索需要面对的问题:针对的是大量的数据、时间要求高


分类:顺序查找、二分查找、非比较类查找


比较类查找


顺序查找:


代码示例:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用来测试顺序查找*/
#define SIZE 10//将数组大小作为常量定义,方便使用
int main()
{
    int d[SIZE]={34,43,98,72,12,47,31,43,1,78};//定义用于检索的数据集合
    int i;
    int key;//定义关键词key
    int index=-1;//定义检索标识index
    printf("Input a key you want to search:");
    scanf("%d",&key);//用户输入key
    for(i=0;i<SIZE;i++)//例遍整个数据集合
        if(key==d[i])//如果i对应的数据和用户输入的key相等
        {
            index=i;//使检索标识等于i(记录下当前数组下标)
            break;
        }
    if(index>=0)//如果检索标识不等于-1,说明index被赋值过,即找到了数据
        printf("The index of the key is %d .\n",index);//输出index
    else//否则,没有找到
        printf("Not found.\n");

    return 0;
}
结果:


解析:

1.循序查找适用于从有序或无序的数组中查找需要的数据,因为该算法会例遍整个数组。直到找到关键词key

2.循序查找往往需要例遍一半或大半的数组,才能找到关键词,甚至最坏的情况下。需要例遍整个数组才能找到关键词。当数据集合巨大时,循序查找的效率将十分低下


二分查找:


代码示例:

#include <stdio.h>
#include <stdlib.h>
/*
这个程序用于演绎二分法
原理:
当面对一组有序的数据时(如1~100),需要查找其中是否包含N这个数(N由用户输入,所以并不知道N的具体值)。
首先,将N与50进行比对。如果N>50,说明N在50~100的区间内,反之则N在1~50的区间内。
假如N>50,将N与75进行比对,如果N>75,说明N在75~100的区间内,反正则在50~75的区间内。。如此循环
介绍:
二分法效率高的原因是,他在每次比对之后,都能缩小一般的数据规模
如果总数据有一百万,使用逐个比对需要一百万次比对,而使用二分法最多只需要20次(一百万约为2的20次方)!
*/
#define SIZE 10

int search(int [],int);
int main()
{
    int d[SIZE]={1,3,9,12,32,41,45,62,75,77};
    int key,index;
    printf("Input a key you want to search:");
    scanf("%d",&key);
    index=search(d,key);
    if(index>=0)//index大于零时说明找到了需要的数
        printf("The index of the key is %d .\n",index);
    else
        printf("Not found.\n");
    return 0;
}

int search(int d[],int key)
{
    int low=0,high=(SIZE-1),mid,index=-1;//此处直接将index的初值赋为-1,这样只要能找到需要的数,index就一定大于零,免去了另外定义标识变量的麻烦
    while(low<=high)//如果low还小于high,继续循环
    {
        mid=(low+high)/2;//每次都取low、high的正中间取值
        if(d[mid]==key)//将中间的值和key对比
        {//如果找到了
            index=mid;
            break;
        }
        else if(d[mid]>key)//如果中间的值大于key,则key必定在low-mid的区间中
            high=mid-1;//将high赋值为mid-1
        else//否则,key必定在mid-high的区间中
            low=mid+1;//将low赋值为mid+1
    }
    return index;
}

结果:


解析:

1.二分查找只适用于从有序数组中检索数据,如果对象是无序数组,应先对齐进行排序。

2.二分查找的优势在于:每次比对之后,都能缩小一半的待检索数组的数据规模


二叉排序树中的二分查找:


定义:二叉排序树遵循左小右大原则存储数据,也是在利用二分法。方便后续的数据检索工作。因此二分查找对其也适用。


代码示例:

#include <stdio.h>
#include <stdlib.h>
/*这个程序从二叉排序树中检索数据*/

struct node
{
    struct node *lchild;
    int data;
    struct node *rchild;
};
int search(struct node*,int);
int main()
{
    /*建立node为节点的二叉排序树*/
    struct node *d1;
    d1=malloc(sizeof(struct node));
    d1->data=10;
    d1->lchild=NULL;
    d1->rchild=NULL;
    struct node *d2;
    d2=malloc(sizeof(struct node));
    d2->data=20;
    d2->lchild=NULL;
    d2->rchild=NULL;
    d1->rchild=d2;
    struct node *d3;
    d3=malloc(sizeof(struct node));
    d3->data=5;
    d3->lchild=NULL;
    d3->rchild=NULL;
    d1->lchild=d3;

    /*获取用户输入的关键词,并进入排序树检索*/
    int key=0;
    scanf("%d",&key);
    int flag=search(d1,key);
    if(flag==key)
        printf("找到了");
    else
        printf("没找到");
    return 0;
}

int search(struct node *p,int k)
{
    if(p==NULL)//如果当前给出的排序树为空
        return -1;
    else if(k==p->data)
        return k;//查找成功
    else if(k<p->data)//如果关键词小于当前节点值
        if(p->lchild!=NULL)//如果左节点不为空
            return search(p->lchild,k);//进入左结点寻找
        else//否则,返回-1代表查找失败
            return -1;
    else
        if(p->rchild!=NULL)
            return search(p->rchild,k);
        else
            return -1;
}

结果:


解析:

二叉排序树在建立时就遵循左小右大,方便二分查找的使用。


非比较类查找


基于简单计数数组的查找


定义:使用简单计数排序原理建立数据集合。


代码示例:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用于测试非比较类查找*/

int search(int h[],int key);//检索
void store(int h[],int data);//初始化
int main()
{
    int data[1000]={0};//定义数据集合大小为1000,允许存储0~999的数据
    int key,n;
    int i;
    /*循环为数据集合赋值*/
    for(i=0;i<6;i++)
    {
        scanf("%d",&n);
        store(data,n);
    }
    scanf("%d",&key);//获取key
    int result=search(data,key);//查找
    if(result)
        printf("在数组中找到.\n");
    else
        printf("没有此数据!\n");

    return 0;
}

void store(int d[],int n)
{
    d[n]++;//为和数据值相等的数组下标的数组值加一
}

int search(int d[],int key)
{
    return d[key];//直接返回当前值
}
结果:


解析:

1.此种检索算法可以说是速度最快的检索方法,一步到位

2.相应的,此种方法的局限性也十分巨大。依赖于排序中的简单计数排序,是以空间换时间的算法


基于哈希法的查找


哈希法:

定义:能将一个大区间的数映射到一个小区间的函数 y=h(x)

应用:将数据x使用函数y=h(x)计算后,将数据x存储入y位置


直接定址法:地址集合和关键字集合大小相同

平方取中法:对关键字平方,并取平方数的中值(如:关键字123,取123的平方得15129,取512作为地址)

折叠法:将一个关键字分为长度相等的多段(最后一部分位数不够可以短些),然后将各段相加。最后按存储数组的长度取后几位作为地址(如:关键字 9876543210,存储数组长度为1000,则将关键字分为四组:987|654|321|0,然后相加得:987+654+321+1=1962,再取后3位得到地址为962

除留取余法(最常用)y=x%p,选p为大质数,取y作为地址(如:关键字5505,存储数组长度为1000,则将5505%1000=505 作为地址)


处理冲突的方法:

定义:使用哈希法时,会出现两个数据计算得到的哈希地址相同的情况。这时候需要一个方法来解决地址上的冲突。


开放地址法:如果两个数据元素的哈希值相同,则在哈希表中为后插入的数据元素另外选择一个表项(该位置的前一个位置,或后一个位置,或该位置对某数取模等等)

再哈希法:如果两个数据元素的哈希值相同,则对后插入的数据元素再一次使用哈希函数。

链地址法:定义一个结点数组用于存储数据,如果两个数据元素的哈希值相同,为当前地址对应的结点增加一个子结点用于存储后插入的数据。


以下介绍的便是除留取余法加开放地址法


代码示例:
#include <stdio.h>
#include <stdlib.h>
/*这个程序用于测试用除留取余哈希函数定址,开放地址解决冲突的数据存储*/

/*
除留取余法容易理解
而开发地址法的冲突解决方式看似粗暴无序。
实际上只要根据存储时遵循的规则进行搜索,仍可搜索到存续其中的数据

*/

void insertHash(int h[],int len,int key);
int searchHash(int h[],int len,int key);
#define N 13
int main()
{
    int data[N]={0};
    int key,i;
    /*使用哈希法存储数据*/
    for(i=0;i<6;i++)
    {
        scanf("%d",&key);
        insertHash(data,N,key);
    }
    printf("Input a key you want to search:");
    scanf("%d",&key);
    /*使用哈希法检索数据*/
    int index=searchHash(data,N,key);
    if(index>=0)
        printf("The index of the key is %d.\n",index);
    else
        printf("Not found.\n");
    return 0;
}
void insertHash(int h[],int len,int key)
{
    int i;
    i=key%len;/*此处实现f(x)=x%13的哈希函数*/
    while(h[i]!=0)/*解决冲突*/
    {
        i++;
        i%=len;//反之i超出数组范围
    }
    h[i]=key;//找到可存储的位置之后,将数据存入
}
int searchHash(int h[],int len,int key)
{
    int i;
    i=key%len;/*此处再次实现f(x)=x%13的哈希函数以找到需要查找的值*/
    /*
    当前位置找不到,就前往其他可能的存储位置寻找
    出现h[i]==key,则找到了。
    出现h[i]==0即说明所有可能的存储位置都找遍了
    */
    while(h[i]!=0&&h[i]!=key)
    {
        i++;
        i%=len;//反之i超出数组范围
    }
    if(h[i]==0)//如果找不到
        i=-1;
    return i;


}

结果:

解析:
1.哈希法解决冲突的方式看似粗暴无序,实际上只要对需要检索的关键词使用相同的方法处理,就总能找到需要寻找的数据
2.哈希法的核心思想是:首先对关键词使用哈希函数,确定一个初步的寻找区间。然后通过向可能的位置循序查找,寻找关键词。

3.这种方法相对简单计数查找的优势在于:不需要过大的数组就能存储大量的数据、同样是使用空间换时间,这种方式存储数据对空间的利用效率更高

4.这种方法相对直接循序查找的优势在于:不需要盲目的从0开始例遍整个数组,通过关键字的哈希值可以直接确定一个距离可能存在的目标最近的检索起点然后再通过存储时确定的冲突解决方式进行循序查找,等于一开始就淘汰了一大批不需要检索的数据,将检索区间大大缩小




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值