问题:如何判断某个数据元素是否存在于线性表中?
遗失的操作 - 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> 符合二次编译现象。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。