效率分析

template <typename T>
class SqlList : public List<T>
{
public:
    bool insert(const T &e);            // O(1)
    bool insert(int i, const T &e);     // O(n)
    bool remove(int i);                 // O(n)
    bool set(int i, const T &e);        // O(1)
    bool get(int i, T &e) const;        // O(1)
    int length() const;                 // O(1)
    void clear();                       // O(1)
    
    T &operator[] (int i);              // O(1)
    T operator[] (int i) const;         // O(1)
    
    virtual int capacity() const = 0;   
};

问题

长度相同的两个 SqlList, 插入和删除操作的平均耗时是否相同?

不一定,需要具体情况具体分析。
例:

SqlList<string> ss;
SqlList<string> si;

ss.insert(0, "D.T.Software");
si.insert(0, 1);

SqlList中,最耗时的是插入和删除操作,因为要进行移位,尤其当数据元素是自定义类类型,并且类非常庞大时耗时尤为明显。
因此,分析一段代码的效率,不能够只看时间复杂度(大O表示法),大O表示法仅为参考指标,而非绝对指标。还需要具体情况具体分析,当前算法是否真的满足需求。
由此也证明顺序存储表不适合类类型的元素存储,适合基本类型。

下面的代码正确吗?为什么?

StaticList<int*, 5> s1;
StaticList<int*, 5> s2;

for (int i=0; i<s1.capacity(); ++i)
{
    s1.insert(0, new int(i));
}

s2 = s1;            // 注意 1

for (int i=0; i<s1.length(); ++i)
{
    delete s1[i];  // 注意 2
    delete s2[i];
}

不正确。
注意 1:两链表中对应两元素指向同一堆空间
注意 2:内存被释放两次,行为未定义

下面的代码正确吗?为什么?

void func()
{
    DynamicList<int> d1(5);
    DynamicList<int> d2 = d1;   // 注意 1
    
    for (int i=0; i<d1.capacity(); ++i)
    {
        d1.insert(i, i);        // 注意 2
        d2.insert(i, i*i);      // 注意 2
    }
    
    for (int i=0; i<d1.lenth(); ++i)
    {
        cout >> d1[i] << endl;
    }
}

不正确。
注意 1: 两链表中对应两元素指向同一堆空间
注意 2: d1中插入的数据被重写
注意 3: 对象析构时,同一堆空间被释放两次

  • 分析:对于容器类型的类,可以考虑禁用拷贝构造和赋值操作.
template <typename T>
class List : public Object
{
protected:
    List(const List&);
    List &operator= (const List&);
public:
    List() { }
    // ...
};

课程中的解释:面向对象是将生活中的概念平行搬移到程序设计中,而生活中无法完成两个容器的拷贝动作。

文件:List.h

#ifndef LIST_H
#define LIST_H

#include "Object.h"

namespace DTLib
{

template<typename T>
class List : public Object
{
public:
    List() {}
    virtual bool insert(const T &e) = 0; 
    virtual bool insert(int i, const T &e) = 0;
    virtual bool remove(int i) = 0;
    virtual bool set(int i, const T &e) = 0;
    virtual bool get(int i, T &e) const = 0;
    virtual int length() const = 0;
    virtual void clear() = 0;

protected:
    List(const List&);
    List<T> &operator= (const List&);
};

}

#endif // LIST_H

下面的代码正确吗?为什么?

int main()
{
    StaticList<int, 5> list;
    
    for (int i=0; i<list.capacity(); ++i)
    {
        list[i] = i * i;
    }
    
    return 0;
}

不正确。
在 [] 数组操作符重载函数中可见, m_length 为 0, 下表合法性检查无法通过,抛出异常

问题分析

顺序存储结构线性表提供了数组操作符重载,通过重载能够快捷方便的获取目标位置处的数据元素,在具体的使用形式上类似数组,但由于本质不同,不能代替数组使用。

  • 线性表必须先插入元素,才能使用操作符 [] 访问元素

实战预告:数组类开发

image.png

小结

  • 顺序存储线性表的插入和删除操作存在重在效率隐患
  • 线性表作为容器,应该避免拷贝构造和拷贝赋值
  • 顺序存储线性表可能被当成数组误用
  • 工程开发中可以考虑使用数组类代替原生数组使用

以上内容整理于狄泰软件学院系列课程,请大家保护原创!


TianSong
734 声望138 粉丝

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