数据结构(六)之线性表

1.1 线性表
1.1.1 生活中的线性表

  • 幼儿园小朋友排队的情况

1.1.2 线性表的表现形式

  • 零个或多个元素组成的集合
  • 数据元素在位置上是有序排列的
  • 数据元素的个数是有限
  • 数据元素的类型必须相同

1.1.3 线性表(List)的抽象定义

  • 线性表是具有相同类型n(n>= 0)个数据元素的有限序列
    (a0, a1 , 。。。 an)
    ai 是表项(数据元素),n是表长度

1.1.3 线性表(List)的性质

  • a0 是线性表的第一个元素,只有一个后继
  • an 是线性表的最后一个元素,只有一个前驱
  • 除a0 和an 外,其他元素既有后继,又有前驱
  • 直接支持逐项访问和顺序存取

1.2 线性表的实现

1.2.1 问题

  • 线性表只是一个单纯的概念吗?
  • 如何在程序中描述和使用一个线性表

1.2.2线性表常用操作

  • 将元素插入线性表
  • 将元素从线性表中删除
  • 获取目标位置处元素的值
  • 设置目标位置处元素的值
  • 获取线性表长度
  • 清空线性表

1.3 线性表的顺序存储

1.3.1顺序存储

  • 线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表中的数据元素
    在这里插入图片描述

1.3.2 设计思路

  • 可以用一维数组来实现顺序存储结构
    • 存储空间: T* m_array;
    • 当前长度: int m_length
template<typename T>
class SeqList : public List<T>
{
protected:
	T * m_array;
	int m_length;
};

1.3.3 顺序存储结构元素的获取

  • 判断合法性
  • 将目标位置作为数组下标获取元素

1.3.4 顺序存储结构元素的插入

  • 判断目标位置是否合法
  • 将目标位置之后的所有元素后移一个位置
  • 将新元素插入目标位置
  • 线性表长度+ 1

1.3.5顺序存储结构元素的删除

  • 判断目标位置是否合法
  • 将目标位置后所有元素前移一个位置
  • 线性表长度减1

1.4

1.4.1 继承图
在这里插入图片描述
1.4.2 问题
在SeqList这个类中完成了具体的插入,删除,获取,设置,但是这个SeqList这个类还是抽象类型?

  • 因为顺序结构存储空间的指定并没有在SeqList中完成,而是在StaticList 和DynamicList中完成

1.4.3 SeqList 设计要点

  • 抽象类模板,存储空间的位置大小由子类完成
  • 实现顺序存储结构线性表的关键操作(增删改查等)
  • 提供数组操作符,方便快捷获取操作

1.4.4 SeqList中成员函数实现的注意事项

  • 获取容量的成员函数Capacity要设置成纯虚函数,因为容量需要到SeqList的子类中才可以确定
virtual int Capacity() const = 0;
  • 需要重载两个版本的数组操作符,一个是const的,一个是非const的。
T& operator[](int i) ;
T& operator[](int i) const ;

1.5 staticList 和DynamicList的实现
1.5.1 思考

  • staticList 和DynamicList的如何实现,差异在哪里?是否可将Dynamic作为StaticList的子类实现?

1.5.2 static 设计实现

  • 设计要点
  • 类模板
    • 使用原生数组作为顺序存储空间
    • 使用模板参数决定数组大小
template < typename T, int N>
class StaticList : public SeqList<T>
{
protected:
	T m_space[N];		// 顺序存储空间, N 为模板参数
public:	
	StaticList();		// 指定父类成员的具体指
	int capacity() const; 
}

1.5.3 DynamicList 设计要点

  • 类模板
    • 申请连续堆空间作为顺序存储空间
    • 动态设置顺序存储空间的大小
    • 保证重置顺序存储空间时的异常安全性

1.5.4DynamicList 设计要点

  • 函数异常安全的概念
    • 不泄露任何资源
    • 不允许破坏数据
  • 函数异常安全的基本保证
    • 如果异常被抛出
      • 对象内的任何成员仍然能保持有效状态(如果这个函数抛出了异常,那么在捕获完异常以后,这个对象仍然是有用的)
      • 没有数据的破坏及资源的泄漏

1.5.5 DynamicList 设计要点

template <typename T>
class DynamicListpublic SeqList<T>
{
protected:
	int m_capacity;  // 顺序存储空间的大小
public:
	DynamicList(int capacity); //申请空间
	int capacity() const;
	void resize(int capacity); //重新设置顺序存储空间的大小
	~DynamicList() ; //归还空间
}

1.5.6 DynamicList 异常安全分析

在这里插入图片描述

1.6 顺序存储线性表的分析
1.6.1 效率分析(大O表示法)

  • 问题: 长相相同的两个SeqList ,插入和删除操作的平均耗时是否相同?
    不一定:
    举例
SeqList<int > s1 ; //5
SeqList<string> s2 //5
s1.insert(0,1);
s2.insert(0,"zhangsan"); //这里可能会发生类对象的copy,所以两个SeqList的耗时需要具体对象,具体分析,不仅仅是大O表示法

1.6.2 思考,下面的代码正确吗

StaticList<int*, 5> s1;
StaticList<int*5> s2;
for(int i = 0; i < s1.capacity(); i++)
{
	s1.insert(0, new int(i));
} 
s2  = s1;  //copy  
for(int i =0; i<s1.length(); i++)
{

	delete s1[i];
	delete s2[i];
}
  • 问题是 同一块内存空间被释放了两次
    在这里插入图片描述

1.6.3 思考,下面的代码正确吗

void func()
{
    DynamicList<int> d1(5);
    DynamicList<int> d2 = d1;  // copy construct 

    for(int i = 0; i<d1.capacity(); i++)
    {
        d1.insert(i, i);
        d2.insert(i, i * i);
    }

    for(int i =0; i < d1.length(); i++)
    {
        cout << d1[i] << endl;
    }
}

在这里插入图片描述

1.6.4 如何解决上面问题

  • 分析,对于容器类型的类,可以考虑禁用copy构造函数和赋值操作
template <typename T>
class List : public Object 
{
protected:
    List(const List&);
    List& operator=(const List&);
public:
    List(){}  // 必须有,要不然会报错,因为定义了copy构造以后,编译器不会提供默认构造函数
};
  • 这种方法合理吗? 从生活的角度来看是合理的,因为是容器的类。两个水杯的例子,
  • 第一个水杯中的水不可能复制一份给第二个杯子
  • 从面向对象来看,我们希望从生活中的例子直接搬移到面向对象编程中,生活中的容器的东西是不能
    复制的。
int main()
{
    DynamicList<int> d1(5);
    DynamicList<int> d2 = d1;
    DynamicList<int> d3(5);
    d3 =d1;
    return 0;
}
  • 禁用了copy构造和赋值以后,就会报这种错
    在这里插入图片描述
  • 我想着,子类的copy 构造函数会默认调用父类的构造函数,要不然不会报错,证明一下
  • 在子类中不定义copy构造函数的时候,会默认调用父类的copy构造函数
  • 但是在子类中定义copy构造函数的时候,会默认调用父类的无参构造函数
  • 比较下面的两个例子
class Parent
{
protected:
    int m_i;
    //Parent(const Parent & e);
public:
    Parent()
    {
        cout << "Parent()" << endl;
    }

    Parent(const Parent & e)
    {
        cout << "Parent(const Parent & e)" << endl;
        m_i = e.m_i;

    }


};

class Child : public Parent
{
public:
    Child()
    {
        cout << "Child()" << endl;
    }
    /*
    Child(const Child &obj)
    {
        cout << "Child(const Child &obj)" << endl;
    }
    */
};
int main()
{
    Child c1;
    Child c2 =c1;
    return 0;
}

在这里插入图片描述

class Parent
{
protected:
    int m_i;
    //Parent(const Parent & e);
public:
    Parent()
    {
        cout << "Parent()" << endl;
    }

    Parent(const Parent & e)
    {
        cout << "Parent(const Parent & e)" << endl;
        m_i = e.m_i;
    }

};
class Child : public Parent
{
public:
    Child()
    {
        cout << "Child()" << endl;
    }
    Child(const Child &obj)   // 默认调用父类无参数构造函数
    {
        cout << "Child(const Child &obj)" << endl;
    }

};
int main()
{
    Child c1;
    Child c2 =c1;
    return 0;
}

在这里插入图片描述
1.6.5 问题

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

    return 0;
}

在这里插入图片描述

  • 问题的原因是线性表必须先插入元素,才能使用操作符[]访问元素。
  • 问题分析
    • 顺序存储结构线性表提供了输出操作符重载,通过重载能快捷方便的获取目标位置处的数据元素,在具体的使用形式上类型数组,但是由于本质不同,不能代替数组使用。
    • 这样就有了数组类的需求

参考一 : 狄泰软件课程
如有侵权:请联系邮箱 1986005934@qq.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值