【数据结构】五分钟自测主干知识(二)

有关数组的基本工具类,看这个就行啦(含很多C语言基本操作)

书继上回,接下来的内容更加干货而精彩!


2.1线性表的基本概念

线性表(Linear List)是nn\geq 0)个数据元素的有限序列,这些数据元素同属于一个集合,zai这个序列中相邻的数据元素之间存在一种相对的位置关系,称为序偶关系。线性表通常记为:

\left ( a_1,a_2,...,a_{i-1},a_i,a_{i+1},...,a_{n-1},a_{n} \right )

i 正是标志数据元素相对位置的一个编号,称为位序

线性表中元素的个数n称为线性表的长度,当n=0时为空表

线性表的抽象数据类型定义如下:(这些也是初学者学C语言经常用到的)

ADT List{
数据对象:D={ a_i|a_i \in ElemSet,i=1,2,…,n,n≥0,ElemSet 为元素集合}
数据关系:R={ <a_{i-1},a_i>|a_{i-1},a_i\in D,i=2,...,n)
基本操作(P):
InitList(&L)
操作结果:创建一个空的线性表L。


DestroyList(&L)
操作结果:销毁线性表L。
参数说明:线性表L已存在。


ClearList(&L)
操作结果:将线性表L置空。
参数说明:线性表L已存在。


ListEmpty(L)
操作结果:检测线性表L是否为空,若线性表L为空则返回 TRUE;否则返回 FALSE。
参数说明:线性表L已存在。


ListLength(L)
操作结果:返回线性表L中的元素个数,即线性表长度。
参数说明:线性表L已存在。


GetElem(L,i,&e)
操作结果:将线性表L中位序为i的元素通过参数e 返回。
参数说明:线性表L已存在,且1≤i<Listlength(L)。


LocateItem(L,e)
操作结果:返回线性表上中首个e元素的位序。若不存在e则返回值为 0。
参数说明:线性表L已存在。


PriorElem(L,cur_e,&pre_e)
操作结果:若 cur_e非线性表上的第一个元素,则将 pre_e 赋值为 cur_e的直接前驱元素,同时返回
TRUE;否则返回 FALSE。
参数说明:线性表L已存在。


NextElem(L,cur_e,&next_e)
操作结果:若 cur_e 非线性表L的最后一个元素,则将 next_e 赋值为 cur_e 的直接后继元素,同时返
回TRUE;否则返回 FALSE。
参数说明:线性表L已存在。

ListInsert(&L,i,e)
操作结果:在线性表L中位序为i的元素前面插入元素 e,线性表长度增加 1.
参数说明:线性表L已存在,且 1≤ i ≤ListLength(L)+1。


ListDelete(&L, i,&e)
操作结果:删除线性表L中位序为i的元素,并将其值通过 e返回,线性表长度减少 1.
参数说明:线性表L已存在,且1≤ i ≤ListLength(L)。


ListTravers(L)
操作结果:按元素的位序依次输出线性表L中的所有元素。
参数说明:线性表L已存在。


}end ADT List 

很好用的工具类!
上述线性表 ADT 描述中的操作仅仅是一种抽象的描述,并没有涉及具体的存储结构和高级语言的实现,稍后我会在顺序表的地方来实现实现(如果仅仅做知识复习,可以略过)


2.2线性表的顺序表示(重点)

通俗点说,数组形式储存的(但线性表当前元素可增删,长度不定而数组不行,所以数组长度和线性表长度不是一个概念,线性表的长度总是应该小于数组长度)

由于数组在建立时并不能预知将来可能存储的元素是多少,所以在创建数组时一般只能根据估算设定数组的规模。

线性表顺序表示为:

#define InitSize 10
typedef int ElemType;//整型 
typedef struct {
	ElemType* elem; //顺序表元素类型 
	int MaxSize;
	int length;
}SqList;

1.顺序表的初始化

void InitList(SqList& L) {//初始化 
	L.elem = (ElemType*)malloc(InitSize * sizeof(ElemType));
	L.length = 0;
	L.MaxSize = InitSize;
}

2.顺序表的销毁操作

void DestroyList(SqList& L) {//销毁顺序表
	delete[] L.elem;
	L.length = 0;
	L.MaxSize = 0;
}

3.检测线性表空,满,获取长度

bool ListEmpty(SqList L) {
	return(L.length == 0);
}
bool ListFull(SqList L) {
	return(L.length == L.MaxSize);
}
int ListLength(SqList L) {
	return L.length;
}

4.查找和获取元素

int GetElem(SqList L, int i) {//按位查找 
	return L.elem[i - 1];
}
int LocateElem(SqList L, ElemType e) {//按值查找 
	for (int i = 0; i < L.length; i++) {
		if (L.elem[i] == e)
			return i + 1;
	}
	return 0;
}

5.插入元素

bool ListInsert(SqList& L, int i, ElemType e) {//插入 
	if (i<1 || i>L.length + 1) {
		printf("i值非法!\n");
		return false;
	}
	if (L.length >= L.MaxSize) {
		printf("插入失败\n");
		return false;
	}
	for (int j = L.length; j >= i; j--) {
		L.elem[j] = L.elem[j - 1];
	}
	L.elem[i - 1] = e;
	L.length++;
	printf("插入成功\n");
	return true;
}

我们的算法需要具有健壮性(详见上一讲),有必要进行错误处理,这里我们先用printf显示,后面我们会使用ErrorMsg方法,处理错误信息

void ErrorMsg(const char* a) {//简易版,可以试试
	printf(a);
}

6.增大表容量

第一种方法

void IncreaseSize(SqList& L, int len) {//第一种
	ElemType* p = L.elem;
	L.elem = (ElemType*)malloc((L.MaxSize + len) * sizeof(ElemType));
	for (int i = 0; i < L.length; i++) {
		L.elem[i] = p[i];
	}
	L.MaxSize = L.MaxSize + len;
	free(p);
}

 更推荐第二种方法

#define LIST_INC_SIZE 20
void Increment(SqList& L, int inc_size = LIST_INC_SIZE) {
	ElemType* a;
	a = new ElemType[L.MaxSize+inc_size];
	if (!a) { ErrorMsg("分配内存错误"); return; }
	for (int i = 0; i < L.length; i++) {
		a[i] = L.elem[i];
	}
	delete[] L.elem;
	L.elem = a;
	L.MaxSize += inc_size;
	
}

使用第二种方法,从而我们上面第五条的算法可以改进为(优化两处错误处理)

bool ListInsert(SqList& L, int i, ElemType e) {//插入 
	if (i<1 || i>L.length + 1) {
		ErrorMsg("i值非法\n");//还有这里,这样可以通过修改ErrorMsg这个方法来“重用”,管理更加灵活
		return false;
	}
	if (L.length == L.MaxSize) {//我改动的是这里
		Increment(L);//修改此处错误处理,显得更加合理
	}
	for (int j = L.length; j >= i; j--) {
		L.elem[j] = L.elem[j - 1];
	}
	L.elem[i - 1] = e;
	L.length++;
	printf("插入成功\n");
	return true;
}

【练习】:算算时间复杂度

顺序表的插入操作与插入点有关。长度为n的顺序表中,如果插入位置恰为n+1,那么元素移动次数为0;如果插入位置是1,那么元素移动次数为n。一般地,如果插入位置是i,则元素移动次数为n+1-i。

如果考虑在所有位置插入概率相同,那么每个位置插入概率都是1/(n+1),这样等概率情况下顺序表插入元素的平均移动次数是

E_{in}=\sum_{i=n+1}^{1}P_iC_i=\frac{1}{n+1}\sum_{i=n+1}^{1}(n+1-i)=\frac{1}{n+1} \frac{n\left ( n+1 \right )}{2}=\frac{n}{2}

由此看出顺序表插入元素的时间复杂度为O\left ( n \right )

7.删除元素

bool ListDelete(SqList& L, int i, ElemType& e) {//删除 
	if (i<1 || i>L.length)
	{
		ErrorMsg("删除失败\n");
		return false;
	}
	e = L.elem[i - 1];
	for (int j = i; j < L.length; j++) {
		L.elem[j - 1] = L.elem[j];
	}
	L.length--;
	printf("删除成功\n");
	return true;
}

【练习】:同理算算这个时间复杂度

略去同上分析

E_{del}=\sum_{i=1}^{n}P_iC_i=\frac{1}{n}\sum_{i=1}^{n}(n-i)=\frac{1}{n}\frac{n(n-1)}{2}=\frac{n-1}{2}

因此顺序表删除元素的时间复杂度也是O\left ( n \right )

综上可看出,顺序表的插入和删除平均都要移动约一半数量的元素。当顺序表长度特别大的时候,这个时间消耗不容忽视,这也是顺序存储的一大缺陷


【最后一练】利用两个顺序表La和Lb分别表示两个集合AB,求集合A=A\bigcup B

算法参考:

void Union(SqList& La, SqList& Lb) {
	for (int i = 0; i < Lb.length; i++) {
		int e = Lb.elem[i];
		int j = 0;
		while (j < La.length && La.elem[j] != e)++j;
		if (j == La.length) {
			La.elem[La.length] = e;
			La.length++;
		}
	}
	delete[] Lb.elem;
	Lb.length = 0; Lb.MaxSize = 0;
}

内容过多,分开一次讲,主打一个快速过目知识点,查漏补缺式复习

下一讲非常重要,也是难点,学完下一讲【线性表的链式表示】,链表不愁!

附上链接:【数据结构】五分钟自测主干知识(三)

http://t.csdnimg.cn/ZRJdMicon-default.png?t=N7T8http://t.csdnimg.cn/ZRJdM

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值