线性表
2.1 线性表的逻辑结构
2.1.1 线性表的概念
线性表是由类型相同的数据元素组成的有限序列,记作:
(a1, a2, …,ai,ai+1,…, an)
该序列中的所有结点具有相同的数据类型
注:
ai 是线性表中数据元素
ai称为ai+1的直接前驱,简称为前驱
ai+1称为ai的直接后继,简称为后继
a1称为线性表的第一个(首)结点
an称为线性表的最后一个(尾)结点。
n 指线性表长度
当n=0时,称为空表。
2.1.2 线性表的特征
除第一个元素外,其他每一个元素有一个且仅有一个 直接前驱。
除最后一个元素外,其他每一个元素有一个且仅有一个 直接后继。
线性表中的结点可以是单值元素(每个元素只有一个数据项)
26个英文字母组成的字母表: (A,B,C、…、Z)
线性表中的结点可以是记录型元素,每个元素含有多个数据项 ,每个项称为结点的一个域 。每个元素有一个可以唯一标识每个结点的数据项组,称为关键字
2.1.3 线性表基本操作
(1)int Length() const
初始条件:线性表已存在。
操作结果:返回线性表元素个数。
(2)bool Empty() const
初始条件:线性表已存在。
操作结果:如线性表为空,则返回true,否则返回false。
(3)void Clear()
初始条件:线性表已存在。
操作结果:清空线性表。
(4)void Traverse(void (*visit)(const ElemType &)) const
初始条件:线性表已存在。
操作结果:依次对线性表的每个元素调用函数(*visit)。
(5)StatusCode GetElem(int position, ElemType &e) const
初始条件:线性表已存在,1≤position≤Length()。
操作结果:用e返回第position个元素的值。
(6)StatusCode SetElem(int position,const ElemType &e)
初始条件:线性表已存在,1≤position≤Length()。
操作结果:将线性表的第position个位置的元素赋值为e。
(7)StatusCode Delete(int position, ElemType &e)
初始条件:线性表已存在,1≤position≤Length()。
操作结果:删除线性表的第position个位置的元素, 并用e返回其值,长度减1。
(8)StatusCode Insert(int position, const ElemType &e)
初始条件:线性表已存在,1≤position≤Length()+1。
操作结果:在线性表的第position个位置前插入元素e,长度加1。
2.2 线性表的存储结构
2.2.1 线性表的顺序存储结构
顺序表的定义:把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。
顺序表的特点:
1.所有元素的逻辑先后顺序与其物理存放顺序一致
2.数据元素之间的关系是以元素在计算机内“物理位置相邻”来体现
3.可利用一维数组描述存储结构,采用线性表的顺序存储方式
4.插入或删除操作需移动大量的数据元素
在具体的机器环境下:设线性表的每个元素需占用l个存储单元,以所占的第一个单元的存储地址作为数据元素的存储位置。
线性表中第i+1个数据元素的存储位置LOC(ai+1)和第i个数据元素的存储位置LOC(ai)之间满足下列关系:
LOC(ai+1)=LOC(ai)+l
线性表的第i个数据元素ai的存储位置为:
LOC(ai)=LOC(a1)+(i-1)*l
顺序表(SeqList)类模板的定义:
// 顺序表类模板
template <class ElemType>
class SqList
{
protected:
// 顺序表实现的数据成员:
int count; // 元素个数
int maxSize; // 顺序表最大元素个数
ElemType *elems; // 元素存储空间
// 辅助函数模板
bool Full() const; // 判断线性表是否已满
void Init(int size); // 初始化线性表
public:
// 抽象数据类型方法声明及重载编译系统默认方法声明:
SqList(int size = DEFAULT_SIZE);// 构造函数模板
virtual ~SqList(); // 析构函数模板
int Length() const; // 求线性表长度
bool Empty() const; // 判断线性表是否为空
void Clear(); // 将线性表清空
void Traverse(void (*visit)(const ElemType &)) const; // 遍历线性表
StatusCode GetElem(int position, ElemType &e) const; // 求指定位置的元素
StatusCode SetElem(int position, const ElemType &e); // 设置指定位置的元素值
StatusCode Delete(int position, ElemType &e); // 删除元素
StatusCode Insert(int position, const ElemType &e);
// 插入元素
SqList(const SqList<elemType> ©);
// 复制构造函数模板
SqList<elemType> &operator =(const
SqList<elemType> ©); // 重载赋值运算符
};
判定线性表是否已满
template <class ElemType>
bool SqList<ElemType>::Full() const
{
return count == maxSize;
}
初始化最大元素个数为size的空线性表
template <class ElemType>
void SqList<ElemType>::Init(int size)
{
maxSize = size; // 最大元素个数
if (elems != NULL) delete []elems;// 释放存储空间
elems = new ElemType[maxSize];// 分配存储空间
count = 0; // 空线性表元素个数为0
}
构造一个最大元素个数为size的空顺序表
template <class ElemType>
SqList<ElemType>::SqList(int size)
{
elems = NULL; // 未分配存储空间前,elems为空
Init(size); // 初始化线性表
}
销毁线性表
template <class ElemType>
SqList<ElemType>::~SqList()
{
delete []elems; // 释放存储空间
}
顺序表的插入
在线性表 L= (a1,…a i-1,ai, ai+1,…,an) 中的第i(1≦i≦n)个位置前插入一个新结点e,使其成为线性表:
L=(a1,…a i-1,e,ai,ai+1,…,an)
实现步骤:
1.线性表是否满?
2.插入位置是否全法?
3.将线性表L中的第i个至第n个结点后移一个位置。
4.将结点e插入到结点ai-1之后。
5.线性表长度加1。
操作代码:
template <class ElemType>
StatusCode SqList<ElemType>::Insert(int position,const ElemType &e)
{
int len = Length();
ElemType tmp;
if (Full())
{
return OVER_FLOW;
}
else if (position < 1 || position > len + 1)
{
return RANGE_ERROR;
}
else
{
count++;
for (int curPosition = len; curPosition >= position; curPosition--)
{
GetElem(curPosition, tmp);
SetElem(curPosition + 1, tmp);
}
SetElem(position, e);//将e赋值到position位置处
return SUCCESS; // 插入成功
}
}
插入操作时间复杂度分析
用结点的移动来估计算法的时间复杂度。
在表中第 i 个位置插入,从data[i-1] 到data [n-1] 成块后移,移动(n-1)-(i-1)+1 = n-i+1项。
设在线性表L中的第i个元素之前插入结点的概率为Pi,不失一般性,设各个位置插入是等概率,则Pi=1/(n+1)
总的平均移动次数:
Einsert=∑pi*(n-i+1) (1≦i≦n+1) =n/2
算法的平均时间复杂度为O(n)。
顺序表的删除
在线性表 L= (a1,…a i-1,ai, ai+1,…,an) 中删除结点ai(1≦i≦n),使其成为线性表:
L= (a1,…ai-1,ai+1,…,an)
实现步骤:
1.删除位置是否合法?
2.将线性表L中的第i+1个至第n个结点依此向前移动一个位置。
3.线性表长度减1
操作代码:
template <class ElemType>
StatusCode SqList<ElemType>::Delete(int position, ElemType &e)
{
int len = Length();
ElemType tmp;
if (position < 1 || position > len)
{
return RANGE_ERROR;
}
else
{
GetElem(position, e);
for (int curPosition = position + 1; curPosition <= len; curPosition++)
{
GetElem(curPosition, tmp);
SetElem(curPosition - 1, tmp);
}
count--; // 删除后元素个数将自减1
return SUCCESS;
}
}
删除操作时间复杂度分析
用结点的移动来估计算法的时间复杂度。
设在线性表L中删除第i个元素的概率为Pi,不失一般性,设删除各个位置是等概率,则Pi=1/n
删除第 i 个表项,需将第 i+1 项到第 n 项全部前移,需前移的项数为 n-(i+1)+1 = n-i
则总的平均移动次数: Edelete=∑pi*(n-i) (1≦i≦n)
Edelete=(n-1)/2
算法的平均时间复杂度为O(n)
2.2.2 线性表的链式存储结构
链表定义:用一组任意的存储单元存储线性表中的数据元素。用这种方法存储的线性表简称线性链表。存储链表中结点的一组任意的存储单元可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
2.2.2.1 单链表
在存储每个结点值的同时,还必须存储指示其直接后继结点的地址(或位置),称为指针(pointer)或链(link),这两部分组成了链表中的结点结构
链表是通过每个结点的指针域将线性表的n个结点按其逻辑次序链接在一起的。
每一个结只包含一个指针域的链表,称为单链表
data(元素的值) + next(后继)= 结点(表示数据元素)
用一组地址任意的存储单元存放线性表中的数据元素,以“结点的序列”表示线性表称作单链表
例:
常见的指针操作:
单链表的类定义:
使用面向对象方法,要把数据与操作一起定义和封装,用多个类表达一个单链表。
链表结点(ListNode)类
链表(List)类
单链表的相关类模板:
// 结点类模板
template <class ElemType>
struct Node
{
// 数据成员:
ElemType data; // 数据域
Node<ElemType> *next; // 指针域
// 构造函数模板:
Node(); // 无参数的构造函数模板
Node(ElemType item, Node<ElemType> *link = NULL);// 已知数据域和指针域建立结构
};
// 简单线性链表类模板
template <class ElemType>
class SimpleLinkList
{
protected:
// 链表实现的数据成员:
Node<ElemType> *head; // 头结点指针
// 辅助函数模板:
Node<ElemType> *GetElemPtr(int position) const; // 返回指向第position个结点的指针
void Init(); // 初始化线性表
public:
// 抽象数据类型方法声明及重载编译系统默认方法声明:
SimpleLinkList(); // 无参数构造函数模板
virtual ~SimpleLinkList(); // 析构函数模板
int Length() const; // 求线性表长度
bool Empty() const; // 判断线性表是否为空
void Clear(); // 将线性表清空
void Traverse(void (*visit)(const ElemType &)) const; // 遍历线性表
StatusCode GetElem(int position, ElemType &e) const; // 求指定位置的元素
StatusCode SetElem(int position, const ElemType &e); // 设置指定位置的元素值
StatusCode Delete(int position, ElemType &e);
// 删除元素
StatusCode Insert(int position, const ElemType &e);
// 插入元素
SimpleLinkList(const SimpleLinkList<ElemType> ©); // 复制构造函数模板
SimpleLinkList<ElemType> &operator =(const SimpleLinkList<ElemType> ©);
// 重载赋值运算符
};
获得第i个结点指针
template<class ElemType>
Node<ElemType> *SimpleLinkList<ElemType>::GetElemPtr(int position) const
{
Node<ElemType> *tmpPtr = head; // 用tmpPtr遍历线性表以查找第position个结点
int curPosition = 0; // tmpPtr所指结点的位置
while (tmpPtr != NULL && curPosition < position)
// 顺指针向后查找,直到tmpPtr指向第position个结点
{
tmpPtr = tmpPtr->next;
curPosition++;
}
if (tmpPtr != NULL && curPosition == position)
{ // 查找成功
return tmpPtr;
}
else
{ // 查找失败
return NULL;
}
}
注:时间复杂度: O(n)
单链表的插入
插入运算是将值为e的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间。
实现步骤:
1.找到ai-1所在的结点tmpPtr
2.生成一个数据域为e的新结点newPtr
newPtr = new Node<ElemType>(e, tmpPtr->next);
3.newPtr结点作为tmpPtr的直接后继结点
tmpPtr->next = newPtr;
操作代码:
template <class ElemType>StatusCode SimpleLinkList<ElemType>::Insert( int position, const ElemType &e)
{
if (position < 1 || position > Length() + 1)
{
return RANGE_ERROR;
}
else
{
Node<ElemType> *tmpPtr;
tmpPtr = GetElemPtr(position - 1);
Node<ElemType> *newPtr;
newPtr = new Node<ElemType>(e,tmpPtr->next);
tmpPtr->next = newPtr;
return SUCCESS;
}
}
插入操作时间复杂度分析
设链表的长度为n,合法的插入位置是1≦i≦n+1,算法的时间主要耗费移动指针p上,时间复杂度为O(n)。
单链表的删除
实现步骤:
1.找到ai-1的存储位置p
2.令p–>next指向ai的直接后继结点,即把ai从链上摘下
3.释放结点ai的空间
操作代码:
template <class ElemType>
StatusCode SimpleLinkList<ElemType>::Delete(int position, ElemType &e)
{
if (position < 1 || position > Length())
{
return RANGE_ERROR;
}
else
{
Node<ElemType> *tmpPtr;
tmpPtr = GetElemPtr(position - 1);
Node<ElemType> *nextPtr = tmpPtr->next;
tmpPtr->next = nextPtr->next;
e = nextPtr->data;
return SUCCESS;
}
}
删除操作时间复杂度分析
设单链表长度为n,则删去第i个结点仅当1≦i≦n时是合法的,算法的时间主要耗费移动指针p上,时间复杂度为O(n)。
2.2.2.2 循环链表
循环链表的概念
循环链表是一种头尾相接的链表,最后一个结点的指针域指向链表的头结点,整个链表的指针域链接成一个环。
特点:从循环链表的任意一个结点出发都可以找到链表中的其它结点,使得表处理更加方便灵活。
循环链表的相关类模板
// 简单循环链表类模板
template <class ElemType>
class SimpleCircLinkList
{
protected:
// 循环链表实现的数据成员:
Node<ElemType> *head; // 头结点指针
// 辅助函数模板:
Node<ElemType> *GetElemPtr(int position) const;
// 返回指向第position个结点的指针
void Init(); // 初始化线性表
public:
// 抽象数据类型方法声明及重载编译系统默认方法声明:
SimpleCircLinkList(); // 无参数的构造函数模板
virtual ~SimpleCircLinkList(); // 析构函数模板
int Length() const; // 求线性表长度
bool Empty() const; // 判断线性表是否为空
void Clear(); // 将线性表清空
void Traverse(void (*visit)(const ElemType &)) const; // 遍历线性表
StatusCode GetElem(int position, ElemType &e) const; // 求指定位置的元素
StatusCode SetElem(int position, const ElemType &e); // 设置指定位置的元素值
StatusCode Delete(int position, ElemType &e);
// 删除元素
StatusCode Insert(int position, const ElemType &e);
// 插入元素
SimpleCircLinkList(const SimpleCircLinkList<ElemType> ©);
// 复制构造函数模板
SimpleCircLinkList<ElemType> &operator =(const
SimpleCircLinkList<ElemType> ©);
// 重载赋值运算符
};
2.2.2.3 双向链表
双向链表(Double Linked List) :指的是构成链表的每个结点中设立两个指针域:一个指向其直接前趋的指针域prior,一个指向其直接后继的指针域next。这样形成的链表中有两个方向不同的链,故称为双向链表。
注:双向链表通常采用带表头结点的循环链表形式
双向链表相关类模板
// 双向链表结点类模板
template <class ElemType>
struct DblNode
{
// 数据成员:
ElemType data; // 数据域
DblNode<ElemType> *back; // 指向前驱的指针域
DblNode<ElemType> *next; // 指向后继的指针域
// 构造函数模板:
DblNode(); // 无数据的构造函数模板
DblNode(ElemType item,
DblNode<ElemType> *linkBack = NULL,
DblNode<ElemType> *linkNext = NULL);// 已知数据域和指针域建立结构
};
// 双向链表类模板
template <class ElemType>
class SimpleDblLinkList
{
protected:
// 循环链表实现的数据成员:
DblNode<ElemType> *head; // 头结点指针
// 辅助函数模板:
DblNode<ElemType> *GetElemPtr(int position) const; // 返回指向第position个结点的指针
void Init(); // 初始化线性表
public:
// 抽象数据类型方法声明及重载编译系统默认方法声明:
DblLinkList(); // 无参数的构造函数模板
virtual ~DblLinkList(); // 析构函数模板
int Length() const; // 求线性表长度
bool Empty() const; // 判断线性表是否为空
void Clear(); // 将线性表清空
void Traverse(void (*visit)(const ElemType &)) const; // 遍历线性表
int GetCurPosition() const; // 返回当前位置
StatusCode GetElem(int position, ElemType &e) const; // 求指定位置的元素
StatusCode SetElem(int position, const ElemType &e); // 设置指定位置的元素值
StatusCode Delete(int position, ElemType &e); // 删除元素
StatusCode Insert(int position, const ElemType &e); // 插入元素
DblLinkList(const DblLinkList<ElemType> ©); // 复制构造函数模板
DblLinkList<ElemType> &operator =(const DblLinkList<ElemType> ©); // 重载赋值运算符
};