温故知新——数据结构之顺序表

一、基本概念

顺序表是线性表的顺序存储结构,使用地址连续的存储单元存放数据元素。

顺序表的符号化表示:

假设有n个(nN)类型相同的数据元素,其中的第i个元素(1≤in)表示为ai,则该顺序表记为L={a1, a2, …, ai, …, an}(1≤i≤n,nN)

顺序表的图形化表示:

温故知新——数据结构之顺序表

顺序表的抽象数据类型:

ADT 顺序表(SqList)
Data
    顺序表的数据元素集合为L={a1, a2, …, ai, …, an}(1≤i≤n,n∈N),每个元素的类型相同除第一个元素a1外,其他元素有且仅有一个前驱元素除最后一个元素an外,其他元素有且仅有一个后继元素
Operation
    InitList(*L);	# 顺序表初始化
    ListEmpty(L);	# 顺序表判空
    ClearList(L);	# 顺序表清空
    GetElem(L, i, *e);	# 按位查找
    LocateElem(L, e);	# 按值查找
    ListInsert(*L, i, e);	# 按位插入
    ListDelete(*L, i, *e);	# 按位删除
    ListLength(L);	# 返回顺序表的长度(元素个数)
EndADT

二、实现思路

基本思路很简单,就是利用数组和指针,声明顺序表的基本结构,然后对这个基本结构进行增删查改操作,注意增删查改操作是大部分数据结构的基本操作,大部分功能都是由这四个基本操作组成。最后再注意一些细节,例如基本的状态判断、边界判断、输入过滤等,这样做可以有效提高程序的鲁棒性。

将抽奖数据类型中的各个操作按照基本操作分个类:

增:InitList(*L)、ListInsert(*L, i, e)

删:ClearList(L)、ListDelete(*L, i, *e)

查:ListEmpty(L)、GetElem(L, i, *e)、LocateElem(L, e)、ListLength(L)

改:无,实际上“改”操作可以看作先增加新值再删除旧值,当然在顺序表里可以直接根据索引修改

三、代码实现

1. 基本数据结构

(1)状态常量

#define OK 1
#define ERROR 0
#define MAXSIZE 20	/*存储空间的初始分配量*/

(2)顺序表结构

typedef int Status;
typedef int ElemType;	/*ElemType类型可以根据实际情况而定*/
typedef struct {
	ElemType data[MAXSIZE];	/*数组存储数据元素,最大值为MAXSIZE*/
	int length;	/*记录顺序表的当前长度*/
}SqList;

2. 增删改查操作原型

void InitList(SqList *L);	/*初始化操作,建立一个空的顺序表L*/
bool ListEmpty(SqList L);	/*顺序表判空操作,若顺序表为空,返回true,否则返回false*/
void ClearList(SqList *L);	/*清空顺序表*/
Status ListInsert(SqList *L, int i, ElemType e);	/*在顺序表的第i个位置插入元素e*/
Status ListDelete(SqList *L, int i, ElemType *e);	/*删除顺序表L中第i个位置的元素,并用e返回其值*/
Status GetElem(SqList L, int i, ElemType *e);	/*将顺序表L中的第i个位置元素值返回给e*/
int LocateElem(SqList L, ElemType e);	/*在顺序表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中的序号表示成功,否则返回0表示失败*/
int ListLength(SqList L);	/*返回顺序表L的元素个数*/
void UnionList(SqList *La, SqList Lb);	/*合并两个顺序表,将所有在顺序表Lb中但不在La中的数据元素插入到La中*/

3. 增操作

(1)顺序表初始化

/*
	初始化操作,建立一个空的顺序表L,并将其所有数据元素初始化,将顺序表的数据长度置为0
	@param *L 指向顺序表的指针
	@return 无
*/
void InitList(SqList *L) {
	int i;

	for (i = 0; i < MAXSIZE; i++) {
		L->data[i] = 0;
	}

	L->length = 0;
}

(2)按位插入

/*
	插入元素,当顺序表存在时,在顺序表的第i个位置插入元素e,顺序表的数据长度加1
	@param *L 指向顺序表的指针
	@param i 插入的位置
	@param e 插入的元素
	@return 返回插入操作的状态,OK表示插入成功,ERROR表示插入出错
*/
Status ListInsert(SqList *L, int i, ElemType e) {
	int k;

	if (L->length == MAXSIZE) {	/*若顺序表已满,则返回错误信息*/
		return ERROR;
	}

	if (i < 1 || i > L->length + 1) {	/*当插入的位置不在范围内,返回错误信息*/
		return ERROR;
	}

	if (i <= L->length) {	/*当插入的位置不在顺序表表尾*/
		for (k = L->length - 1; k >= i - 1; k--) {	/*将要插入位置后的数据元素依次向后移动一位*/
			L->data[k + 1] = L->data[k];
		}
	}

	L->data[i - 1] = e;	/*插入新元素*/
	L->length++;

	return OK;
}

4. 删操作

(1)顺序表清空

/*
	清空顺序表,删除顺序表中所有数据,并将数据长度置为0
	@param *L 指向顺序表的指针
	@return 无
*/
void ClearList(SqList *L) {
	int i;

	for (i = 0; i < MAXSIZE; i++) {
		L->data[i] = 0;
	}

	L->length = 0;
}

(2)按位删除

/*
	若顺序表存在,删除顺序表L中第i个位置的元素,并用e返回其值,L的长度减1
	@param *L 指向顺序表的指针
	@param i 删除的位置
	@param *e 指向删除元素的指针
	@return 返回删除操作的状态,OK表示删除成功,ERROR表示删除出错
*/
Status ListDelete(SqList *L, int i, ElemType *e) {
	int k;
	if (L->length == 0) {	/*若顺序表为空,则返回错误信息*/
		return ERROR;
	}

	if (i < 1 || i > L->length) {
		return ERROR;	/*如果删除的位置不正确,返回错误信息*/
	}

	*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 OK;
}

5. 查操作

(1)顺序表判空

/*
	顺序表判空操作,若顺序表为空(即数据长度为0),返回true,否则返回false
	@param L 已初始化的顺序表
	@return 返回一个布尔值
*/
bool ListEmpty(SqList L) {
	if (L.length > 0) {
		return false;
	} else {
		return true;
	}
}

(2)按位查找

/*
	按位查找,将顺序表L中的第i个位置元素值返回给e
	@param L 已初始化的顺序表
	@param i 查找的位置
	@param *e 指向查找元素的指针
	@return 返回查找操作的状态,OK表示查找成功,ERROR表示查找出错
*/
Status GetElem(SqList L, int i, ElemType *e) {
	if (L.length == 0 || i < 1 || i > L.length) {	/*当顺序表为空,或查找位置超出范围时,返回错误信息*/
		return ERROR;
	}

	*e = L.data[i - 1];

	return OK;
}

(3)按值查找

/*
	在顺序表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中的序号表示成功,否则返回0表示失败
	@param L 已初始化的顺序表
	@param e 待查找的元素
	@return 查得元素的顺序表位置序号
*/
int LocateElem(SqList L, ElemType e) {
	if (L.length == 0) {	/*若顺序表为空,则返回错误信息*/
		return 0;
	}

	int k;
	for (k = 0; k < L.length; k++) {	/*遍历顺序表,对比每个元素值与给定元素值*/
		if (L.data[k] == e) {
			return k;
		}
	}

	return 0;
}

(4)获取顺序表长度(元素个数)

/*
	返回顺序表L的元素个数
	@param L 已初始化的顺序表
	@return 顺序表中当前元素个数
*/
int ListLength(SqList L) {
	return L.length;
}

6. 基本操作综合应用案例:合并顺序表

/*
	合并两个顺序表,将所有在顺序表Lb中但不在La中的数据元素插入到La中
	@param *La	指向链表a的指针
	@param Lb	已初始化的链表b
*/
void UnionList(SqList *La, SqList 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);	/*取出Lb中第i个数据元素赋值给e*/
		if (!LocateElem(*La, e)) {	/*La中不存在和e相同的数据元素时,将该数据元素插入La中*/
			ListInsert(La, ++La_len, e);
		}
	}
}

四、时间复杂度分析

增:如果在顺序表表头增加一个元素,那么首元素之后的所有元素都要向后挪一个位置,那么时间复杂度为O(n),如果在表尾增加一个元素,就没有需要挪动的元素,时间复杂度为O(1)。综合起来,平均时间复杂度还是O(n)。

删:如果在顺序表表头删除一个元素,那么首元素之后的所有元素都要向前挪一个位置,那么时间复杂度为O(n),如果在表尾删除一个元素,就没有需要挪动的元素,时间复杂度为O(1)。综合起来,平均时间复杂度还是O(n)。

查:按位查找可以直接根据索引找到对应的元素,所以时间复杂度为O(1);按值查找需要遍历顺序表,逐个比较顺序表中元素与给定元素是否相同,最好的情况是,在表头就找到了目标值,此时时间复杂度为O(1),最差的情况是,遍历到表尾最后一个元素才找到目标值,此时的时间复杂度为O(n),综合一下,平均时间复杂度还是O(n)。

五、顺序表的优缺点

优点:

(1)空间资源占用相对较少;

(2)可以进行随机存取

缺点:

(1)增删操作时间开销大

(2)顺序表的容量固定,不能动态拓展

(3)必须使用连续的地址存储数据元素,容易造成存储空间碎片


参考资料:

[1] 大话数据结构. 程杰.

[2] 数据结构(C语言版). 严蔚敏, 吴伟民.

上一篇:数据结构C++实现——线性表之顺序表(静态分配)


下一篇:luogu P4931 情侣?给我烧了!