1. 程式人生 > >九、順序表和單鏈表的對比分析

九、順序表和單鏈表的對比分析

opera 基礎數據類型 insert 是我 public 位置 參數 amp 一次

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、小結

線性表中元素的查找依賴於相等比較符==

順序表適用於訪問需求量較大的場合(隨機訪問)

單鏈表適用於數據元素頻繁插入刪除的場合(順序訪問)

當數據類型相對簡單時,順序表和單鏈表的效率不相上下

九、順序表和單鏈表的對比分析