算法分析与设计课程复习之堆和不相交集(并查集)数据结构
一、堆
1.定义:
一个(二叉)堆是一个几乎完全的二叉树,它的每个节点都满足堆的特性:如果v和p(v)分别是节点和它的父节点,那么存储在p(v)中的数据项键值不小于存储在v中数据项的键值。
空树、单节点树是堆
2.堆上的运算
-
Sift-up
假定对于某个i>1, H[i]变成了键值大于它父节点键值的元素,这样就违反了堆的特性,因此这种数据结构就不再是堆了。如要修复堆的特性,需要用称为Sift-up的运算把新的数据项上移到在二叉树中适合它的位置上
// heap[0]用来存储数组中堆数据的长度,堆数据heap[1]...heap[heapLength]
// 所以数组的实际长度是heapLength+1,我们只对从数组索引为1开始的heapLength个数据进行操作
void siftUp(int heap[], int index)
{
int heapLength = heap[0];
if (index < 1 || index > heapLength)
return;
bool done = false;
while(!done && index > 1)
{
if (heap[index] > heap[index / 2])
{
swap(heap[index],heap[index/2]);
}
else
{
done = true;
}
index /= 2;
}
}
-
Sift-down
假定对于i<=n/2,存储在H[i]中元素的键值变成小于H[2i]和H[2i+1]中的最大值(如果H[2i+1]存在的话)
// heap[0]用来存储数组中堆数据的长度,堆数据heap[1]...heap[heapLength]
// 所以数组的实际长度是heapLength+1,我们只对从数组索引为1开始的heapLength个数据进行操作
void siftDown(int heap[], int index)
{
int heapLength = heap[0];
if (index < 1 || index * 2 > heapLength)
return;
bool done = false;
while(!done && index * 2 <= heapLength)
{
index *= 2;
if (index + 1 <= heapLength && heap[index + 1] > heap[index])//判断左孩子大还是右孩子大
index += 1;
if (heap[index] > heap[index / 2])
{
swap(heap[index],heap[index/2]);
}
else
{
done = true;
}
}
}
-
Insert
为了把元素x插入到堆heap中,先将堆大小加1,然后将x添加到heap的末尾,再根据需要,把x上移,直到满足堆的特性。
// heap[0]用来存储数组中堆数据的长度,堆数据heap[1]...heap[heapLength]
// 所以数组的实际长度是heapLength+1,我们只对从数组索引为1开始的heapLength个数据进行操作
void Insert(int heap[], int x)
{
heap[0]+=1;
int heapLength = heap[0];
heap[heapLength]=x;
siftUp(heap,heapLength);
}
-
Delete
要从大小为n的堆heap中删除元素heap[i],可先用heap[n]替换heap[i],然后将堆栈大小减1,如果需要的话,根据H[i]的键值与存储在它父节点和子节点中元素键值的关系,对heap[i]做Sift-up或Sift-down运算,直到满足堆特性为止。
// heap[0]用来存储数组中堆数据的长度,堆数据heap[1]...heap[heapLength]
// 所以数组的实际长度是heapLength+1,我们只对从数组索引为1开始的heapLength个数据进行操作
void Delete(int heap[], int i)
{
heap[0]-=1;
int heapLength = heap[0];
int x = heap[i];
int y = heap[heapLength+1];
if(i==heapLength+1)return;//如果删除的是最后一个节点
heap[i]=y;//将最后一个节点的值赋给要删除的节点
if(y>x)//新节点的值更大
{
siftUp(heap,i);
}
else //新节点的值小于或等于
{
siftDown(heap,i);
}
}
-
DeleteMax
在一个非空堆H中删除并返回最大键值的数据项。直接完成这项运算要用到删除运算:只要返回根节点中的元素并将其从堆中删除
// heap[0]用来存储数组中堆数据的长度,堆数据heap[1]...heap[heapLength]
// 所以数组的实际长度是heapLength+1,我们只对从数组索引为1开始的heapLength个数据进行操作
int DeleteMax(int heap[])
{
int x = heap[1];
Delete(heap,1);
return x;
}
3.创建堆
方法1:
给出一个有n个元素的数组A[1…n] ,可以这样进行:从空的堆开始,不断插人每一个元素,直到A完全被转移到堆中为止。
时间复杂度为O(nlogn)
// heap[0]用来存储数组中堆数据的长度,堆数据heap[1]...heap[heapLength]
// 所以数组的实际长度是heapLength+1,我们只对从数组索引为1开始的heapLength个数据进行操作
void MakeHeap(int heap[],int A[])
{
for(int i=1;i<=n;i++)
{
Insert(heap,A[i]);
}
}
方法 2:
把一棵n个节点的几乎完全的二叉树转换成堆H[1…n]。从最后一个节点开始(编码为n的那一个)到根节点(编码为1的节点),逐个扫描所有的节点,根据需要,每一次将以当前节点为根节点的子树转换成堆。
时间复杂度为:O(n)
void MakeHeap(int A[])
{
for(int i=n/2;i>=1;i--)
{
siftDown(A,i);
}
}
4.堆排序
时间复杂度为:O(nlogn)
//A[0]用来存堆中数据的长度
void HeapSort(int A[])
{
MakeHeap(A);//将A数组建立成堆
for(int i=n;i--;i>=2)
{
//交换A[1]和A[i]
swap(A[1],A[i]);
A[0]--;//直到堆的大小变为1为止
siftDown(A[],1);//此时在A[1...j-1]中执行
}
}
5.最大堆和最小堆
最大堆: 存储在根节点以外的节点的键值,小于或等于存储在它父节点中的键值。
最小堆:存储在根节点以外的节点的键值,大于或等于存储在它父节点中的键值。
二、不相交集(并查集)
路径压缩下的并查集算法
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 普通合并a和b所在的两个集合:
p[find(a)] = find(b);
//按秩合并x和y所在的两个集合
int u = find(x),v = find(y);
if(rank(u)<=rank(v))
{
p[u]=v;
if(rank(u)=rank(v))rank(v)=rank(v)+1;
}
else p[v]=u;