线性表:线性表的顺序存储结构

线性表的逻辑结构

线性结构的基本特征

线性结构是一个数据元素的有序(次序)集

  1. 集合中必存在唯一的一个“第一元素”;
  2. 集合中必存在唯一的一个“最后元素”;
  3. 除最后元素外,均有唯一的后继
  4. 除第一元素外,均有唯一的前驱

线性表的定义

ADT List{

数据对象:
D={ai | ai | \in ElemSet, i=1,2,3,…,n, n \geq 0 }
{称n为线性表的表长;称n=0时的线性表为空表}

数据关系:
R1= { <ai-1 ,ai > | ai-1 ,ai \in D, i=2,…,n }
{ '<'和 '>'是有具体含义的,表示ai-1是ai的直接前驱,ai是ai-1的直接后继,设线性表为(a1, a2,…,ai,…, an),称i为ai在线性表中的位序。}

基本操作:

  • 结构初始化操作
  • 结构销毁操作
  • 引用型操作:使用这个数据结构
  • 加工型操作:对这个数据结构的参数或长度等进行改动

其它的复杂操作可以靠基本操作组合实现

}ADT List

容易混的概念:引用符号&,和引用型操作没有关系

  • 结构初始化操作
    InitList(&L)
    结果:构造一个空的线性表L
  • 结构销毁操作
    DestroyList(&L)
    初始条件:线性表L已存在
    结果:销毁线性表L
  • 引用型操作
    • 遍历线性表
      ListTraverse(L,visit())
      初始条件:线性表L已存在;visit()是一个访问函数。
      操作结果:依次对L中每个元素调用函数visit(),一旦visit()失败,则访问失败。
    • 判断线性表是否为空
      ListEmpty(L)
    • 获得线性表长度
      ListLength(L)
    • 获得指定位序的元素的前驱
      PriorElem(L,cur_e,&pre_e)
    • 获得指定位序的元素的后继
      NextElem(L,cur_e,&next_e)
    • 获得指定位序的元素
      GetElem(L,i,&e)
    • 获得符合条件的指定元素在线性表中的位序
      LocateElem(L,e,compare())
  • 加工型操作:
    • 将线性表清空
      ClearList(&L)
    • 对指定位序的元素重新赋值
      PutElem(&L,i,&e)
    • 将元素插入指定位序之后
      ElemInsert(&L,i,e)
    • 删除指定位序的元素
      DeleteElem(&L,i,&e)

遍历:按照一定的规则对数据结构中每个元素访问且只访问一次。

扫描二维码关注公众号,回复: 4940430 查看本文章
  • 注意规则
  • 访问且只访问一次
  • 访问是一个抽象的概念,对于不同的问题需要进行细化

利用上述定义的线性表类型实现其它的更复杂操作

例1.
假设:有两个集合A和B分别用两个线性表LA和LB表示,即:线性表中的数据元素即为集合中的成员。
现要求一个新的集合A=A \cup B。
上述问题可演绎为:
对线性表作如下操作:
扩大线性表LA,将存在于线性表LB中而不存在于线性表LA中的数据元素插入到线性表LA中去。
操作步骤:

  1. 从线性表LB中依次查看每个数据元素;
    e \gets GetElem(LB,i,&e)
  2. 依值在线性表LA中进行查找;
    LocateElem(LA,e,equal())
  3. 若不存在,则插入;
    ElemInsert(&LA,n+1,e)
    (n表示线性表LA当前长度,线性表位序从1开始)
void union(List& LA,List LB)
{
	LA_len=ListLength(LA);
	LB_len=ListLength(LB);
	for(i=1;i<=LB_len;i++)
	{
		GetElem(LB,i,&e);
		if(!LocateElem(LA,e,equal()))
			ListInsert(&LA,++LA_len,e);
	}//for
}//union 

例2.
归并两个“其数据元素按值非递减有序排列”的有序表LA和LB,求得有序表LC也具有同样特性。
注意:归并和求并集的区别,归并是把两个集合所有元素合在一起,没有舍弃任何元素。
设La=(a1,…,ai,…,an),
Lb=(b1,…,bj,…,bm),
求得
Lc=(c1,…,ck,…,cm+n)
且已由(a1,…,ai-1)和(b1,…,bj-1)归并得(c1,…,ck-1


c k = { a i , a i b j k = 1 , 2 , . . . , m + n b j , a i &gt; b j c_k = \left\{ \begin{array}{lr} a_i, a_i \leq b_j &amp; \\ &amp; k=1,2,...,m+n \\ b_j,a_i &gt; b_j &amp; \end{array} \right.
基本操作:

  1. 初始化LC为空表;
  2. 分别从LA和LB中取得当前元素ai和bj
  3. 若ai \leq bj,则将ai插入到LC中,否则将bj插入到LC中;
  4. 重复2和3,直至LA或LB中元素被取完为止;
  5. 将LA表或LB表剩余元素按顺序插入到LC中。
void MergeList(List La,List Lb,List& Lc)
{
	InitList(&Lc);
	La_len=ListLength(La);
	Lb_len=ListLength(Lb);
	i=1,j=1,k=0;
	while((i<=La_len)&&(j<=Lb_len))
	{
		GetElem(La,i,&a);
		GetElem(Lb,j,&b);
		if(a>=b) 
		{
			ElemInsert(&Lc,++k,b);
			j++;
		}
		else 
		{
			ElemInsert(&Lc,++k,a);
			i++;
		}
	}
	while(i<=La_len)
	{
		GetElem(La,i++,&a);
		ElemInsert(&Lc,++k,a);
	}
	while(j<=Lb_len)
	{
		GetElem(Lb,j++,&b);
		ElemInsert(&Lc,++k,b);
	}
}

线性表的顺序存储结构

1.顺序映像
以x的存储位置和y的存储位置之间某种关系表示逻辑关系<x,y>

最简单的一种顺序映像方法是:
令y的存储位置和x的存储位置相邻。

用一组地址连续的存储单元依次存放线性表中的数据元素。

a1 a2 ai-1 ai an

\qquad \uparrow
线性表的起始地址,称做线性表的基地址。
以“存储位置相邻”表示有序对<ai-1,ai>
即:LOC(ai)=LOC(ai-1)+C,C是一个元素所占存储量。
所有数据元素的存储位置均取决于第一个数据元素的存储位置。
LOC(ai)=LOC(a1)+(i-1)*C
LOC(a1)是基地址。

所以,顺序表的按序号查找是一种随机存取结构。
注意:

  • 存取结构和存储结构是不同的概念。
  • 存取结构是在一个数据结构上对查找操作的时间性能的一种描述。
  • 通常有两种存取结构:随机存取结构和顺序存取结构。
  • 随机存取结构是指在一个数据结构上进行查找的时间性能是O(1),即查找任意一个数据元素的时间是相等的,均为常数时间。
  • 顺序存储结构是指在一个数据结构上进行查找的时间性能是O(n),即查找一个数据元素的时间复杂度是线性的,与该元素在结构中的位置有关。

顺序映像的c语言描述

线性表的静态分配顺序存储结构:

#define LISTSIZE 100 //存储空间最大分配量
typedef struct{
	int elem[LISTSIZE];
	int length;//当前长度
}Sqlist;

线性表的静态分配顺序存储结构中,线性表的最多数据元素为LISTSIZE,元素数量不能随意增加,这是以数组方式描述线性表的缺点。

为了实现线性表最大存储数据元素可随意变化,可以使用一个动态分配的数组来取代上面的固定长度数组,如下描述。

线性表的动态分配顺序存储结构:

#define LIST_INIT_SIZE 100 //初始分配量
#define LISTINCREMENT 20 //分配增量
typedef struct{
	int* elem; //存储空间基址
	int length;//当前长度
	int listsize;//当前分配的存储容量
	//以sizeof(int)为单位
}Sqlist;

线性表的基本操作在顺序表中的实现

  • 静态分配线性表中InitList(&L);的实现:
    静态分配线性表的初始化在结构体中已经定义了,给length一个初值就可以了。
int InitList(Sqlist& L)
{
	L.length=0;
	return 0;
}
  • 动态分配线性表中InitList(&L);的实现:
int InitList(Sqlist& L)
{
	L.elem=(int *)malloc(LIST_INIT_SIZE*sizeof(int));
	if(!L.elem) return -1;//存储分配失败
	L.length=0;
	L.listsize=LIST_INIT_SIZE;//初始存储容量
	return 0;
}
  • LocateElem(L,x,compare());
    使用顺序查找
int LocateElem(Sqlist L,int val){
	int n = L.length;
	for (int i=0; i < n; i++){
		if (L.elem[i] == val) return i;
	}
	return -1;
}

设元素都是等概率的,顺序表按值查找的平均比较次数是n/2,时间复杂度O(n)。

  • 静态分配线性表的ListInsert(&L,i,e);实现
//在第i个元素之前插入指定元素
int ElemInsert(Sqlist& L, int i, int val)
{
	int n = L.length;
	if (n >= LISTSIZE) return -1;	//溢出判断
	if (i < 0 || i > n) return -1;	//插入位置合法性判断
	int* q = L.elem + i - 1;		//q指向插入位置
	int* p = L.elem + L.length - 1; //p指向线性表末尾元素
	for (; p >= q; p--) {
		*(p+1) = *p;				//插入位置及之后的元素右移
	}
	*p = val;						//插入val
	++L.length;						//线性表表长加1
	return 0;
}

移动元素的平均情况:
假设在第 i i 个元素之前插入的概率为 p i p_i ,则在长度为n的线性表中插入一个元素所需移动元素次数的期望值为:
E i s = i = 1 n + 1 p i ( n i + 1 ) E_{is}=\sum_{i=1}^{n+1}p_i (n-i+1)
若在线性表上任何一个位置进行插入的概率相等 p i = p = 1 n + 1 p_i=p=\frac1 {n+1} ,则移动元素的期望值为: n 2 \frac n 2
时间复杂度是 O ( n ) O(n)

  • DeleteElem(&L,i,&e);实现
//删除第i个元素
int ElemDelete(Sqlist& L, int i, int &val){
	int n = L.length;
	if (i < 0 || i >= n) return -1;		//删除位置合法性判断
	int* p = &(L.elem[i-1]);			//p指向被删除位置
	val = *p;							//返回删除元素
	int* q = L.elem+L.length - 1;		//q指向线性表表尾元素
	for (; p < q; p++) {
		*p = *(p + 1);					//被删除元素之后的元素左移
	}
	--L.length;							//线性表长度减1
	return 0;
}

移动元素的平均情况:
设删除第 i i 个元素的概率为 q i q_i ,则在长度为 n n 的线性表中删除一个元素所需移动元素次数的期望值为:
E d l = i = 1 n q i ( n i ) E_{dl}=\sum_{i=1}^{n}q_i (n-i)
若在线性表上任何一个位置进行插入的概率相等 q i = q = 1 n q_i=q=\frac1 n ,则移动元素的期望值为: n 1 2 \frac {n-1} 2
算法时间复杂度显然也是O(n)。

猜你喜欢

转载自blog.csdn.net/weixin_36049506/article/details/85884099