数据结构-并查集和堆、哈夫曼树

lickylin 2020-02-22

一、并查集的定义

  1. 并查集是一种维护集合的数据结构,它的名字中“并”、“查”、“集”。分别取自Union(合并)、Find(查找)、Set(集合)。
  • 合并:就是合并两个集合
  • 查找:判断两个元素是否在一个集合
  • 那么并查集是用什么实现的,就是一个数组,
  • 对于同一个集合来说只存在一个根结点,且将其作为所属集合的标识。

二、并查集的基本操作

  1. 初始化,每个元素都是一个独立的集合,因此需要令所有的father[i] = i;
for(int i = 1; i <= N; i++){
    father[i] = i;
}
  1. 查找,就是查找反复寻找父亲结点。
int findFather(int x){
    while(x != father){
        x = father[x];
    }
    return x;
}

int findFather(int x){
    if(x == father[x]) return x;
    else return findFather(father[x]);
}
  1. 合并,就是把两个集合合并成一个集合。
void Union(int a, int b){
    int faA = findFather(a);
    int faB = findFather(b);
    if(faA != faB){
        father[faA] = faB;
    }
}

三、路径压缩

  • 把当前查询结点的路径上的所有结点的父亲都指向根结点。
int findFather(int x){
    //由于x在下面的while中会变成根结点,因此先把原先的x保存一下
    int a = x;
    while(x != father[x]){
        x = father[x];
    }
    //到这里,x存放的是根结点,下面把路径上所有的结点的father都改成根结点
    while(a != father[a]){
        int z = a;
        a = father[a];//a回溯父亲结点
        father[z] = a;//把原来的结点a的父亲改为根结点
    }
    return x;//返回根结点
}

四、堆的定义

  1. 堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子结点的值。
  2. 如果父亲结点的值大于或等于孩子结点的值,那么称这样的堆为大顶堆,反之为小顶堆。
  3. 堆一般用于优先队列的实现,而优先队列默认情况下使用大顶堆。

五、堆的基本操作

1. 建堆向下调整
//对于heap数组在[low, high]的范围进行向下调整
//其中low为欲调整结点的数组下标,high一般为堆的最后一个元素的数组下标
void downAdjust(int low, int high){
    int i = low, j = i * 2;
    while(j <= high){
        if(j+1 <= high && heap[j+1] > heap[j]){
            j = j + 1;
        }
        if((heap[j] > heap[i]){
            swap(heap[j], heap[i]);
            i = j;
            j = i * 2;
        }else{
            break;
        }
    }
}
2. 建堆
  • 假设序列中元素的个数为n,根据完全二叉树的特性,知道叶子结点个数为n/2个,因此数组下标在[1, n/2]的范围内是非叶子结点。于是可以从n/2号位开始倒着枚举结点,对每个遍历到结点i进行[i, n]范围的调整。
void createHeap(){
    for(int i = n / 2; i >= 1; i--){
        downAdjust(i, n);
    }
}
3. 删除堆中最大元素
  • 只需要最后一个元素覆盖堆顶元素,然后对根结点进行调整。
void deleteTop(){
    heap[1] = heap[n--];
    downAdjust(1, n);
}
4. 添加元素
  • 也就是把添加的元素放在数组的最后一个位置,然后进行上调操作。
//对于heap数组在[low, high]的范围进行向上调整
//其中low为欲调整结点的数组下标设置为1,high一般为堆的最后一个元素的数组下标
void upAdjust(int low, int high){
    int i = high, j = i / 2;//i为欲调整结点,j为其父亲
    while(j >= low){
        if(heap[j] < heap[i]){
            swap(heap[j], heap[i]);
            i = j;
            j = i / 2;
        }else{
            break;
        }
    }
}

//添加元素
void insert(int x){
    heap[++n] = x;//让元素个数加1,然后将数组末尾赋值为x
    upAdjust(1, n);//向上调整新加入的结点n
}
5. 堆排序
  • 是使用堆结构对一个序列进行排序。
void heapSort(){
    createHeap();
    for(int i = n; i > 1; i--){
        swap(heap[i], heap[1]);
        downAdjust(1, i - 1);
    }
}

六、哈夫曼树的相关定义

  1. 其中叶子结点的路径长度是指从根结点出发到达结点的边数。
  2. 把叶子结点的权值乘以其路径长度的结果称为这个叶子结点的带权路径长度。
  3. 树的带权路径长度(WPL)等于它所有叶子结点带权路径的长度之和。
  4. 带权路径长度最小的就是称为哈夫曼树(最优二叉树),显然对于同一组叶子结点来说,哈夫曼树可以是不唯一的,但是带权路径长度一定是唯一的。

七、哈夫曼编码

  1. 对于任何一个叶子结点,其编码一定不会成为其任何一个结点编码前缀。
  2. 其中任何一个字节编码都不是另一个字符编码的前缀,同时满足这种方式的编码方式称为前缀编码,意义不产生混淆。
  3. 哈夫曼编码,就是使得字符串编码成01串后长度最短的前缀编码。
  4. 哈法曼编码是针对于特定的字符串来讲的。

相关推荐