算法分析与设计课程复习之堆和不相交集(并查集)数据结构

  • Post author:
  • Post category:其他



算法分析与设计课程复习之堆和不相交集(并查集)数据结构


一、堆


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;



版权声明:本文为m0_53641110原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。