【第七章】神奇的树

第一节:

  • 树是不包含回路的联通无向图
  • 基础

第二节:

  • 二叉树

第三节:堆

建立堆

建立堆,书上讲了两种方法
这里用第二种方法(毕竟方便)

const h = [0, 99, 5, 36, 2, 19, 1, 46, 12, 7, 22, 25, 28, 17, 92]
const n = h.length - 1;
const siftdown = (i) => {
    let t, flag = 0;
    while (i * 2 <= n && flag === 0) {
        if (h[i] > h[i * 2]) {
            t = i * 2;
        } else {
            t = i;
        }
        // 如果有右孩子,在判断右孩子的情况
        if (i * 2 + 1 <= n) {
            if (h[t] > h[i * 2 + 1]) {
                // 更新较小的结点编号
                t = i * 2 + 1;
            }
        }
        // 如果发现最小的节点不是自己,那么交换
        if (t != i) {
            swap(t, i);
            i = t;
        } else {
            flag = 1;
        }
    }
}
const siftup = (i) => {
    let flag = 0;
    if (i == 1) return;  // 已经到堆顶了
    while (i != 1 && flag == 0) {
        // 判断是否比父节点的小
        let fa = parseInt(i / 2);
        if (h[i] < h[fa]) {
            swap(i, fa)
        } else {
            flag = 1;
        }
        i = parseInt(i / 2);
    }
}
const swap = (i, j) => {
    [h[j], h[i]] = [h[i], h[j]];
}

// 建立堆
for (let i = parseInt(n / 2); i >= 1; i--) {
    siftdown(i);
}
console.log(h);

堆排序–小根堆

const h = [0, 99, 5, 36, 2, 19, 1, 46, 12, 7, 22, 25, 28, 17, 92]
let n = h.length - 1;

// 下沉操作
const siftdown = (i) => {
    let t, flag = 0;
    while (i * 2 <= n && flag === 0) {
        // flag 控制是否需要继续下沉
        if (h[i] > h[i * 2]) {
            // 和左孩子比较
            t = i * 2;
        } else {
            t = i;
        }

        // 如果有右孩子,在判断右孩子的情况
        if (i * 2 + 1 <= n) {
            if (h[t] > h[i * 2 + 1]) {
                // 更新较小的结点编号
                t = i * 2 + 1;
            }
        }

        // 如果发现最小的节点不是自己,那么交换
        if (t != i) {
            swap(t, i);
            i = t;
        } else {
            // 如果最小的是自己,那么就不需要下沉了。
            flag = 1;
        }
    }
}

// 上浮
const siftup = (i) => {
    let flag = 0;
    if (i === 1) return;  // 已经到堆顶了
    while (i !== 1 && flag === 0) {
        // 判断是否比父节点的小
        let fa = parseInt(i / 2);
        if (h[i] < h[fa]) {
            swap(i, fa)
        } else {
            flag = 1;
        }
        i = fa;
    }
}

const swap = (i, j) => {
    [h[j], h[i]] = [h[i], h[j]];
}

/* 
    建立堆
    思想就是,判断以当前节点为根节点的树是否满足最小根的规则
    那么从哪里开始呢:叶子节点是不需要考虑的,倒数,从第一个非叶子节点开始
    从后向前排查即可。
*/

for (let i = parseInt(n / 2); i >= 1; i--) {
    siftdown(i);
}
console.log('小根堆建立完成:>>', h);

// 堆排序:从小到大排序
const deleteMax = () => {
    let t = h[1];   // 把堆顶保存起来
    h[1] = h[n];    // 把最后一个点赋值到堆顶
    n--;            // 缩小堆的大小
    siftdown(1);    // 下浮根节点,维护小根堆的规则。
    return t;
}
let num = h.length - 1;

// for 循环里并不是 i <= n; 因为 n 在 deleteMax 中在自减。
for (let i = 1; i <= num; i++) {
    console.log(deleteMax());
}

堆排序–大根堆

const h = [0, 99, 5, 36, 2, 19, 1, 46, 12, 7, 22, 25, 28, 17, 92]
let n = h.length - 1;

// 下沉操作
const siftdown = (i) => {
    let t, flag = 0;
    while (i * 2 <= n && flag === 0) {
        // flag 控制是否需要继续下沉
        if (h[i] < h[i * 2]) {              //  最大堆
            // 和左孩子比较
            t = i * 2;
        } else {
            t = i;
        }

        // 如果有右孩子,在判断右孩子的情况
        if (i * 2 + 1 <= n) {
            if (h[t] < h[i * 2 + 1]) {      //  最大堆
                // 更新较小的结点编号
                t = i * 2 + 1;
            }
        }

        // 如果发现最小的节点不是自己,那么交换
        if (t != i) {
            swap(t, i);
            i = t;
        } else {
            // 如果最小的是自己,那么就不需要下沉了。
            flag = 1;
        }
    }
}

const siftup = (i) => {
    let flag = 0;
    if (i === 1) return;  // 已经到堆顶了
    while (i !== 1 && flag === 0) {
        // 判断是否比父节点的小
        let fa = parseInt(i / 2);
        if (h[i] > h[fa]) {             //  最大堆
            swap(i, fa)
        } else {
            flag = 1;
        }
        i = fa;
    }
}

const swap = (i, j) => {
    [h[j], h[i]] = [h[i], h[j]];
}

/* 
    建立堆
    思想就是,判断以当前节点为根节点的树是否满足最小根的规则
    那么从哪里开始呢:叶子节点是不需要考虑的,倒数,从第一个非叶子节点开始
    从后向前排查即可。
*/

for (let i = parseInt(n / 2); i >= 1; i--) {
    siftdown(i);
}
console.log('大根堆建立完成:>>', h);

const heapSort = () => {
    // 如果从小到大排序,那么这样做的前提是大根堆;
    while (n >= 1) {
        swap(1, n);
        n--;
        siftdown(1);
    }
}
heapSort();
console.log('排序完成:>>', h);

小节总结

这一小节其实东西挺重要的,二叉树的典型应用
优先队列:支持插入元素并且寻找最大值、最小值的数据解构
应用场景:

  • 求一个数列中第k大的树:我们可以用一个简单的例子,比方说[1,2,3,4,5],找第三个最大的数,先建立规模为k的最小堆,然后从4开始,与最小堆的根作比较,如果根 小于 4 ,那么替换掉,并下沉,然后接着比较5,5 和 2 比较,也是小于,替换掉,并下沉,此时的根就是3了。如果大于的话,跳过替换的操作即可。(字好多啊,有时间提取出来写个小专题代码)

并查集

// 找集合
// 遵守两种规则:1、靠左法则 2、擒贼擒王
let f = new Array(11);
let n = 10,
    m = 9;

// 初始化 
for (let i = 1; i <= n; i++) {
    f[i] = i;
}

// 获取最大 boss
const getf = (v) => {
    if (f[v] === v) {
        return v;
    } else {
        // 路径压缩
        f[v] = getf(f[v]);
        return f[v];
    }
}
var count = 0;
console.log(`合并第${count}次的f:>>`, f);
// 合并
const merge = (v, u) => {
    let t1 = getf(v),
        t2 = getf(u);
    if (t1 !== t2) {
        f[t2] = t1; //  f[右] = 左,所谓的靠左原则
    }
    count++;
    console.log(`合并第${count}次的f:>>`, f);
}
let a = [
    [0, 0], [1, 2], [3, 4], [5, 2], [4, 6], [2, 6], [8, 7], [9, 7], [1, 6], [2, 4]
]
for (let i = 1; i <= m; i++) {
    merge(a[i][0], a[i][1])
}
let sum = 0;
for (let i = 1; i <= n; i++) {
    if (f[i] === i) {
        sum++;
    }
}
console.log("最终f:>>", f);
console.log("并查集结果:>>", sum);

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值