【數據結構】23_順序表和單鏈表的對比分析

問題:如何判斷某個數據元素是否存在於線性表中?

遺失的操做 - find

  • 能夠爲線性表(List)增長一個查找操做
  • int find(const T &e) const;node

    • 參數:ios

      • 待查找的數據元素
    • 返回值編程

      • >=0: 數據元素第一次在線性表中出現的位置
      • -1: 數據元素不存在

數據元素查找示例

LinkList<int> list;
for (int i=0; i<5; ++i)
{
    list.insert(0, i);
}
cout << list.find(3) << endl;

編程實驗:查找操做

LinkList.hide

#ifndef LINKLIST_H
#define LINKLIST_H

#include "List.h"
#include "Exception.h"

namespace DTLib
{

template <typename T>
class LinkList : public List<T>
{
public:
    LinkList()
    {
        m_header.next = nullptr;
        m_length      = 0;
    }

    bool insert(const T &e) override  // O(n)
    {
        return insert(m_length, e);
    }

    bool insert(int i, const T &e) override  // O(n)
    {
        bool ret = ((0 <= i) && (i <= m_length));

        if (ret)
        {
            Node *node = new Node();
            if (node != nullptr)
            {
                Node *current = position(i);

                node->value = e;
                node->next = current->next;
                current->next = node;

                ++m_length;
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element ...");
            }
        }

        return ret;
    }

    bool remove(int i) override  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if (ret)
        {
            Node *current = position(i);

            Node *toDel = current->next;
            current->next = toDel->next;
            delete toDel;

            --m_length;

        }

        return ret;
    }

    bool set(int i, const T &e) override  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if (ret)
        {
            position(i)->next->value = e;
        }

        return ret;
    }

    T get(int i) const  // O(n)
    {
        T ret;

        if (!get(i, ret))
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
        }

        return ret;
    }

    bool get(int i, T &e) const override  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if (ret)
        {
            e = position(i)->next->value;
        }

        return ret;
    }

    int  find(const T &e) override  // O(n)
    {
        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;
    }

    int length() const  // O(1)
    {
        return m_length;
    }

    void clear()  // O(n)
    {
        while (m_header.next)
        {
            Node *toDel = m_header.next;
            m_header.next = toDel->next;
            delete toDel;

            --m_length;
        }
    }

    ~LinkList()  // O(n)
    {
        clear();
    }

protected:
    struct Node : public Object
    {
        T value;
        Node *next;
    };

    mutable struct : public Object
    {
        char reserved[sizeof (T)];
        Node *next;
    }m_header;

    int m_length;

    Node *position(int i) const  // O(n)
    {
        Node *ret = reinterpret_cast<Node*>(&m_header);

        for (int p=0; p<i; ++p)
        {
            ret = ret->next;
        }

        return ret;
    }
};

}

#endif // LINKLIST_H

文件:main.cpp函數

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace DTLib;

int main()
{
    cout << "main begin" << endl;

    LinkList<int> list;

    for (int i=0; i<5; ++i)
    {
        list.insert(0, i);
    }

    for (int i=0; i<list.length(); ++i)
    {
        cout << list.get(i) << endl;
    }

    cout << "-----------" << endl;

    cout << list.find(3) << endl;

    cout << "main end" << endl;

    return 0;
}

輸出:this

main begin
4
3
2
1
0
-----------
1
main end

find的疑惑

class Test
{
public:
    Test(int i = 0)
    {
        this->i = i;
    }
private:
    int i;
};
會發生什麼?
void main()
{
    linkList<Test> list;
}
編譯輸出:
error: no match for 'operator==' (operand types are 'Test' and 'const Test')
             if (node->value == e)
分析:

LinkList 模板類的 find 函數須要使用 '==' 運算符,而 Test 類沒有提供。spa

帶給使用者的疑惑:

僅僅定義LinkList<Test>類對象,而沒有進行查找操做,爲何會報錯呢?難道在使用LinkList管理自定義類類型時強制要求使用者重載'=='運算符?這是極不便利的。設計

解決方案:(兩全折中方案)

在頂層父類實現默認的 '==' 重載實現;
當自定義類類型時,將自定義類繼承自Object。
(以上僅保證編譯經過,當須要進行find時,還須要使用者提供自定義類類型的'=='重載實現)指針

文件:Object.hcode

#ifndef OBJECT_H
#define OBJECT_H

namespace DTLib
{

class Object
{
public:
    void *operator new (unsigned int size) noexcept;
    void operator delete (void *p);
    void *operator new[] (unsigned int size) noexcept;
    void operator delete[] (void *p);
    bool operator == (const Object &obj);
    bool operator != (const Object &obj);
    virtual ~Object() = 0;
};

}

#endif // OBJECT_H

文件:Object.cpp

#include "Object.h"

#include <cstdlib>

namespace DTLib
{

void *Object::operator new (unsigned int size) noexcept
{
    return malloc(size);
}

void Object::operator delete (void *p)
{
    free(p);
}

void *Object::operator new[] (unsigned int size) noexcept
{
    return malloc(size);
}

void Object::operator delete[] (void *p)
{
    free(p);
}

bool Object::operator == (const Object &obj)
{
    return (this == &obj);
}

bool Object::operator != (const Object &obj)
{
    return (this != &obj);
}

Object::~Object()
{
}

}

文件:main.cpp

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace DTLib;

class Test : public Object
{
public:
    Test(int i = 0)
    {
        this->i = i;
    }

    bool operator== (const Test &t)
    {
        return (this->i == t.i);
    }

private:
    int i;
};

int main()
{
    cout << "main begin" << endl;

    Test t1(1);
    Test t2(2);
    Test t3(3);
    LinkList<Test> list;

    list.insert(t1);
    list.insert(t2);
    list.insert(t3);

    cout << list.find(t2) << endl;

    cout << "main end" << endl;

    return 0;
}

輸出:

main begin
1
main end

效率對比分析

時間複雜度對比分析

操做 SqlList 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)
有趣的問題

順序表的總體時間複雜度比單鏈表要低,那麼單鏈表還有使用價值嗎?

效率的深度分析

  • 實際工程中,時間複雜度只是效率的一個參考指標

    • 對於內置基礎類型,順序表和單鏈表的效率不想上下
    • 對於自定義類型,順序表在效率上低於單鏈表
  • 插入和刪除

    • 順序表:設計大量數據對象的複製操做
    • 單鏈表: 只設計指針操做,效率與數據對象無關
  • 數據訪問

    • 順序表: 隨機訪問,可直接定位數據對象
    • 單鏈表: 順序訪問,必須從頭訪問數據對象,沒法直接定位

工程開發中的選擇

  • 順序表

    • 數據元素的類型相對簡單,不涉及深拷貝
    • 數據元素相對穩定,訪問操做遠多於插入和刪除操做
  • 單鏈表

    • 數據元素的類型相對複雜,複製操做相對耗時
    • 數據元素不穩定,須要警察插入和刪除,訪問操做較少

小結

  • 線性表中元素的查找依賴於相等比較操做符(==)
  • 順序表適用於訪問需求量較大的場合(隨機訪問)
  • 單鏈表適用於數據元素頻繁插入和刪除的場合(順序訪問)
  • 當數據類型相對簡單時,順序表和單鏈表的效率不相上下

以上內容整理於狄泰軟件學院系列課程,請你們保護原創!


有關 "find的疑惑" 章節的相關說明一:

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace DTLib;

class Test : public Object
{
public:
    Test(int i)
    {
        this->i = i;
    }

    bool operator== (const Test &t)
    {
        return (this->i == t.i);
    }

private:
    int i;
};

int main()
{
    cout << "main begin" << endl;

    LinkList<Test> list;

    cout << "main end" << endl;

    return 0;
}

編譯輸出:

error: no matching function for call to 'Test::Test()'

當沒有對Test的構造函數提供默認參數,編譯時會報錯。

分析:

struct Node : public Object
{
    T value;  // 這裏致使!!
    Node *next;
};

STL中的表現

#include <iostream>
#include <list>

using namespace std;

class Test
{
    int i;
public:
    Test(int i)
    {
        this->i = i;
    }
};

int main()  // 編譯正常
{
    list<Test> list;

    return 0;
}

由於二者採用不一樣的策略,STL更爲友好。


有關 "find的疑惑" 章節的相關說明二:

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace DTLib;

class Test
{
public:
    Test(int i = 0)
    {
        this->i = i;
    }
private:
    int i;
};

int main()
{
    cout << "main begin" << endl;

    LinkList<Test> list;

    cout << "main end" << endl;

    return 0;
}

編譯輸出:

error: no match for 'operator==' (operand types are 'Test' and 'const Test')
             if (node->value == e)

我的疑惑:
在C++類模板編程時,編譯器會進行兩次編譯,
第一次,對類模板自己語法語義檢查;
第二次,對實例化後的調用部分進行編譯。

那麼上面代碼沒有對 find 進行調用爲何會編譯報錯呢?
有明白這裏的同窗麻煩留言告知一下。

STL中的表現:

#include <iostream>
#include <list>

using namespace std;

class Test
{
    int i;
public:
    Test(int i)
    {
        this->i = i;
    }
};

int main()
{
    list<Test> list;

    return 0;
}

編譯經過

#include <iostream>
#include <list>

using namespace std;

class Test
{
    int i;
public:
    Test(int i)
    {
        this->i = i;
    }
};

int main()
{
    list<Test> list;

    list.sort();

    return 0;
}

編譯輸出:

error: no match for 'operator<' (operand types are 'Test' and 'Test')
        if (*__first2 < *__first1)
            ~~~~~~~~~~^~~~~~~~~~~

STL 中 list<T> 符合二次編譯現象。

相關文章
相關標籤/搜索