九、順序表和單鏈表的對比分析
1、如何判斷某個數據元素是否存在於線性表中?
find()
操作:
可以為線性表
List
增加一個查找操作int find(const T& e)const;
參數:待查找的數據元素
返回值:
大於0:數據元素在線性表中第一次出現的位置
-1:數據元素不存在
針對基礎數據類型,首先在頂層父類List
中增加一個虛函數virtual int find(const T& e) const = 0;
,然後在各子類中實現這個函數
// 順序表中的實現 SeqList.h int find(const T& e) const // O(n) { int ret = -1; for(int i = 0; i < m_length; i++) { if(m_array[i] == e) { ret = i; break; } } return ret; } // 單鏈表中的實現 LinkList.h int find(const T& e) const { int ret = -1; int i = 0; Node* node = m_header.next; while(node) { if(node->value == e) { ret = i; break; } else { node = node->next; i++; } } return ret; }
針對自定義類類來說
class Test { int i; public: Test(int v = 0) { i = v; } }; int main() { Test t1(1); Test t2(2); Test t3(3); LinkList<Test> list; return 0; } // 會報一個錯,錯誤的地方在LinkList.h int find(const T& e) const { ...... while(node) { if(node->value == e) // 這句話報錯 } ...... } /* 報錯的原因是: node->value 和 e都是Test對象,並沒有重載Test類的"=="操作符,在這裏編譯器不知道如何去比較這兩個Test對象 */
解決方案1:在Test
類中直接重載相等比較操作符==
class Test
{
int i;
public:
Test(int v = 0)
{
i = v;
}
bool operator == (const Test& t)
{
return (i == t.i);
}
};
這樣就可以編譯通過,但是這樣寫存在著一個問題,我們的本意是定義一個單鏈表對象,保存的對象元素是Test
對象,並沒有進行實際的查找,也就是說還沒想用==
操作符來比較,我們就要去在Test
類中重載==
操作符,這意味著,但凡我們想要定一個這樣的保存自定義類類型的單鏈表對象,我們就必須在自定義類類型中重載==
find()
查找函數的便利性還沒有體現出來,編譯錯誤先出現了,find
操作使我們自定義類類型的時候也更加麻煩了。但是
find
操作還是應該存在的,需要解決的問題是,使find
函數依然存在,但是自定義類類型的時候也不需要每次都重載,只在需要用到查找函數的類類型時再重載。
思路:在頂層父類中實現操作符==
和!=
的重載,定義類時,都繼承於這個父類,這樣類模板中的find()
函數實現就可以通過編譯。如果自定義類類型需要用到這個find()
函數時,再重載操作符==
實現相等比較邏輯。
// Object.h
class Object
{
public:
...
bool operator == (const Object& obj);
bool operator != (const Object& obj);
...
};
// Object.cpp
bool Object::operator == (const Object& obj)
{
// 默認實現的方式最好就是通過比較地址
return (this == &obj);
}
bool Object::operator != (const Object& obj)
{
return (this != &obj);
}
// 使用的時候,就需要在定義自定義類類型的時候繼承Object父類
class Test : public Object
{
int i;
public:
Test(int v = 0)
{
i = v;
}
};
// 這樣,這句話就不會出現編譯錯誤了
LinkList<Test> list;
// 下面再考慮查找find函數
Test t1(1);
Test t2(2);
Test t3(3);
LinkList<Test> list;
list.insert(0,t1);
list.insert(0,t2);
list.insert(0,t3);
list.find(t3);
// 查找的依據應該是Test內的i的值,此時就需要在Test類中重載"=="操作符, 提供具體的相等比較邏輯
class Test : public Object
{
public:
...
bool operator ==(const Test& t)
{
return (i == t.i);
}
...
};
頂層父類中重載的== !=
只是提供了一個默認的相等比較符的實現方式,是為了類模板中編譯通過,針對某個具體的自定義類類型的時候,如果需要用到==
或!=
時,需要在自定義類中重載這兩個操作符,因為默認的邏輯不是我們需要的相等或不等比較的實現邏輯。
2、順序表和單鏈表的對比分析
操作 | SeqList | LinkList |
---|---|---|
insert |
O(n) | O(n) |
remove |
O(n) | O(n) |
set |
O(1) | O(n) |
get |
O(1) | O(n) |
find |
O(n) | O(n) |
length |
O(1) | O(1) |
clear |
O(1) | O(n) |
從時間復雜上來說,單鏈表和順序表比起來並不高效,但是工程上單鏈表用得比順序表更多
順序表的整體時間復雜度比單鏈表要低,那麽單鏈表還有使用價值嗎?
實際工程開發中,時間復雜度只是效率的一個參考指標
- 對於內置基礎類型,順序表和單鏈表的效率不相上下
- 對於自定義類類型,順序表再效率上低於單鏈表
效率的深度分析
插入和刪除:
- 順序表:涉及大量數據對象的復制操作
- 單鏈表:只涉及指針操作,效率與數據對象無關
基礎內置類型如
int
,順序表的復制操作只涉及到4個字節,而單鏈表涉及的是指針操作,4字節或8字節,兩者效率相差不大自定義類類型:非常復雜的類類型,涉及深拷貝的類類型,采用順序表來存儲,但凡到插入和刪除一個對象,就要進行很多次大數據對象的復制,還是深拷貝對象的復制,這個大數據對象占用的空間可能會很大,此時順序表的插入和刪除,效率極低;如果采用單鏈表來存儲的話,就只涉及指針操作,效率和具體的數據對象類型是無關的
數據訪問
- 順序表:隨機訪問,可直接定位數據對象,內部實現是用原生數組來做的,定位的時候基本不耗時,時間復雜度是常量
- 單鏈表:順序訪問,必須從頭訪問數據對象,無法直接定位
工程開發中的選擇:
- 順序表:
- 數據元素的類型想對簡單,不涉及深拷貝
- 數據元素相對穩定,訪問操作遠多於插入和刪除操作
- 單鏈表:
- 數據元素的類型相對復雜,復制操作相對耗時
- 數據元素不穩定,需要經常插入和刪除,訪問操作比較少
3、小結
線性表中元素的查找依賴於相等比較符
==
順序表適用於訪問需求量較大的場合(隨機訪問)
單鏈表適用於數據元素頻繁插入刪除的場合(順序訪問)
當數據類型相對簡單時,順序表和單鏈表的效率不相上下
九、順序表和單鏈表的對比分析