问题:如何判断某个数据元素是否存在于线性表中?

遗失的操作 - find

  • 可以为线性表(List)增加一个查找操作
  • int find(const T &e) const;

    • 参数:

      • 待查找的数据元素
    • 返回值

      • >=0: 数据元素第一次在线性表中出现的位置
      • -1: 数据元素不存在

数据元素查找示例

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

编程实验:查找操作

LinkList.h

#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;
}

输出:

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 类没有提供。

带给使用者的疑惑:

仅仅定义LinkList<Test>类对象,而没有进行查找操作,为什么会报错呢?难道在使用LinkList管理自定义类类型时强制要求使用者重载'=='运算符?这是极不便利的。

解决方案:(两全折中方案)

在顶层父类实现默认的 '==' 重载实现;
当自定义类类型时,将自定义类继承自Object。
(以上仅保证编译通过,当需要进行find时,还需要使用者提供自定义类类型的'=='重载实现)

文件:Object.h

#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> 符合二次编译现象。


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧