线性表(数组与链表)

本文介绍了数据对象和数据结构的概念,重点讨论了数组和链表两种数据结构,包括它们的特点和操作。数组在访问上的高效,链表在插入和删除上的优势被详细阐述。接着,文章提到了桶排序和基数排序中链表的应用,并深入探讨了在线等价类问题,即并查集。并查集是一种用于处理元素分组的数据结构,支持合并和查找操作,文中提供了基于数组和链表的并查集实现。
摘要由CSDN通过智能技术生成

数据对象和数据结构

数据对象是一组实例或值,比如布尔值、整型、自然数。

数据对象的一个实例要么是不可再分的原子,要么是另一个数据对象,称呼为元素。

数据结构是一个数据对象,同时这个对象的实例以及构成实例的元素都存在着联系,而且这个联系由函数来规定。

数组描述

STL提供了一个基于数组的类vector。数组的长度是按需要动态增加的,如果实施插入操作时vector已满,那么vector的容量将按照原容量的50%~100%增加。

数据结构最常见的四种操作,分别是:访问、插入、删除、查找。

数组的优势在于访问。

双指针

传送门

链式描述

单向链表

每一个节点都明确包含另一个相关节点的位置信息,这个信息被称为链或指针。

每个节点只有一个链被称为单向链表

删除和插入都需要直到目标位置的前一个节点,所以在forward_list中的这两个操作是insert_after(p,t)erase_after(p)

头节点指的是在链表前面增加一个节点。有了这个节点,空表就不做特殊情况来处理了。这样,每个链表都至少包括了一个节点。

除了单向链表外还有单向循环链表、双向链表与双向循环链表。

链表的优势在于插入和删除。

链表的访问比较复杂,所以没有和数组一样的索引方式,只能通过迭代器或者front、back方法得到元素。

应用

桶排序和基数排序

桶排序和基数排序中的桶的大小未知,所以用链表的话合适一些(虽然vector实现也很方便)。

并查集

等价类

假定一个具有n个元素的集合U=1,2,…,n和一个具有r个关系的集合 R = ( i 1 , j 1 ) , ( i 2 , j 2 ) , . . . , ( i r , j r ) R=(i_1,j_1),(i_2,j_2),...,(i_r,j_r) R=(i1,j1),(i2,j2),...,(ir,jr).

关系R是一个等价关系,当且仅当它具有这些性质:

  1. 自反性:对于所有的 a ∈ U a\in U aU,有 ( a , a ) ∈ R (a,a)\in R (a,a)R
  2. 对称性: ( a , b ) ∈ R (a,b)\in R (a,b)R,当且仅当 ( b , a ) ∈ R (b,a)\in R (b,a)R
  3. 传递性:若 ( a , b ) ∈ R (a,b)\in R (a,b)R ( b , c ) ∈ R (b,c)\in R (b,c)R,则有 ( a , c ) ∈ R (a,c)\in R (a,c)R

所谓等价类,指的是相互等价的元素的最大集合。等价关系把集合U划分为互不相交的等价类。

在离线等价类问题中,已知n和R,确定所有的等价类。

在在线等价类问题中,初始时有n个元素,每个元素都属于一个独立的等价类。需要执行以下操作:

  1. combine(a,b)把包含a和b的两个等价类合并成一个等价类/把两个不想交的集合合并为一个集合;
  2. find(theElement),确定元素theElement在哪一个类,目的是对给定的两个元素,确定是否属于同一个类。。它对同一个类的元素返回相同的结果。

本节主要关注在线等价类的问题,这类问题又称为并查集问题。

并查集

并查集(union-find)是一种数据结构,用于解决元素分组问题,用于管理一系列不相交的集合。支持上述提到的两个操作combine/union和find。

// 基于数组的并查集实现
int* equivClass, // 等价类数组
	n;			 // 元素个数

void initialize(int numberOfElements) {
	// 数组的索引是成员的id号,最开始每个成员都自成一个等价类
	// 数组中的元素表示该成员所属类的代表成员
	n = numberOfElements;
	equivClass = new int[n+1]; // 个人感觉没什么意义,大概是为了和链表表示保持统一
	for (int e = 1; e <= n; e++)
		equivClass[e] = e;
}

void unite(int classA, int classB) {
	// 合并类classA和classB
	// 假设类classA != classB
	// 进行循环,对每一个属于classB的成员改变其类为classA
	for (int k = 1; k <= n; k++)
		if (equivClass[k] == classB)
			equivClass[k] = classA;
}

int find(int theElement) {
	// 查找具有成员theElement的类
	return equivClass[theElement];

接下来我们介绍用链表表示的并查集。

优势在于如果一个等价类对应一个链表,那么合并操作的时间复杂度就可以降低。因为在一个等价类中,可以沿着链表的指针找到所有的元素,而不必检查所有equivClass的值。

// 链表实现
struct equivNode {
	int equivClass, // 元素类标识符
		size,		// 类的元素个数
		next;		// 类中指向下一个元素的指针
};

equivNode* node;
int n;

void initialize(int numberOfElements) {
	// 用每个类的一个元素,初始化numberOfElements
	n = numberOfElements;
	node = new equivNode[n+1]; // 需要多一个位置来表示尾节点
	for (int e = 1; e <= n; e++) {
		node[e].equivClass = e;
		node[e].next = 0;
		node[e].size = 1;
	}
}

void swap(int classA, int classB) {
	equivNode temp = node[classA];
	node[classA] = node[classB];
	node[classB] = temp;
}

void unite(int classA, int classB) {
	// 使classA成为较小的类
	if (node[classA].size > node[classB].size)
		swap(classA, classB);
	// 改变较小类中所有元素的equivClass值
	int k;
	for (k = classA; node[k].next != 0; k = node[k].next)
		node[k].equivClass = classB;
	node[k].equivClass = classB;

	// 在链表classB的首元素之后插入链表classA,就是插入在首元素之后,第二个元素之前
	// 修改新链表的大小
	node[classB].size += node[classA].size;
	node[k].next = node[classB].next;
	node[classB].next = classA;
}

int find(int theElement) {
	return node[theElement].equivClass;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

右边是我女神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值