iOS標準庫中常用資料結構和演算法之查詢

歐陽大哥2013發表於2019-04-22

囉嗦幾句

我本來想說的是Unix系統C標準庫所提供的一些演算法和資料結構API,但畢竟帶有iOS標題可能更加吸引眼球一些。其實我說的也沒有錯,因為iOS畢竟是從Unix衍生出來的系統,所以說標題所述也算是正確的。下面將要介紹的幾類API,有些可以在POSIX平臺中支援,有些則只能在FreeBSD中支援,有些則只有在iOS系統中單獨支援。

iOS系統中的C標準庫中主要提供了線性查詢、二分查詢、雙向連結串列、快速排序、堆排序、歸併排序、並行排序、基數排序、二叉排序樹、雜湊表、KV資料庫、位串、記憶體池、cache等眾多的API。這些API基本覆蓋了在應用中的常見資料結構和演算法的需求。

那既然Foundation和CoreFoundation庫中都提供了眾多的基於OC語言的演算法和資料結構為什麼還要使用這些函式呢?原因就是效能和相容性。

?線性查詢

功能:遍歷陣列,查詢滿足條件的記錄。

標頭檔案:#include <search.h>

平臺:POSIX

函式簽名

//查詢
void *lfind(const void *key, const void *base, size_t *nelp, size_t width, int (*compar)(const void *, const void *));
//查詢並追加
void *lsearch(const void *key, void *base, size_t *nelp, size_t width, int (*compar)(const void *, const void *));

複製程式碼

引數

key: [in] 要查詢的元素。

base:[in] 陣列元素的首地址。

nelp: [in/out] 陣列的元素個數指標。

width: [in] 陣列中每個元素的尺寸。

compar: [in] 函式比較器,查詢時會對陣列中的每個元素進行遍歷並和要查詢的元素呼叫函式比較器來判斷是否匹配成功。函式比較器的格式如下:

/*
@key: 是要查詢的元素,也是上面lfind和lsearch中傳入的第一個引數key。
@element: 元素在陣列中的地址,這裡需要注意的是這個引數不是元素本身,而是元素所在的陣列中的偏移地址。
@return: 如果比較結果相等則返回0,否則返回非0
*/
 int compar(const void *key, const void *element);
複製程式碼

return:[out] 如果陣列中找到了對應的元素,則返回元素在陣列中的地址,如果沒有找到則lfind返回NULL。而lsearch則會將要查詢的元素追加到陣列後面,並返回元素在陣列中的地址,同時更新nelp的值。

描述

系統提供的lfind和lsearch函式都是用於線性查詢,但是二者的區別是:

  1. lsearch中的key必須和陣列的元素是相同的資料型別,而lfind則沒有這個要求。
  2. 因為lsearch函式在查詢不到時會將key的內容拷貝(memcpy)到陣列的尾部,因此lsearch除了具有查詢外還有新增陣列元素的能力,而且陣列的容量應該要大於nelp引數中所指定的陣列的元素個數,否則就有可能產生異常。同時在函式返回後nelp中儲存的將是最終陣列中實際的元素個數,這也是為什麼nelp要以指標的形式進行傳遞。而lfind則只是查詢並不會追加。

lsearch也有可能在查詢新增失敗時返回NULL。

示例程式碼:

typedef struct student
{
    int age;
    char *name;
} student_t;

//注意這裡的key的型別可以不和陣列元素型別相同,同時第二個引數是元素在陣列中的指標而不是元素本身。
int lfindcompar(const char *key, const student_t *pstudent)
{
    return strcmp(key, pstudent->name);
}

//注意這裡的key的型別必須要和陣列元素型別相同,同時第二個引數是元素在陣列中的指標而不是元素本身。
int lsearchcompar(const  student_t *key, const student_t *pstudent)
{
    return strcmp(key->name, pstudent->name);
}

void main()
{
    student_t students[10] = {{10, "Bob"}, {20, "Alex"}, {15, "Lucy"}, {19, "Ada"}, {25, "Max"}};
    size_t  count = 5;  // 實際的元素個數為5

    //lfind第一次查詢沒有找到
    student_t *pstudent = lfind("Lily", students, &count, sizeof(student_t), &lfindcompar);
    NSAssert(pstudent == NULL, @"oops");  //沒有找到。

    
    student_t newstudent = {20, "Lily"};
    //lsearch中的key的型別必須要和陣列元素型別保持一致,如果沒有找到就會新增到陣列尾部,並且數量引數count會加1
    pstudent = lsearch(&newstudent, students, &count, sizeof(student_t), &lsearchcompar);
    NSAssert(pstudent != NULL && count == 6, @"oops");

    //再次通過lfind就查詢成功了。
    pstudent = lfind("Lily", students, &count, sizeof(student_t), &lfindcompar);
    NSAssert(pstudent != NULL, @"oops");
}

複製程式碼

?二分查詢

功能:對有序陣列進行二分查詢,並查詢滿足條件的記錄,二分查詢法的時間複雜度為logN。

標頭檔案:#include <stdlib.h>

平臺:POSIX

函式簽名


void *bsearch(const void *key, const void *base, size_t nel, size_t width, int (*compar) (const void *, const void *));
//bsearch_b並不是POSIX標準中的函式,而是iOS對二分查詢的block形式的擴充套件
void *bsearch_b(const void *key, const void *base, size_t nel, size_t width, int (^compar) (const void *, const void *));

複製程式碼

引數

key: [in] 要查詢的元素。

base:[in] 陣列元素的首地址。

nel: [in] 陣列的元素個數。

width: [in] 陣列中每個元素的尺寸。

compar: [in] 函式比較器,查詢時會對陣列的某些元素和要查詢的元素呼叫函式比較器來判斷是否匹配成功。函式比較器的格式如下

/*
@key: 是要查詢的元素,也是上面bsearch和bsearch_b中傳入的第一個引數key。
@element: 元素在陣列中的地址,這裡需要注意的是這個引數不是元素本身,而是元素所在的陣列中的偏移地址。
@return : 如果比較結果相等則返回0, 如果key小於element則返回小於0,如果key大於element則返回大於0
*/
 int compar(const void *key, const void *element);
複製程式碼

return:如果找到則返回元素在陣列中的指標,如果沒有找到則返回NULL。

描述

函式要求陣列必須是有序的,至於是升序還是降序則跟函式比較器的返回是相關的。預設的情況是按升序進行二分查詢的。bsearch_b和bsearch的區別是前者是block的形式的比較器,而後者則是函式形式的比較器,block形式的比較器功能更加強大一些。

示例程式碼:

int bsearchcompar(const int *key, const student_t *pstudent)
{
    return *key - pstudent->age;
}

void main()
{
   student_t students[5] = {{10, "Bob"}, {20, "Alex"}, {30, "Lucy"}, {40, "Ada"}, {50, "Max"}};

   int age = 30;  //查詢的關鍵字
   student_t *pstudent = bsearch(&age, students, sizeof(students)/sizeof(student_t), sizeof(student_t), &bsearchcompar);
   NSAssert(pstudent != NULL && pstudent->age == 30);

}

複製程式碼

相關文章