C 泛型線性查詢演算法 find

2022-05-07 12:06:12 字數 3463 閱讀 7506

《泛型程式設計和stl》筆記及思考。

線性查詢可能是最為簡單的一類查詢演算法了。他所作用的資料結構為一維線性的空間。這篇文章主要介紹使用 c++ 實現泛型演算法 find的過程。

首先介紹 c find 演算法的實現,用以引入 c++ 版本。

char *find1(char *first,char *last,int c)
該版本的演算法迴圈檢查每個元素,尾後指標(last)作為結束標識。

使用舉例如下:

char a[n];

...char *result = find1(a,a + n,c);

if(result == a + n)

printf("search failed\n");

else printf("found it");

c 實現的 find 演算法實現很簡單,但使用範圍很侷限,只能應用於字元陣列中對指定字元的查詢。

由於 c 版本 find 的使用範圍侷限性,在 c++ 中,我們可以使用泛型對策,利用 template 將函式的引數型別引數化。

首先,我們可以考慮設計乙個類似 c 版本的 find 演算法,以任意型別 t 的指標作為引數,代替原來的 char 指標。所以該方法宣告如下:

templatet *find2(t *first,t *last,t value);
這樣 find 方法就不在侷限於一種型別可以使用了。

不過,stl 的泛型做法不像上述那般顯而易見。stl 的線性查詢演算法是這樣定義的:

templateiterator find(iterator first,iterator last,const t& value)
為什麼是 find 而不是看起來更淺顯的 find2 呢?

原因簡單的說,是因為這樣的函式比 find2 更加的一般化。這種一般化的乙個主要體現就是,它不再依賴資料結構的具體實現。比如,在鍊錶上的線性查詢。

我們將把 find 用於單鏈表的線性查詢,來證實 find 的一般性。雖然陣列和鍊錶對元素的組織方式不同,但是 基於線性序列的 find 仍可以適用於二者。

下面是乙個鍊錶結點的資料結構:

struct int_node ;
鍊錶的遍歷方法:

int_node* p;

for (p = list_head; p != null; p = p->next)

//pass

單鏈表是乙個線性序列,線性查詢是一種常見的行為。既然是線性查詢,而我們之前又寫過線性查詢演算法,那麼我們不應該編寫重複的**,而是考慮重用這個函式。

首先考慮我們實現過的第乙個泛型查詢演算法 find2。find2 接受的引數為乙個範圍 [first,last),這個範圍通過傳遞兩個指標來界定。但是這裡有個顯而易見的問題,指向結點的指標如何在單鏈表上前進?假設我們有乙個 int_node 指標 first 並傳遞給 find2 函式,然後我們希望通過 first++ 來實現指標的移動,注意問題便在這裡,first ++ 操作無法到達下乙個元素。因為 find2 演算法應對的是線性序列使用陣列實現的情況,而現在,序列元素的組織方式為鏈式結構,指標前進的方式不再是通過增加元素指標的值(first++)來實現。對於鍊錶,操作應當是 first = first->next。

如何解決這個問題?

方案一:使用 c++ 中的操作符過載

如果上述的 operator++ 行為不符合需要,那麼就重新定義他的行為,

也即:

原 ++ 操作:	a = a + 1;

現 ++ 操作: a = a->next;

使得 find2 可以正常工作。

然而,重新定義引數型別為 int_node* 型別的 operator++ 操作符是不可能的,c++ 允許我們定義操作符的表示式意義,單不允許變動既有的語法意義(我們不能隨便的將乙個指標的自加行為改變為其他的操作,就像不能將整數 + 運算子定義為 減、乘操作,這是不合適的)。

外覆類的定義:

template//這裡傳遞的引數是 型別 int_node

struct node_wrap

node& operator* const

node* operator-> const

node_wrap& oeprator++()

node_wrap operator++(int)

bool operator== (const node_wrap& i) const

bool operator!= (const node_wrap& i) const

};

事實上我們還是過載了 operator++ 的行為,但是現在是在外覆類上的過載,而不是指標上的過載,對於外覆類來說,這種行為是合適的。

最後,由於 find 函式中的

while(... *first != value)
語句中,*first != value 這個不等運算子的操作並沒有定義,所以下面對他進行定義:

bool operator!= (csonst node_wrap& i,int n) const
那麼現在,我們欲查詢 int_node 中的某乙個特定值,我們不需要在重複編寫任何**了,我們可以重複利用 find,將查詢動作寫成單一函式呼叫:

find(node_wrap(list_head),node_wrap(),val);
其中第二個引數是利用 node_wrap 的預設建構函式做出。他產生出乙個內含 null 指標的 node_wrap。由於鍊錶最後乙個結點的 next 指標即為 null,所以我們會從 list_head 查詢直至鍊錶尾端。至此,我們重用了已有的演算法來作用於鍊錶上。

他將我們原始的結點指標包裝了起來,同時定義或過載了一些操作,使得整個外覆類對外顯示出乙個指標常見的操作介面,以便其他的元件可以透明的將他作為乙個指標來使用。而因不同的資料組織方法而形成指標操作差異將由外覆類負責包裝和隱藏,並由他在類的內部將這種差異進行具體的實現,導致最終的結果是,他將原本有差異的事物,統一了起來。

帶來的好處是什麼?

在考慮他帶來的好處之前,我們先想想沒有他是怎樣的情況。如果沒有外覆類的包裝,每當我們實現乙個概念上相同的資料結構時,我們要將所有在這個資料結構上存在的演算法實現一遍,即便已經存在相同概念的模型的演算法,但是由於資料的組織方式不同存在的一些差異,我們很難重用這些演算法。

外覆類帶來的好處顯而易見的是我們可以重用已經實現過的演算法。而得以實現這一點的關鍵就在於外覆類消除了差異性,對外提供了統一的介面,而差異性越少,我們能重複利用的部分就越多。

出處:本部落格中未標明**的文章歸作者 skipper 和共有,歡迎**,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

C 泛型演算法

標準庫並未給每個容器都定義成員函式來實現這些操作,而是定義了一組泛型演算法,稱他們為演算法是因為他們實現了一些經典演算法的公共介面,如排序和搜尋 稱他們為排序的是因為它們可以用於不同型別的元素和多種容器型別。大多數演算法都定義在標頭檔案algorithm中。標準庫還在標頭檔案numeric中定義了一...

C 泛型演算法

1 泛型演算法初始 標準庫演算法都是對乙個範圍內的元素進行操作 除了少數例外 並將此範圍稱為 輸入範圍 而且總是使用前兩個引數來表示次範圍,這兩個引數分別代表,要處理的首元素和尾元素之後位置的迭代器。1.1 唯讀演算法 只會讀取輸入範圍內的元素,而從不改變元素 find int sum accumu...

C 泛型演算法

1 泛型演算法定義 泛型演算法 因為它們實現共同的操作,所以稱之為 演算法 而 泛型 指的是它們可以操作在多種容器型別上 不但可作用於 vector 或 list 這些標準 庫型別,還可用在內建陣列型別 甚至其他型別的序列上,這些我們將在本章的 後續內容中了解。自定義的容器型別只要與標準庫相容,同樣...