四、线性表的定义、特点和类型定义及C++模板实现

简介: 四、线性表的定义、特点和类型定义及C++模板实现

1、线性表定义


线性表是具有相同特性的数据元素的一个有限序列。

8c487240581d4e99a0b43a2578c9ff64.png

线性表  n(n≥0)个数据元素(结点)  a1,a2,...,an组成的有限序列。


其中数据元素的个数  n定义为表的长度;


当  n==0时,称为空表;


将非空的线性表(a1,a2,...,an)


这里的数据元素 ai(1≤i≤n)只是一个抽象符号,其具体含义在不同的情况下可以不同。


211de3f4b41049f2a318595bac49a6ad.png


同一个线性表中的元素必定具有相同的特性,数据元素间的关系式线性关系。



2、线性表的逻辑特征



在非空的线性表,有且仅有一个开始结点 a1,它没有直接前趋,而仅有一个直接后继a2;


有且仅有一个终端结点  an,它没有直接后继,而仅有一个直接前趋an−1;


其余的内部结点ai(2≤i≤n−1)都有且仅有一个直接前趋 ai−1和一个直接后继 ai+1


线性表是一种典型的线性结构。


3、线性表的抽象数据类型



抽象数据类型线性表的定义如下:

ADT List

{数据对象: D = { a i ∣ a i 属于Element,(i=1,2,...,n,n≥0)}


数据关系:R={<ai−1,ai>∣ai−1,ai属于D,(i=2,3,...,n)}


基本操作:


InitList(&L);


Destroy(&L);


ListInsert(&L,i,e);//在位置i插入元素e


ListDelete(&L,i,&e);//在位置i删除元素e


DestroyList(&L);//销毁线性表


ClearList(&L);//将线性表L重置为空表


ListEmpty(L);//判断线性表L是否为空


ListLength(L);//返回线性表中数据元素的个数


GetElem(L,i,&e);//用e返回线性表中第i个元素的值


LocateElem(L,e,compare());//返回L中第1个与e满足compare()的数据元素的位序。若这样的元素不存在,则返回值为0


PriorElem(L,cur_e,&pre_e);//若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前趋,否则操作失败,pre_e无意义


NextElem(L, cur_e, &next_e);//若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无意义


ListTraverse(&L, visited());//遍历线性表中每一个元素,并做一个基本操作(这个基本操作不一定是做什么)visited()


… …

}ADT List




4、线性表的顺序表示和实现



线性表的顺序表示又称为顺序存储结构或者顺序映像,其中顺序存储的定义为:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。

59b9236c6c42495bb2f3b6dff9c1b59d.png


线性表的存储结构依次存储,地址连续,中间没有空出的存储单元,线性表顺序存储结构占用一片连续的存储空间,知道某个元素的存储位置就可以计算其他元素的存储位置。假设线性表的每个元素需要占 l个存储单元,则第  i个数据元素的存储位置和第1个元素的存储位置之间满足关系:

Loc(ai)=Loc(a1)+(i1)l


线性表中第一个元素的地址称为基地址,根据上式可知,线性表中查找某个元素的时间复杂度是) O(1)。


顺序表的顺序存储表示,顺序表元素的地址连续,依次存放,随机存取,类型相同,所以可以ongoing一个数组来表示顺序表;但是线性表的长度是可变的,但数组的长度不可以动态定义,为了解决这个问题,可以使用一个变量表示顺序表的长度属性,在长度不够用时,重新构建一个新的顺序表,将旧表的元素进行相应的拷贝。顺序表的模板定义如下所示:


#define LIST_INIT_SIZE 100  //线性表存储空间的初始分配量
typedef struct
{
  ElemType elem[LIST_INIT_SIZE ];//静态数组定义
  int length;           //当前长度
}Sqlist;
#define LIST_INIT_SIZE 100  //线性表存储空间的初始分配量
typedef struct
{
  ElemType *elem;     //动态数组定义
  int length;       //当前长度
}Sqlist;
L.elem = (ElemType*) malloc(sizeof(ElemType)*LIST_INIT_SIZE);
//L.elem = new ElemType(LIST_INIT_SIZE);
//基于C++模板的线性表定义
template<class Elem>
class MyVector
{
private:
  Elem *data;
  int length;
  int size;
public:
  MyVector() {};        //空构造函数
  MyVector(MyVector &L);    //赋值构造1
  MyVector(int num);      //赋值构造2
};


线性表的基本操作包括以下几种:

InitList(&L);
Destroy(&L);
ListInsert(&L,i,e);//在位置i插入元素e
ListDelete(&L,i,&e);//在位置i删除元素e
DestroyList(&L);//销毁线性表
ClearList(&L);//将线性表L重置为空表
ListEmpty(L);//判断线性表L是否为空
ListLength(L);//返回线性表中数据元素的个数
GetElem(L,i,&e);//用e返回线性表中第i个元素的值
LocateElem(L,e,compare());//返回L中第1个与e满足compare()的数据元素的位序。若这样的元素不存在,则返回值为0
PriorElem(L,cur_e,&pre_e);//若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前趋,否则操作失败,pre_e无意义
NextElem(L, cur_e, &next_e);//若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无意义
ListTraverse(&L, visited());//遍历线性表中每一个元素,并做一个基本操作(这个基本操作不一定是做什么)visited()
//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
//初始化线性表-基于C++实现
template<class Elem>
MyVector<Elem>::MyVector(MyVector &L)
{//赋值构造1
  try { 
    data = new Elem(L.size);
  } 
  catch (const std::bad_alloc& e) {
    throw std::exception("内存分配失败!");
  }
  memcpy(data,L.data,sizeof(Elem)*L.length);
  length = L.length;
  size = L.size;
}
template<class Elem>
MyVector<Elem>::MyVector(int num)
{//赋值构造2
  try {
    data = new Elem[MyMAXSIZE];
  }
  catch (const std::bad_alloc& e) {
    throw std::exception("内存分配失败!");
  }
  length = num;
  size = MyMAXSIZE;
}
//销毁线性表
template<class Elem>
MyVector<Elem>::~MyVector()
{//销毁线性表
  if(!data) delete data;
}
//清空线性表
template<class Elem>
void MyVector<Elem>::MyClear()
{//清空线性表
  length = 0;
}
//求线性表的长度
template<class Elem>
int MyVector<Elem>::GetLength()
{//获取线性表中元素的个数
  return length;
}
//判断线性表是否为空
template<class Elem>
bool MyVector<Elem>::IsEmpty()
{//获取线性表中元素的个数
  return length==0;
}
template<class Elem>
Elem& MyVector<Elem>::GetElem(int pos)
{//获取pos处的元素值
  try {
    if (pos < 0 || pos > length - 1)
      throw exception("索引超过数组长度,GetElem下标越界!");
  }
  catch (const exception &e) {
    cout << e.what() << endl;
    exit(-1);
  }
  return data[pos];
}
template<class Elem>
Elem& MyVector<Elem>::operator [](int n)
{//重载取元素运算符
  try {
    if(n < 0 || n > length - 1)
      throw exception("索引超过数组长度,GetElem下标越界!");
  }
  catch (const exception &e){
    cout << e.what() << endl;
    exit(-1);
  }
  return data[n];
}
template<class Elem>
int MyVector<Elem>::FindElem(Elem elem)
{//查找线性表中指定值Elem的位置,若查找到则返回位置下标,否则返回-1表示没有查找到或者线性表为空
  for (int i = 0; i < length; ++i)
  {
    if (data[i] == elem)
      return i;
  }
  return -1;
}

平均查找长度ASL(Average Search Length)


为确定记录在表中的位置,需要与给定的值进行比较的关键字的个数的期望值叫做查找算法的平均查找长度。对含有 n n n个记录的表,查找成功时,


ASL=i=1nPiCi


其中  Pi表示第i个记录被查找的概率;Ci表示第i个记录需要比较的次数。将上式展开即为:


ASL=P1+2P2+...+(n1)Pn1+nPn


若假设线性表中没有重复元素,,则每个记录被查找的概率相等: 1/n,则


ASL=1/n(1+2+...+n)=2nn(n+1)=2n+1


所以线性表的查找时间复杂度为O(n)

template<class Elem>//线性表的扩容操作
void MyVector<Elem>::_expandSize()
{//线性表的size变为两倍
  size *= 2;
  Elem *tempData = new Elem[size];
  memcpy(tempData,data,length*sizeof(Elem));
  delete data;//删除之前的旧表
  data = tempData;
}
template<class Elem>
void MyVector<Elem>::MyPush(Elem elem)
{//往线性表末尾插入一个元素
  if (length == size)
  {//线性表需要扩容
    _expandSize();
  }
  data[length++] = elem;
}
template<class Elem>
void MyVector<Elem>::MyInsert(int pos, Elem elem)
{
  if (length == size)
  {//线性表需要扩容
    _expandSize();
  }
  if (pos == length)
  {//尾部插入
    MyPush(elem);
    return;
  }
  else
  {//中间插入,将pos之后的值依次往后搬运
    for (int i = length - 1; i >= pos; --i)
      data[i + 1] = data[i];
    data[pos] = elem;
  }
  ++length;

插入操作的平均算法移动次数


Eins=n+11i=1n+1(ni+1)=n+11[(n+1)(n+1)[(n+1)+2n(n+1)]]=


n+112n(n+1)=2n


所以算法的时间复杂度为 O ( n )

template<class Elem>
void MyVector<Elem>::MyPopBack()
{//删除尾部元素
  if (length == 0) return;
  --length;
}
template<class Elem>
void MyVector<Elem>::MyDelete(int pos)
{//随机删除一个元素
  if (pos < 0 || pos > length - 1)
  {
    cout << "删除位置有误,删除操作失败!" << endl;
    throw exception();
  }
  if (pos == length - 1)
  {
    MyPopBack();
    return;
  }
  else
  {//删除中间元素
    for (int i = pos; i < length - 1; ++i)
      data[i] = data[i + 1];
  }
  --length;
}

删除操作的平均算法移动次数

Edel=n1i=1n(ni)=n12n(n1)=2n1



所以算法的时间复杂度为 O ( n )



5、线性表的优缺点


优点: 存储密度大,节点本身所占存储量/节点结构所占存储量=1;可以随机存取表中任意元素;


缺点: 在插入,删除某个元素时,需要移动大量的元素;某些申请的存储空间没有充分利用,浪费存储空间




相关文章
|
3月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
136 10
|
12天前
|
存储 算法 测试技术
【C++数据结构——线性表】求集合的并、交和差运算(头歌实践教学平台习题)【合集】
本任务要求编写程序求两个集合的并集、交集和差集。主要内容包括: 1. **单链表表示集合**:使用单链表存储集合元素,确保元素唯一且无序。 2. **求并集**:遍历两个集合,将所有不同元素加入新链表。 3. **求交集**:遍历集合A,检查元素是否在集合B中存在,若存在则加入结果链表。 4. **求差集**:遍历集合A,检查元素是否不在集合B中,若满足条件则加入结果链表。 通过C++代码实现上述操作,并提供测试用例验证结果。测试输入为两个集合的元素,输出为有序集合A、B,以及它们的并集、交集和差集。 示例测试输入: ``` a c e f a b d e h i ``` 预期输出:
36 7
|
12天前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
30 5
|
12天前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
27 5
|
2月前
|
安全 编译器 C++
【C++11】可变模板参数详解
本文详细介绍了C++11引入的可变模板参数,这是一种允许模板接受任意数量和类型参数的强大工具。文章从基本概念入手,讲解了可变模板参数的语法、参数包的展开方法,以及如何结合递归调用、折叠表达式等技术实现高效编程。通过具体示例,如打印任意数量参数、类型安全的`printf`替代方案等,展示了其在实际开发中的应用。最后,文章讨论了性能优化策略和常见问题,帮助读者更好地理解和使用这一高级C++特性。
85 4
|
2月前
|
算法 编译器 C++
【C++】模板详细讲解(含反向迭代器)
C++模板是泛型编程的核心,允许编写与类型无关的代码,提高代码复用性和灵活性。模板分为函数模板和类模板,支持隐式和显式实例化,以及特化(全特化和偏特化)。C++标准库广泛使用模板,如容器、迭代器、算法和函数对象等,以支持高效、灵活的编程。反向迭代器通过对正向迭代器的封装,实现了逆序遍历的功能。
39 3
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
199 4
|
2月前
|
编译器 C++
【c++】模板详解(1)
本文介绍了C++中的模板概念,包括函数模板和类模板,强调了模板作为泛型编程基础的重要性。函数模板允许创建类型无关的函数,类模板则能根据不同的类型生成不同的类。文章通过具体示例详细解释了模板的定义、实例化及匹配原则,帮助读者理解模板机制,为学习STL打下基础。
39 0
|
3月前
|
编译器 程序员 C++
【C++打怪之路Lv7】-- 模板初阶
【C++打怪之路Lv7】-- 模板初阶
28 1
|
3月前
|
存储 编译器 C++
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
【C++篇】引领C++模板初体验:泛型编程的力量与妙用
59 9