线性表的两种实现方式
线性表是由同类型数据元素构成的有序序列的线性结构。
包含的基本操作主要有:初始化、查找、插入、删除。
两种实现方式:顺序表和链表,在实现基本操作时各自的关键点如下。
顺序表
利用数组的连续存储空间顺序存放线性表的个元素。
由此说明顺序表的元素都是一个一个挨在一起的,那么如果操作是在当前数组元素序列外插入元素,例如与数组最后一个元素隔几个再插入,这是不允许的,所以其删除给出的位置也不会超过表长。
定义顺序表结构体: 需要MAXSIZE大小的数组——存放数据 需要指示数组最后一个数据位置的标志——判断插入、删除位置是否合法,以及空满。 表有两种使用方式:结构体变量、结构体指针。
#define MAXSIZE 10
template <typename ElementType>
typedef int Position;
typedef struct LNode *List;
struct LNode{
ElementType data[MAXSIZE];
Position last;
};
初始化: 结构体变量 struct LNode L; 使用方式:L.data[i] = 10;L.last = 10; 结构体指针如下,使用方式L->data[i] = 10;
~
将L->last赋值为-1,表示此时没有元素,同时需要注意表的长度为L->last+1。
List MakeEmpty()
{
List L = (List)malloc(sizeof(struct LNode));
L->last = -1;
return L;
}
查找: 根据给出的元素值返回元素所在位置。 对于顺序表来说,给出数组元素索引,可以直接得到元素值,操作简单。 遍历数组,与 x
比较,相等即返回元素位置,否则返回error。
#define ERROR -1;
Position Find(List L,ElementType x)
{
Position i = 0;
for(;i<=L->last;i++)
{
if(L->data[i]==x)
{
return i;
}
}
return ERROR;
}
插入: 在给出的位置上插入一个元素,并返回插入操作结果(bool)。
~
首先,插入位置是在哪?为什么这么问,是因为如果给出的位置是 “-1” 呢?(这里的实现是数组索引从0开始)或者超出表长呢?(表长是last+1,就是在表最后一个元素后面插入一个元素)这都是不可以的,需要判断!
~
对于顺序表的插入,在插入之前需要将给定位置腾出来,所以需要将给定位置后面的元素都向后移一位,这里就有个疑问了——还有位置给我腾吗?需要判断是否满!
~
方法:last++增加一个位置,从后往前一个一个移,直到下一个位置是给定位置,直接赋值。
bool insert(List L,ElementType x,Position p)
{
if(L->last == MAXSIZE)
{
cout<<"表满"<<endl;
return false;
}
if(p<0||p>L->last+1)
{
cout<<"不能插入在该位置"<<endl;
return false;
}
L->last++;
Position i = L->last;
for(;i>p;i--)
{
L->data[i] = L->data[i-1];
}
L->data[i] = x;
return true;
}
删除: 删除给定位置的元素,并返回操作结果(bool)
~
表中是否有元素可以被删除?即判断表是否为空。
~
删除位置是否已经存储数据?即给定位置要在[0,last]范围中。
~
方法:从给定位置开始,将其后面的元素一个一个往前移,最后last–,实时更新表长度。
bool delete(List L,Position p)
{
if(L->last == -1)
{
cout<<"表空"<<endl;
return false;
}
if(p<0||p>L->last)
{
cout<<"该位置不存在元素"<<endl;
return false;
}
Position i = p;
for(;i<L->last;i++)
{
L->data[i] = L->data[i+1];
}
L->last--;
return true;
}
链表
不要求逻辑上相邻的两个元素物理上也相邻。通过“链”建立起数据元素之间的逻辑关系。因此插入、删除不需要移动数据元素,只需要修改“链”。
定义链结构体: 其实就是定义每个结点的结构,包括存储的数据和指向下个结点或NULL的“链”。
typedef struct LNode *PtrtoLNode;
template <typename E>
typedef PtrtoLNode List;
struct LNode{
E data;
List next;
};
初始化: 根据传入的值定义头结点,头结点指向NULL。
List MakeEmpty(E x)
{
List L = (List)malloc(sizeof(struct LNode));
L->data = x;
L->next = NULL;
return L;
}
查找:
- 根据给出的元素值返回存储该元素的结点。
- 根据给出的序号返回该位置的结点。
~
对于链表来说,查找是很不方便的,无论哪种查找方式,都需要遍历、比较来获取最后的结果。
#define ERROR NULL
//给出元素值
List Find(List L,E x)
{
/*直接赋值时,不需要申请空间 */
// List p = (List)malloc(sizeof(List)); //需要释放空间吗?要如何释放呢?
// p = L;
List p = L;
while(p&&p->data!=x)
{
p = p->next;
}
return p;
}
//给出序号
List FindByIndex(List L,int x)
{
List p = L;
int i = 1;
while(i<x&&p)
{
p = p->next;
i++;
}
return p;
}
插入: 在给出的位置插入一个结点,并返回头结点。
~
因为可能在表头插入,那么会改变头结点,所以为了避免失去头结点而无法获取其他节点,需要返回头结点(无论是否修改)。
List Insert(List L,E x,int i)
{
if(i==1) //表头插入
{
List k = (List)malloc(sizeof(struct LNode));
k->data = x;
k->next = L;
return k;
}
else
{
List p = FindByIndex(L,i-1);
if(p == NULL)
{
cout<<"插入位置参数有误"<<endl;
}
else
{
List k = (List)malloc(sizeof(struct LNode));
k->data = x;
k->next = p->next;
p->next = k;
}
return L;
}
}
删除: 删除给定位置的元素,并返回头结点
~
当头结点为空时,直接返回NULL。
~
同样可能删除头结点,所以要返回头结点。
~
链表的删除核心操作是需要将位置 i-1 的结点的“链”修改正确,并释放位置 i 的结点的空间。尤其要注意结点(i-1)和结点(i)为NULL的情况。
List Delete(List L,int i)
{
if(L == NULL) return NULL;
if(i == 1) //删除头结点
{
List S = L;
L = L->next;
free(S);
}
else
{
List p = FindByIndex(L,i-1);
if( p == NULL || p->next == NULL )
{
cout<<"删除结点不存在"<<endl;
}
else
{
List s = p->next;
p->next = s->next;
free(s);
}
}
return L;
}
表长: 直接遍历即可
int Length(List L)
{
int len = 0;
List p = L;
while(p)
{
len++;
L = L->next;
}
return len;
}