线性表:线性表是由n个数据元素组成的一个有限序列,线性表中数据元素的个数n就是线性表的长度,n==0时称为空表。
特性:
1、第一个元素没有前驱
2、最后一个元素没有后继
3、其他元素都是首尾相接、有且只有一个前驱和后继
线性表的顺序表示方法:
在计算机中存放线性表的最简单的一种方法就是顺序存储。把线性表的节点按逻辑顺序依次存放在一组地址连续的存储单元中就构成了线性表的顺序存储,采用顺序存储结构的线性表叫做顺序表。特点有:
1、线性表中所有元素的存储空间是连续的
2、线性表中的逻辑顺序与物理顺序一致
3、数组中的每一个元素的位置可以用公式来计算如
Loc(ei) = Loc(e1)+(i-1)*k
针对数据结构有创建,销毁,输出和增删改查等功能,因此我们定义如下的模板类:
template<class T>
class LinearList{
public:
LinearList(int LLMaxSize);
~LinearList();
LinearList<T>& Insert(int k,const T& x);//const表示参数不能更改其值
bool IsEmpty() const;//const表示本函数的this指针为常量指针,不可更改其值
//const对象函数可以调用非const函数,非const函数不能调用const函数?
int GetLength() const;
bool GetData(int k,T& x);
bool ModifyData(int k,const T& x);
int Find(const T& x);
LinearList<T>& DeleteByIndex(int k,T& x);
LinearList<T>& DeleteByKey(const T& x,T& y);
void Output(ostream& out) const;
private:
int length;
int MaxSize;
T *element;
};
1、线性表的插入算法
a、插入元素之后,插入点之后的元素都需要右移
b、需要判断插入位置的合理性以及线性表是否已经满了
c、从最后一个元素开始,将每个元素向右移动一个位置,直到第i个位置空闲为止
d、在第i个位置放入新元素x
e、将线性表的长度+1
复杂度分析:插入操作主要是数据的移动,可用节点的移动次数来估计算法的时间复杂度。在当前长度为n的顺序表中插入一个元素,插入位置有n+1个,设pi是在第i个位置插入元素的概率,不失一般性的,设各个位置的插入概率相等,则pi=1/(n+1),在位置i插入一个元素,需移动n-(i-1)个元素,则在长度为n的线性表中插入一个元素,总的平均移动次数为:
E = 总加和|P(n-i+1) = n/2
以上式子表明,在顺序表中进行插入操作,平均要移动一半的节点,当表长较长时算法的效率很低。
2、线性表的删除操作
算法描述:
1、判断删除位置的合理性
2、从第i+1个元素开始,依次向后直到最后一个元素,将每个元素向前移动一个位置
3、将线性表的长度减1
其算法时间复杂度是(n-1)/2。
综上所诉:顺序表的插入和删除算法的时间复杂度都是O(n)。
顺序表基本操作的C++实现如下:
template<typename T>
LinearList<T>::LinearList(int LLMaxSize){
MaxSize=LLMaxSize;
element=new T[LLMaxSize];
length=0;
}
template<class T>
LinearList<T>::~LinearList(){
delete []element;
}
template<class T>
LinearList<T>& LinearList<T>::Insert(int k,const T& x){
if(k<1||k>length+1){
cout<<"元素下标越界,添加元素失败"<<endl;
}
else{
if(length==MaxSize){
cout<<"此表已满,无法添加新元素"<<endl;
}
else{
for(int i=length;i>k-1;i--){
element[i]=element[i-1];
}
element[k-1]=x;
length++;
}
}
return *this;
}
template<class T>
bool LinearList<T>::IsEmpty() const{
return length==0;
}
template<class T>
int LinearList<T>::GetLength() const{
return length;
}
template<class T>
bool LinearList<T>::GetData(int k,T& x){
if(k<1||k>length){
return false;
}
else{
x=element[k-1];
return true;
}
}
template<class T>
bool LinearList<T>::ModifyData(int k,const T& x){
if(k<1||k>length){
return false;
}
else{
element[k-1]=x;
return true;
}
}
template<class T>
int LinearList<T>::Find(const T& x){
for(int i=0;i<length;i++){
if(element[i]==x){
return i+1;
}
}
return 0;
}
template<class T>
LinearList<T>& LinearList<T>::DeleteByIndex(int k,T& x){
if(GetData(k,x)){
for(int i=k-1;i<length-1;i++){
element[i]=element[i+1];
}
length--;
}
else{
cout<<"元素下标越界,删除失败";
}
return *this;
}
template<class T>
LinearList<T>& LinearList<T>::DeleteByKey(const T& x,T& y){
int index=Find(x);
if(index!=0){
return DeleteByIndex(index,y);
}
else{
cout<<"没有此元素,删除失败";
return *this;
}
}
template<class T>
void LinearList<T>::Output(ostream& out) const{
for(int i=0;i<length;i++){
out<<element[i]<<endl;
}
}
template<class T>
ostream& operator<<(ostream& out,const LinearList<T>& x){
x.Output(out);
return out;
}
其中,最后的对“<<”的重载表示的就是运算符左边是输出流对象,右边是一个目标输出数组,对其作出重载后,当“<<”右边出现的是数组时,会调用Output()函数遍历输出数组元素。同时运算符重载的return out;是为了级联调用,即可以连续使用此功能,如果不返回此对象(即return out;),那么对运算符“<<”调用过一次之后就默认返回只能输出基本类型了,这是C++的基本语法,对ostream流的功能重载。
【写在最后】
顺序表具有简单、存储密度大、空间利用率高和存储效率高等优点,但在顺序表中进行插入删除时往往需要移动大量的元素,时间复杂度较高。同时由于顺序表的长度不好估计,往往需要为顺序表分配足够大的空间,造成空间浪费,因此对于元素变动频繁、长度变化大的线性表,不适宜采用顺序存储结构。