数据结构与算法3——线性表性质与顺序存储结构

这是本次学习的第一个数据结构:线性表。
首先通过图片感受一下,线性表的特征。
数据结构与算法3——线性表性质与顺序存储结构_第1张图片
——大家姑且可以把这幅图想象为老师带领同学们外出野营。因为咱们的队伍比较强大,要记住每一位同学并且区分还真是一件不容易的事。
——所以咱们想到了一个办法,让大家按照一个约定排成一队,以后大家要怎么记住自己的位置呢?没错,大家只需要记住自己前面的同学就可以了。
——老师也可以很容易的清点人数,万一有人走丢,也能在最快时间内知道是谁不见了,因为只需要问哪一位同学的“前驱”不见了即可。

一、线性表的性质

线性表,从刚才描述的例子中我们很容易感受到是像排队一样,具有线一样性质的结构。

如果是下面这样,我们就不能说是线性表了:
数据结构与算法3——线性表性质与顺序存储结构_第2张图片
线性表(List):由零个或者多个数组元素组成的有限序列。

——它是一个序列,有一个“先来后到”,不能没有顺序
——若存在多个元素,则第一个元素没有前驱,而最后一个元素没有后继,其他元素都有且只有一个前驱和后继
——另外,线性表强调是有限的

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

线性表的基本操作:

  • ADT 线性表(List)
  • Data
  • Operation
函数 作用
InitList(*L) 初始化操作,建立一个空的线性表L
ListEmpty(L) 判断线性表是否为空表,若线性表为空,返回ture,否则返回false
ClearList(*L) 将线性表清空
GetElem(L,i,*e) 将线性表L中的第i个位置元素值返回给e
LocateElem(L,e) 在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号;否则返回0表示失败
  • endADT

举例:

实现两个线性表A、B的并集操作,即要使得集合A=A∪B。
(说白了,就是把存在集合B中但不存在集合A中的元素插入到A中即可)
数据结构与算法3——线性表性质与顺序存储结构_第3张图片
综合分析,我们需要运用到几个基本的操作组合即可:

  • ListLength(L); //获取长度
  • GetElem(L,i,*e); //获取元素
  • LocateElem(L,e); //定位
  • ListInsert(*L,i,e); //插入

代码如下:

void unionL(List *La, List Lb)
{
	int La_len, Lb_len, i;
	
	ElemType e;
	La_len = ListLength(*La);
	Lb_len = ListLength(Lb);
	
	for(i=1; i<=Lb_len; i++)
	{
			GetElem(Lb, i, &e);
			if( !LocateElem(*La, e) )
			{
				ListInsert(La, ++La_len, e);
			}
	}
}

三、线性表的顺序存储结构

1.首先看一下线性表顺序存储的结构代码:

#define MAXSIZE 20
typedef int ElemType;

typedef struct
{
	ElemType data[MAXSIZE];
	int length;		// 线性表当前长度
}SqList;

由上面的代码可知,这里封装了一个结构体,事实上就是对数组进行封装,增加了当前长度的变量罢了。

总结一下,顺序存储结构封装需要三个属性:

  • 存储空间的起始位置,数组data,他的存储位置就是线性表存储空间的存储位置
  • 线性表的最大存储容量:数组的长度MAXSIZE
  • 线性表的当前长度:length

注意一下,数组的长度与线性表的当前长度需要区分一下,数组的长度是存放线性表的存储空间的总长度,一般初始化后不变。而线性表的当前长度是线性表中元素的个数,是会变化的。数组下标要从0开始,线性表从1开始。两者之间有点区别。

2.地址计算方法

对于第i个数据元素ai的存储位置可以由a1推算得出:LOC(ai) = LOC(a1) + (i-1)*c

结合下图理解:
在这里插入图片描述

四、顺序存储结构 插入操作

数据结构与算法3——线性表性质与顺序存储结构_第4张图片
数据结构与算法3——线性表性质与顺序存储结构_第5张图片
插入算法的思路:

  • 如果插入位置不合理,抛出异常;
  • 如果线性表长度大于等于数组长度,则抛出异常或者动态增加数组容量;
  • 从最后一个元素开始向前遍历到第 i 个位置,分别将他们都向后移动一个位置;
  • 将要插入元素填入位置 i 处;
  • 线性表长度+1;

实现代码:

int ListInsert(SqList *L, int i, ElemType e)
{
	int k;
	if( L->length == MAXSIZE ) // 顺序线性表已经满了
	{
		return 0;
	}
	if( i<1 || i > L->length+1 ) // 当i不在范围内时
	{
		return 0;
	}
	if( i <= L->length ) // 若插入数据位置不在表尾
	{
		for( k=L->length-1; k >= i-1; k-- )
		{
			/* 将第i个位置后面的元素都往后移一位 */
			L->data[K+1] = L->data[k];
		}
	} 
	L->data[i-1] = e; // 将新元素插入
	L->length++;

	return 1;
}

五、顺序存储结构 删除操作

删除算法的思路:

  • 如果删除位置不合理,抛出异常;
  • 取出删除的元素;
  • 从第i元素开始向后遍历到最后一个位置,分别将它们都向前移动一个位置;
  • 线性表长度-1;

实现代码:

int ListDelete(SqList *L, int i, ElemType *e)
{
	int k;
	if( L->length == 0 )
	{
		return 0;
	}
	if( i<1 || i > L->length )
	{
		return 0;
	}
	*e = L->data[i-1];
	if( i < L->length )
	{
		for( k=i; k < L->length; k++ )
		{
			/* 分别将后面的元素的值赋给前一个元素 */
			L->data[K-1] = L->data[k];
		}
	}
	L->length--;

	return 1;
}

六、插入和删除的时间复杂度

线性表的顺序存储结构,在存、读数据时,不管是在哪个位置,时间复杂度都是O(1)。而在插入和删除时,时间复杂度都是O(n)。
这就说明,它比较适合元素个数比较稳定,不经常插入和删除元素,而更多的操作是存储数据的应用。

七、顺序存储结构的优缺点

优点:

  • 无须为表示表中元素之间的逻辑关系而增加额外的存储空间。
  • 可以快速地存取表中任意位置的元素。

缺点:

  • 插入和删除操作需要移动大量元素。
  • 当线性表长度变化比较大时,难以确定存储空间的容量。
  • 容易造成存储空间的“碎片”。

你可能感兴趣的:(数据结构,线性表)