顺序查找、单链表查找、折半查找

线性表查找

在查找表的组织方式中,线性表是最简单的一种。我们在学习线性表的时候对线性表操作的实现中就涉及到查找操作的实现,只不过当时没有考虑到效率或者其他的问题,只采用了最简单的一次循环遍历进行查找。

顺序查找(Sequential Search)

顺序查找(Sequential Search)的查找过程为:从表的一端开始,依次将记录的关键字给定值进行比较,若某个记录的关键字和给定值相等,则查找成功;若在扫描整个表后仍未找到关键字和给定值相等的记录则表示查找失败。

顺序查找方法既适用于线性表的顺序存储结构,也适用于线性表的链式存储结构。

对于顺序存储结构查找成功时可以返回其在顺序表中的位置,对于链式存储结构在查找成功时则可以返回该元素的指针,即结点所在地址。

假设有一个学生结构体定义如下:

[C代码]
typedef struct _student
{
int stu_number; // 学号
char name[20]; // 姓名
char gender[4]; // 性别
// 其他信息略
}STUENDT;
typedef int KEY_TYPE; // 关键字类型,此处以学生的学号作为查询关键字

学生学号唯一,因此可以将学号作为主关键字来区分学生。

下面以在一个学生信息集合中根据主关键字学号来查找某个学生为例,分别从顺序存储结构和链式存储结构介绍顺序查找算法的实现。

顺序表的定义

#define MAX_SIZE 100 // 顺序表最大长度
typedef STUDENT ELEM_TYPE; // 线性表元素类型
typedef struct
{
ELEM_TYPE data[MAX_SIZE]; // 使用数组存储顺序表数据
int length; // 顺序表长度
}SSTABLE; // 顺序查找表
// 注:为了贴合“查找”的主题,这里的顺序表我们取名为SSTABLE,即顺序查找表(Sequential Search Table)。

顺序表查找

从查找表的一端开始依次比较,实现代码如下:

int seq_search(SSTABLE st, KEY_TYPE key)
{
int i;
for (i = 0; i < st.length; i++)
{
if (st.data[i].stu_number == key)
{
return i + 1; // 下标从0开始,返回的位置从1开始
}
}
return 0; // 返回0表示查找失败
}

这里使用主关键字进行查找,所以结果要么是找到唯一的一个,要么没有找到,包括后面的查找算法实现均讨论使用主关键字进行查找。

顺序存储结构的顺序查找实现的改进

前面的查找算法在每一次循环时都需要检测i是否越界,即判断i是否小于顺序表长度,可以改进这个程序来免去这个检测,具体的操作是:在顺序表存储数据元素时,从数组的下标1开始存储,下标0的元素空置,然后在实现顺序查找时,将下标0的元素赋值为查找给定的值,然后从后向前查找,由于下标0的元素现在也是和给定值相等,所以这次查找必然会找到结果:原顺序表中存在主关键字与给定值相等的记录或者在原顺序表中没有查找到结果而与下标0完成比较。

改进后算法实现如下:

int seq_search2(SSTABLE st, KEY_TYPE key)
{
st.data[0].stu_number = key;
int i = st.length;
while (st.data[i].stu_number != key)
{
i--;
}
return i;
}

测试完整源码:

#pragma once

//顺序存储结构的顺序查找实现
#define MAX_SIZE 100  //数组最大容量
#include <stdio.h>

typedef struct student {
int stu_number;//学号
char name[32];//姓名
char gender[4];//性别
float score;//成绩
}STUDENT;//学生结构体


typedef struct seq_search_table {
STUDENT data[MAX_SIZE];//数组存储顺序表数据
int length;//顺序表长度

}SSTABLE;

int seq_search(SSTABLE table, int number) {
int i;
for (i = 0; i < table.length; i++)
{
if (table.data[i].stu_number == number)
{
return i + 1;//查找成功,i为数组下标,返回出去时+1表示第 x 个人
}
}
return 0;//表示没有查找到,查找失败
}

int main() {
//测试数据
SSTABLE table = {
{
{1001,"张三","男",88.5f},
{1003,"李四","男",80.5f},
{1005,"王五","男",78.5f},
{1007,"张三","女",90.0f},
{1002,"刘柳","男",88.5f},
{1004,"田淇","女",98.5f},
{1006,"陈八","男",91.0f},
},7

};
//==================================
int rlt1 = seq_search(table, 1007);
if (rlt1 == 0)
{
printf("查找失败,没有查找到该学号对应的学生信息!\n");
}
else {

printf("已查找到!该学生是第%d个\n", rlt1);
printf("学号:%d 姓名:%s 性别:%s 成绩:%.1f\n",table.data[rlt1-1].stu_number, table.data[rlt1-1].name, table.data[rlt1-1].gender, table.data[rlt1-1].score);
}
rlt1 = seq_search(table, 1008);
printf("\n");
if (rlt1 == 0)
{
printf("查找失败,没有查找到该学号对应的学生信息!\n");
}
else {

printf("已查找到!该学生是第%d个\n", rlt1);
printf("学号:%d 姓名:%s 性别:%s 成绩:%.1f\n", table.data[rlt1-1].stu_number, table.data[rlt1-1].name, table.data[rlt1-1].gender, table.data[rlt1-1].score);
}
printf("\n");
return 0;
}

测试结果:

这里分别查找学号为 1007和 1008的学生(样本中只有1007,没有1008)

 

 

 

链式存储结构的顺序表查找

顺序查找也适用于链式存储结构,在线性表的链式存储结构中我们已经实现过了,这里对比前面的顺序存储结构的实现,这里给出基于单链表的实现,首先是单链表的定义:

单链表的定义

typedef STUDENT ELEM_TYPE; // 线性表元素类型
typedef struct _link_node
{
ELEM_TYPE data; // 数据域
struct _link_node *next; // 指针域
}LINK_NODE, *LSTABLE; // 链式查找表(Linked Search Table)

基于单链表的顺序查找的实现如下:

LINK_NODE* seq_search(LSTABLE st, KEY_TYPE key)
{
LINK_NODE *p_find = st->next; // 指向头结点的next,即首元结点
while (p_find != NULL)
{
if (p_find->data.stu_number == key)
{
return p_find;
}
p_find = p_find->next;
}
return NULL;
}

测试完整源码:

//单链表的顺序查找
#include <stdio.h>

typedef struct student {
int stu_number;//学号
char name[32];//姓名
char gender[4];//性别
float score;//成绩
}STUDENT;//学生结构体


typedef STUDENT ELEM_TYPE;

typedef struct link_node {
ELEM_TYPE data;//数据域
struct link_node *next;//指针域
}LINK_NODE,*LSTABLE;//链式查找表(Linked Search Table)

//单链表的顺序查找
LINK_NODE * seq_search(LSTABLE table,int number) { //返回一个结点的地址--指针
LINK_NODE *p_find = table->next;
while (p_find !=NULL)
{
if (p_find->data.stu_number == number)
{
return p_find;//查找成功
}
p_find = p_find->next;//如果该结点的数据域中的学号与所要查找的number不匹配,则指向下一结点
}

return NULL;//循环结束,查找失败,指针类型,返回空
}

int main() {

LINK_NODE n1 = { {1001,"张三","男",88.5f},NULL };//尾结点,下一指针地址为空
LINK_NODE n2 = {{ 1003,"李四","男",80.5f }, &n1};//n2->next = &n1
LINK_NODE n3 = {{ 1005,"王五","男",78.5f }, &n2};//n3->next = &n2
LINK_NODE n4 = { {1007,"张三","女",90.0f}, &n3};//n4->next = &n3
LINK_NODE n5 = { {1002,"刘柳","男",88.5f}, &n4};//n5->next = &n4
LINK_NODE n6 = { {1004,"田淇","女",98.5f},&n5 };//n6->next = &n5
LINK_NODE n7 = { {1006,"陈八","男",91.0f}, &n6};//n7->next = &n6,首元结点

LINK_NODE head = { {0,"","",0.0f},&n7 };//head->next = &n7,头结点

LSTABLE table = &head;

LINK_NODE *p_rlt1 = seq_search(table, 1004);

if (p_rlt1!=NULL)
{
printf("学号:%d,\t姓名:%s,\t性别:%s,\t成绩:%0.1f\n",p_rlt1->data.stu_number,p_rlt1->data.name,p_rlt1->data.gender,p_rlt1->data.score);
}
else
{
printf("未查找到该学号对应学生的任何信息!");
}
LINK_NODE *p_rlt2 = seq_search(table, 1008);
printf("\n");
if (p_rlt2 != NULL)
{
printf("学号:%d,\t姓名:%s,\t性别:%s,\t成绩:%0.1f\n", p_rlt2->data.stu_number, p_rlt2->data.name, p_rlt2->data.gender, p_rlt2->data.score);
}
else
{
printf("未查找到该学号对应学生的任何信息!");
}
printf("\n");
return 0;
}

测试结果:

这里测试查找学号为 1004 和 1008 的学生信息,如果查找到该学生则打印该学生的全部信息

 

 

 

折半查找(Binary Search)

折半查找也叫做二分查找(Binary Search),其查找过程是:从表的中间位置的记录开始,如果给定值和中间记录的关键字相等,则查找成功;如果给定值大于或者小于中间位置的记录的关键字,则在表中大于或小于中间记录的那一半中继续查找,重复这个操作,直到查找成功,或者在某一步时查找区间为空,则代表查找失败。

这里需要注意,折半查找要求线性表中的元素要按关键字有序排列,且由于每次查找都按位置进行操作,所以要求是顺序存储结构

为了标记查找过程中每一次的查找区间,使用low和high标记当前查找区间的下界和上界,使用mid标记区间的中间位置。

折半查找要求

/*折半查找 要求: 1、线性表中的与元素按关键字有序排列 2、每次查找都是按位置进行操作,因此要求存储结构为顺序存储结构(链式存储查找复杂度高,增删复杂度低) 3、使用low和high标记当前查找取键的下界和上界,使用mid标记取键的中间位置 */

//折半查找的时间复杂度为O(log2n),要比顺序查找的O(n)效率更高, //但是折半查找只适用于有序表,且限于顺序存储结构。

折半查找过程

给出一个有序表(其数据元素的值即为关键字):

{6,13,21,29,30,37,41,47,55,66,68,78}

在其中查找关键字66的过程如下:

(1)初始查找区间,low为1,high为表长,计算mid=(low+high)/2

 

 

 

(2)顺序表中位置6的元素值小于关键字,因此下一次查找的区间为大于位置6的区间,因此调整low值为mid+1,再次计算mid:

 

 

 

(3)顺序表中第9个元素的值仍然小于关键字,因此继续调整查找区间,使low为mid+1,并再次计算mid:

 

 

 

4)顺序表中第11个元素的值大于关键字,因此下一次查找区间为小于第11个元素的区间,此时需要调整high为mid-1,并计算mid:

 

 

 

(5)此时得到mid所指示位置的元素即为需要查找的关键字,查找成功。否则查找失败。

折半查找实现

对于前面折半查找的过程,我们可以总结如下:

(1)设置查找区间初始值,low为1,high为表长

(2)当low小于等于high时,重复执行以下操作:

a.mid取low和high的中间值(因为mid表示位置,所以需取整)

b.将给定值key与中间位置记录的关键字进行比较,若相等则查找成功,返回mid

c.若不相等,则利用中间位置将表对分为前、后两个子表,如果key比中间位置记录的关键字小,则high取mid-1,否则low取mid+1

3)当low大于high时,说明查找区间为空,表示查找失败,返回0。

使用折半查找指定学号的学生

int binary_search(SSTABLE st, KEY_TYPE key)
{
int low = 1, high = st.length, mid;
while (low <= high)
{
mid = (low + high) / 2;
if (st.data[mid - 1].stu_number == key) // 找到指定学号的学生,减1是因为下标从0开始
{
return mid;
}
else if (st.data[mid - 1].stu_number > key) // 继续在前一子表中查找
{
high = mid - 1;
}
else // 继续在后一子表中查找
{
low = mid + 1;
}
}
return 0; // 表中不存在待查元素
}

测试源码

//
/*折半查找 要求:
1、线性表中的与元素按关键字有序排列
2、每次查找都是按位置进行操作,因此要求存储结构为顺序存储结构(链式存储查找复杂度高,增删复杂度低)
3、使用low和high标记当前查找取键的下界和上界,使用mid标记取键的中间位置
*/

//折半查找的时间复杂度为O(log2n),要比顺序查找的O(n)效率更高,
//但是折半查找只适用于有序表,且限于顺序存储结构。

#include <stdio.h>


typedef struct student {
int stu_number;//学号
char name[32];//姓名
char gender[4];//性别
float score;//成绩
}STUDENT;//学生结构体

#define MAX_SIZE 100
typedef STUDENT ELEM_TYPE;

typedef struct seq_search_table
{
STUDENT data[MAX_SIZE];//数组存储顺序表数据
int length;//顺序表长度

}SSTABLE;

//折半查找
int binary_search(SSTABLE table,int number) {

int low = 1;
int high = table.length;
int mid;
while (low<=high)
{
mid = (low + high)/2;//计算mid的值
if (table.data[mid-1].stu_number==number)//mid - 1 ,下标从1开始,而数组下标从0开始
{
//printf("%d ",mid);
printf("学号:%d,\t姓名:%s,\t性别:%s,\t成绩:%0.1f\n", table.data[mid-1].stu_number, table.data[mid-1].name, table.data[mid - 1].gender, table.data[mid - 1].score);

return mid;
}
else if (table.data[mid - 1].stu_number >= number)
{
high = mid - 1;
}else
{
low = mid + 1;
}
}

//没有找到与关键字相匹配的数据,此时high和low重叠,查找失败,结束
return 0;
}



int main() {
//测试数据
SSTABLE table = {
{
{1001,"张三","男",88.5f},
{1002,"李四","男",80.5f},
{1003,"王五","男",78.5f},
{1004,"张三","女",90.0f},
{1005,"刘柳","男",88.5f},
{1006,"田淇","女",98.5f},
{1007,"陈八","男",91.0f},
},7//学号为关键字,升序排放

};
printf("\n");
int rlt1 = binary_search(table,1004);
if (rlt1==0)
{
printf("查找失败!");
}

printf("\n");
int rlt2 = binary_search(table, 1008);
if (rlt2 == 0)
{
printf("查找失败!");
}

printf("\n");
return 0;
}

测试结果

这里测试查找学号为 1004 和1008的学生对应的信息;

 

 

 

折半查找的效率

折半查找的时间复杂度为O(log2n),要比顺序查找的O(n)效率更高,但是折半查找只适用于有序表,且限于顺序存储结构。



 水平有限,如有错误,请指正,谢谢!

 

posted @ 2020-10-04 18:32  destiny-2015  阅读(1236)  评论(0编辑  收藏  举报