順序查找、單鏈表查找、折半查找


線性表查找

在查找表的組織方式中,線性表是最簡單的一種。我們在學習線性表的時候對線性表操作的實現中就涉及到查找操作的實現,只不過當時沒有考慮到效率或者其他的問題,只采用了最簡單的一次循環遍歷進行查找。

順序查找(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)效率更高,但是折半查找只適用於有序表,且限於順序存儲結構。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM