数据结构和算法——3.列表(List)

列表(List)

线性表的定义

线性表:简称表,是n(n≥0)个具有相同类型的数据元素的有限序列

线性表的长度:线性表中数据元素的个数

空表:长度等于零的线性表,记为:𝐿 = ()

非空表记为:𝐿 = e0, e1, … , ei-1, ei , … , en-1

其中, ei(0 ≤ 𝑖 < 𝑛)称为数据元素;

下角标 i 表示该元素在线性表中的位置或序号 。

线性表的特性

有限性:线性表中数据元素的个数是有穷的。

相同性:线性表中数据元素的类型是同一的。

顺序性:线性表中相邻的数据元素𝑒𝑖−1和𝑒𝑖之间存在序偶关系 𝑒𝑖−1, 𝑒𝑖 ,

即𝑒𝑖−1是𝑒𝑖的前驱,𝑒𝑖是𝑒𝑖−1的后继;𝑒0无前驱,𝑒𝑛−1无后继,其它每个

元素有且仅有一个前驱和一个后继。

➢ (a)结点集N中有一个唯一的开始结点,它没有前驱,但有一个唯一的后继;

➢ (b)对于有限集N, 它存在一个唯一的终止结点,该结点有一个唯一的前驱而没有后继;

➢ (c)其它的结点皆称为内部结点,每一个内部结点既有一个唯一的前驱,也有一个唯一的后继;

➢ (d)线性表所包含的结点个数称为线性表的长度,它是线性表的一个重要参数;长度为0的线性表称为空表;

线性表的操作

LenList(L):求表L的长度

GetElem(L, i):取表L中第i个数据元素

SearchElem(L, e):按值查找

InsertElem(&L, i, e):在表L中第i个位置插入新的数据元素e

DeleteElem(&L, i):删除表L中第i个数据元素

线性表的ADT (Abstract Data Type)

Operations

  1. 求表的长度:size(L)

初始条件:线性表L已存在

操作结果:返回L中数据元素个数

  1. 取表L中第i个数据元素:GetElem(L, i)

    初始条件:线性表L已存在, 1≤i≤LenList(L)

    操作结果:返回L中第i个数据元素的值

  2. 按值查找:SearchElem(L, e)

    初始条件:线性表已存在

    操作结果:若e存在表中,返回第一个e的位置,否则返回0

  3. 在L中第i个位置插入新的数据元素e :InsertElem(&L, i, e)

    初始条件:线性表L已存在, 1≤i≤LenList(L)+1

    操作结果:在L中第i个位置插入新的数据元素e,表长加1

  4. 删除表中第i个数据元素:DeleteElem(&L, i)

    初始条件:线性表已存在且非空,1≤i≤LenList(L)

    操作结果:删除L中第i个位置的数据元素,表长减1

两种类型的存储结构

  • 顺序存储结构

➢ 用一组连续的存储单元依次存储数据元素

➢ 数据元素之间的逻辑关系由元素的存储位置来表示。

  • 链接存储结构

➢ 用一组任意的存储单元存储数据元素

➢ 数据元素之间的逻辑关系用指针来表示

基于两种存储结构的实现

顺序表(Sequence List, Array)

• 线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素

链表(Linked List)

• 线性表的链式存储结构,指的是用一组任意的存储单元存储线性表的数据元素,

这组存储单元可以是连续的,也可以是不连续的。

顺序表实现:

template<class T>
class arrayList : public linearList<T> {
    public:
    // 构造函数、复制构造函数和析构函数
    arrayList(int initialCapacity = 10);
    arrayList(const arrayList<T>&);
    ~arrayList() {delete [] element;}
    // ADT 方法
    bool empty() const {return listSize == 0;}
    int size() const {return listSize};
    T& get(int theIndex) const; //按位置查找
    int indexOf(const T& theElement) const; //按值查找
    void erase(int theIndex);
    void insert(int theIndex, const T& theElement);
    protected:
    void checkIndex(int theIndex) const;
    T* element; // 存储线性表元素的一维数组
    int arrayLength; // 一维数组的容量
    int listSize; // 线性表的元素个数
}

顺序表的运算

➢ 插入元素运算

• void insert(int theIndex, const T& theElement);

➢ 删除元素运算

• void erase(int theIndex);

➢ 查找元素运算

• T& get(int theIndex) const; //按位置查找

• int indexOf(const T& theElement) const; //按值查找

在位置i 插入元素e:

template<class T>
void arrayList<T>::insert(int theIndex, const T &theElement) {
    // 在索引 theIndex处插入元素 theElement
    if (theIndex < 0 || theIndex > listSize) {
        // 无效索引
    }
    if(listSize == arrayLength) {
        // 数组空间已满,数组长度倍增。这不是基础内容
    }
    // 把元素向右移动一个位置
    copy_backward(element + theIndex, element + listSize, element + listSize + 1);
    element[theIndex] = theElement;
    listSize++;
}

删除位置theIndex的元素:

template<class T>
void arrayList<T>::erase(int theIndex) {
    // 删除其索引为theIndex的元素
    // 如果该元素不存在,则抛出异常 illegalIndex
    checkIndex(theIndex);
    // 有效索引,移动其索引大于 theIndex 的元素
    copy(element + theIndex +1, element + listSize, element + theIndex);
    element[--listSize].~T(); // 调用析构函数
}

插入和删除操作的比较

➢ 与顺序表的长度和位置相关

➢ 插入:

• 最坏:i=1,移动次数为n

• 最好: i=表长+1,移动次数为0

• 平均:等概率情况下,平均移动次数n/2

➢ 删除:

• 最坏:i=1,移动次数为n-1

• 最好: i=表长,移动次数为0

• 平均:等概率情况下,平均移动次数(n-1)/2

按位置查找:

template<class T>
T& arrayList<T>::get(int theIndex) const {
    // 返回元素 theElement 第一次出现时的索引
    // 若该元素不存在,则抛出异常
    checkIndex(theIndex);
    return element[theIndex];
}

按值查找:

template<class T>
int arrayList<T>::indexOf(const T &theElement) const {
    // 返回元素 theElement 第一次出现时的索引
    // 若该元素不存在,则返回 -1
    // 查找元素 theElement
    int theIndex = (int) (find(element, element + listSize, theElement) - element);
    // 确定元素 theElement 是否找到
    if (theIndex == listSize) {
        // 没有找到
        return -1;
    } else {
        return theIndex;
    }
}

顺序表实现的特点

➢ 优点

• 顺序表的结构简单

• 顺序表的存储效率高,是紧凑结构,无须为表示节点间的逻辑关系而增加额外的存

储空间

• 顺序表是一个随机存储结构(直接存取结构)

➢ 缺点

• 在顺序表中进行插入和删除操作时,需要移动数据元素,算法效率较低。

• 对长度变化较大的线性表,或者要预先分配较大空间或者要经常扩充线性表,给

操作带来不方便。

posted @ 2024-07-16 15:29  Michael_Yeung  阅读(240)  评论(0)    收藏  举报