并查集和哈夫曼树

1.并查集

1.1需求分析

假设现在有这样一个需求,如下图的每一个点代表一个村庄,每一条线就代表一条路,所以有些村庄之间有连接的路,有
些村庄没有连接的路,但是有间接连接的路,根据上面的条件,能设计出一个数据结构,能快速执行下面2个操作:

  • 查询两个村庄之间是否有连接的路
  • 连接两个村庄
    在这里插入图片描述
    如何判断图中两点是否有连接
    在这里插入图片描述

1.2并查集概念

并查集(英文:Disjoint-set data structure,直译为不交集数据结构)是一种数据结构,用于处理一些不交集(Disjoint sets,一系列没有重复元素的集合)的合并查询问题。并查集支持如下操作:

  • 查询(Find):查询某个元素属于哪个集合,通常是返回集合内的一个“代表元素”。这个操作是为了判断两个元素是 否在同一个集合之中。
  • 合并(Union):将两个集合合并为一个。
  • 添加:添加一个新集合,其中有一个新元素。添加操作不如查询和合并操作重要,常常被忽略。
    由于支持查询和合并这两种操作,并查集在英文中也被称为联合-查找数据结构(Union-find data structure)或者
    合并-查找集合(Merge-find set)。

1.3并查集算法介绍

  • 并查集有两种常见实现思路
  • a.Quick Find
  • 查找效率:O(1)
  • 合并效率:O(N)
  • b.Quick Union
  • 查找效率:O(logN)
  • 合并效率:O(logN)

1.3.1QuickFind的思路

  • 核心是将所有元素进行ID分组管理,每个元素对应一个ID号
  • find的时候,只需要返回这个元素对应的分组ID
  • 合并的时候Union(a,b)时候,将是属于a分组的元素,都改为b分组的ID号

1.3.2QuickUnion的思路

  • 将集合分为根结点和父节点的思想,所有节点保存他的父节点信息,当发现某个节点的父节点就是他自己的时候,这个节点就是根结点
  • find操作就是找到这个元素的根结点,判断两个元素的根结点是不是一致,来判断是否连通。
  • 合并操作的时候,Union(a, b)的时候,不是合并a和b,而是将a的根结点和b的根节点进行合并

1.3.3基于size的算法改进

  • 在Union过程中,可能会出现不平衡的情况,甚至退化成为链表,Union(1,3)

在这里插入图片描述

  • 将元素少的树,嫁接到元素多的树

1.3.4 基于rank的算法改进

在这里插入图片描述

  • 矮的树,嫁接到高的树

#ifndef DATA_STRUCTURE_BASE_H
#define DATA_STRUCTURE_BASE_H
typedef int Element;
#endif //DATA_STRUCTURE_BASE_H


#ifndef DATA_STRUCTURE_QUICKFINDSET_H
#define DATA_STRUCTURE_QUICKFINDSET_H
#include "base.h"
/* 并查集的QuickFind算法下的结构定义
 * 每一个元素都存有一个编号,只要编号一样,就代表一个集团的人
 * 合并时,就是把另外一个团队的人的老大改为这个团队的老大
 * */
typedef struct{
    Element *data;				// 存放的具体数据,利用索引来建立数据的关系
    int n;						// 并查集的元素个数
    int *groupID;				// 每个元素的编号
}QuickFindSet;
//产生n个元素的并查集
QuickFindSet *createQuickFindSet(int n);
//释放并查集
void releaseQuickFindSet(QuickFindSet *setQF);
// 初始化并查集,设置元素值和对应的编号
void initQuickFindSet(QuickFindSet *setQF, const Element *data, int n);
// 查:判断两个元素是不是同一个组ID
int isSameQF(QuickFindSet *setQF, Element a, Element b);
// 并:合并两个元素
void unionQF(QuickFindSet *setQF, Element a, Element b);
#endif //DATA_STRUCTURE_QUICKFINDSET_H


#include "quickFindSet.h"
#include<iostream>
//产生n个元素的并查集
QuickFindSet *createQuickFindSet(int n){
    QuickFindSet* setQF=new QuickFindSet ;
    setQF->data=new Element[n] ;
    setQF->groupID=new int[n];
    setQF->n=n;
    return setQF;
}
//释放并查集
void releaseQuickFindSet(QuickFindSet *setQF){
    if(setQF){
        if(setQF->groupID){
            delete setQF->groupID;
        }
        if(setQF->data){
            delete setQF->data;
        }
        delete setQF;
    }
}
// 初始化并查集,设置元素值和对应的编号
void initQuickFindSet(QuickFindSet *setQF, const Element *data, int n){
    for(int i=0;i<n;i++){
        setQF->data[i]=data[i];     //元素赋值
        setQF->groupID[i]=i;        //第i个元素的组ID就是i,自己管理自己
    }
}
static int findIndex(QuickFindSet *setQF, Element a){
    for(int i=0;i<setQF->n;i++){
        if(setQF->data[i]==a){
            return i;
        }
    }
    return -1;
}
// 查:判断两个元素是不是同一个组ID
int isSameQF(QuickFindSet *setQF, Element a, Element b){
    // 把元素转换为并查集中对应的索引号
    int aIndex= findIndex(setQF,a);
    int bIndex= findIndex(setQF,b);
    if(aIndex==-1||bIndex==-1){         // 并查集中没有a或b,返回假
        return 0;
    }
    return aIndex==bIndex;
}
// 并:合并两个元素
void unionQF(QuickFindSet *setQF, Element a, Element b){
    int aIndex= findIndex(setQF,a);
    int bIndex= findIndex(setQF,b);
    int bGrapId=setQF->groupID[bIndex];         // 备份b的老大,后面就判断所有元素是不是老大是b
    for(int i=0;i<setQF->n;i++){
        if(setQF->groupID[i]==bGrapId){
            setQF->groupID[i]=setQF->groupID[aIndex];   // b的组ID,替换成a的组ID
        }
    }
}


#ifndef DATA_STRUCTURE_QUICKUNIONSET_H
#define DATA_STRUCTURE_QUICKUNIONSET_H
/* QuickUnion方法
 * 每个元素只保存他的父元素的编号,通过不断找父元素,最终找到根编号
 * find : 两个元素的根一样
 * union : a元素的根 和 b元素的根 进行合并
 * */
#include "base.h"

typedef struct {
    Element *data;
    int n;
    int *parent;			// 每个元素只知道他的父节点,通过父节点不断去找根节点
    int *size;				// 根节点位置存放这个集合的个数
}QuickUnionSet;
//产生n个元素的并查集
QuickUnionSet *createQuickUnionSet(int n);
//释放并查集
void releaseQuickUnionSet(QuickUnionSet *setQU);
//初始化并查集
void initQuickUnionSet(QuickUnionSet *setQU, const Element *data, int n);
// 查:判断两个元素是不是同一个组ID
int isSameQU(QuickUnionSet *setQU, Element a, Element b);
// 并:合并两个元素
void unionQU(QuickUnionSet *setQU, Element a, Element b);
#endif //DATA_STRUCTURE_QUICKUNIONSET_H


#include "quickUnionSet.h"
#include<iostream>
//产生n个元素的并查集
QuickUnionSet *createQuickUnionSet(int n){
    QuickUnionSet *setQU=new QuickUnionSet ;
    setQU->data=new Element[n];
    setQU->parent=new int[n];
    setQU->size=new int[n];
    setQU->n=n;
    return setQU;
}
//释放并查集
void releaseQuickUnionSet(QuickUnionSet *setQU){
    if(setQU){
        if(setQU->size){
            delete setQU->size;
        }
        if(setQU->parent){
            delete setQU->parent;
        }
        if(setQU->data){
            delete setQU->data;
        }
        delete setQU;
    }
}
//初始化并查集
void initQuickUnionSet(QuickUnionSet *setQU, const Element *data, int n){
    for(int i=0;i<setQU->n;i++){
        setQU->data[i]=data[i];
        setQU->parent[i]=i;
        setQU->size[i]=1;
    }
}
static int findIndex(QuickUnionSet *setQF, Element a){
    for(int i=0;i<setQF->n;i++){
        if(setQF->data[i]==a){
            return i;
        }
    }
    return -1;
}
static int findRootIndex(QuickUnionSet *setQU, Element a){
    // 找e的父亲,再找这个父亲的父亲,直到发现父亲的父亲是自己,那就是根了
    int curIndex= findIndex(setQU,a);       // 如果为-1 补
    // 向上遍历
    while(setQU->parent[curIndex]!=curIndex){
        curIndex = setQU->parent[curIndex];			// 将父节点ID作为下一个寻找的ID号
    }
    return curIndex;
}
// 查:判断两个元素是不是同一个组ID
int isSameQU(QuickUnionSet *setQU, Element a, Element b){
    // a和b的根节点
    int aRoot = findRootIndex(setQU, a);
    int bRoot = findRootIndex(setQU, b);
    if (aRoot == -1 || bRoot == -1) {
        return 0;
    }
    return aRoot == bRoot;
}
/* 在QuickUnion集合中,将a元素和b元素合并
 * 1. 找到a和b的根节点
 * 2. 将b的父节点指向a的索引
 * 根据size做优化,谁的元素多,就让另一根接入元素多的根
 * */
// 并:合并两个元素
void unionQU(QuickUnionSet *setQU, Element a, Element b){
    int aRoot = findRootIndex(setQU, a);
    int bRoot = findRootIndex(setQU, b);
    if (aRoot == -1 || bRoot == -1) {
        return;
    }
    if(aRoot!=bRoot){
        // 根据根节点元素个数来决定合并顺序
        int aSize=setQU->size[aRoot];
        int bSize=setQU->size[bRoot];
        if(aSize>bSize){
            setQU->parent[bRoot]=aRoot;
            setQU->size[aRoot]+=bSize;
        }else{
            setQU->parent[aRoot]=bRoot;
            setQU->size[bRoot]+=aSize;
        }
    }
}

#include <stdio.h>
#include "quickFindSet.h"
#include "quickUnionSet.h"
// QuickFind的测试
int test01() {
    int n = 9;
    QuickFindSet *QFSet = createQuickFindSet(n);
    Element data[9];
    for (int i = 0; i < sizeof(data) / sizeof(data[0]); ++i) {
        data[i] = i;
    }
    initQuickFindSet(QFSet, data, n);

    unionQF(QFSet, 3, 4);
    unionQF(QFSet, 8, 0);
    unionQF(QFSet, 2, 3);
    unionQF(QFSet, 5, 6);
    if (isSameQF(QFSet, 0, 2)) {
        printf("Yes\n");
    } else {
        printf("No\n");
    }
    if (isSameQF(QFSet, 2, 4)) {
        printf("Yes\n");
    } else {
        printf("No\n");
    }
    unionQF(QFSet, 5, 1);
    unionQF(QFSet, 7, 3);
    unionQF(QFSet, 1, 6);
    unionQF(QFSet, 4, 8);
    if (isSameQF(QFSet, 0, 2)) {
        printf("Yes\n");
    } else {
        printf("No\n");
    }
    if (isSameQF(QFSet, 2, 4)) {
        printf("Yes\n");
    } else {
        printf("No\n");
    }
    releaseQuickFindSet(QFSet);
    return 0;
}

int test02() {
    int n = 9;
    QuickUnionSet *QUSet = createQuickUnionSet(n);
    Element data[9];
    for (int i = 0; i < sizeof(data) / sizeof(data[0]); ++i) {
        data[i] = i;
    }
    initQuickUnionSet(QUSet, data, n);

    unionQU(QUSet, 3, 4);
    unionQU(QUSet, 8, 0);
    unionQU(QUSet, 2, 3);
    unionQU(QUSet, 5, 6);
    if (isSameQU(QUSet, 0, 2)) {
        printf("Yes\n");
    } else {
        printf("No\n");
    }
    if (isSameQU(QUSet, 2, 4)) {
        printf("Yes\n");
    } else {
        printf("No\n");
    }
    unionQU(QUSet, 5, 1);
    unionQU(QUSet, 7, 3);
    unionQU(QUSet, 1, 6);
    unionQU(QUSet, 4, 8);
    if (isSameQU(QUSet, 0, 2)) {
        printf("Yes\n");
    } else {
        printf("No\n");
    }
    if (isSameQU(QUSet, 2, 4)) {
        printf("Yes\n");
    } else {
        printf("No\n");
    }
    // releaseQuickFindSet(QFSet);
    return 0;
}

int main() {
    test01();
    return 0;
}

1.3.5 路径压缩

  • 在find时使路径上的所有节点都指向根节点,从而降低树的高度
  • 指向父节点的父节点
  • 都指向根结点

2. 哈夫曼树

2.1 哈夫曼树相关的几个名词

  • 路径

在一棵树中,一个结点到另一个结点之间的通路,称为路径。
从根结点到结点 a 之间的通路就是一条路径。

  • 路径长度

在一条路径中,每经过一个结点,路径长度都要加 1 。
例如在一棵树中,规定根结点所在层数为1层,那么从根结点到第 i 层结点的路径长度为 i - 1 。
从根结点到结点 c 的路径长度为 3。

  • 节点的权

给每一个结点赋予一个新的数值,被称为这个结点的权。
例如,结点 a 的权为 7,结点 b 的权为 5。

  • 节点的带权路径长度

指的是从根结点到该结点之间的路径长度与该结点的权的乘积。
例如,结点 b 的带权路径长度为 2 * 5 = 10 。

树的带权路径长度为树中所有叶子结点的带权路径长度之和。通常记作 “WPL” 。
例如图中所示的这颗树的带权路径长度为:

WPL = 7 * 1 + 5 * 2 + 2 * 3 + 4 * 3
在这里插入图片描述

2.2 什么是哈夫曼树

当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称
这棵树为“最优二叉树”,有时也叫“赫夫曼树”或者“哈夫曼树”。

2.3 构建哈夫曼树

  • 对于给定的有各自权值的 n 个结点,构建哈夫曼树有一个行之有效的办法:
  • 1.在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权
    值的和;
    1. 在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推;
  • 3.重复 1 和 2 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。
    在这里插入图片描述
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值