🥳每日一练-kruskal-最小生成树-JS简易版

这篇文章来分享用 JS 语言来实现 kruskal 算法。kruskal很简单,快来看看

这个图来自王道考研的数据结构视频课程中的截图。B 站可搜王道考研数据结构

实现思路:先找出图中所有的权值最小的边,将边两头的节点放进最小生成树的集合里面;然后在剩余边里面继续找出最小的边,并且将第二小的边两头的节点放入最小生成的树的集合里面;继续寻找下一个最小的边。

如果最小边的两头节点,已经存在于最小生成树的集合里,说明这两个节点之间,有更小的边在之前已经被添加进来了,所以不做任何处理

准备数据

无向图的点集合,边集合

javascript 复制代码
/**@type {number[]} */
let graphNodes = Array(6)
	.fill(0)
	.map((item, index) => index);

/**@type {number[][]} */
const graphEdges = {
    0: [
        [1, 6],
        [3, 1],
        [2, 5],
    ],
    3: [
        [1, 5],
        [2, 4],
    ],
    4: [
        [1, 3],
        [3, 6],
        [5, 6],
    ],
    5: [
        [2, 2],
        [3, 4],
    ],
};

准备两个数据结构,

  • graphNodes表示点的集合;
  • graphEdges表示边的集合;graphEdges是一个对象,对象的 key 表示边的起点,对应的 value 表示边的终点和边的权重,即[destination,power]

并查集

因为 kruskal 涉及到并查集的操作,所以需要并查集的数据结构和操作。

了解更多并查集的内容,可以看这篇文章:🥳每日一练--两种并查集的JS实现 - 掘金

javascript 复制代码
// 创建一个长度为8的数组,用-1填充,并用map方法将索引添加到数组中
const unionArray = Array(8)
    .fill(-1)
    .map((item, index) => index);

// 定义一个查找根节点的函数,传入节点,返回根节点
const findRoot = (unionArray, node) => {
    while (unionArray[node] !== node) node = unionArray[node];

    return node;
};

// 定义一个检查两个节点是否属于同一 union 的函数,传入两个节点,返回布尔值
const checkSameUnion = (unionArray, node1, node2) => {
    return findRoot(unionArray, node1) == findRoot(unionArray, node2);
};

// 定义一个合并两个 union 的函数,传入两个节点,不返回任何值
const combineUnion = (unionArray, node1, node2) => {
    const union1 = findRoot(unionArray, node1);
    const union2 = findRoot(unionArray, node2);
    if (union1 == union2) return; // 如果两个节点的根节点相同,说明它们属于同一 union,无需合并

    unionArray[union2] = union1; // 将其中一个节点的根节点设置为另一个节点的根节点
};
  • unionArray存储集合关系的结构
  • findRoot 函数:查找节点的根节点
  • checkSameUnion 函数:检查两个节点是否属于同一 union。传入两个节点,它会使用 findRoot 函数查找它们的根节点,然后比较它们的根节点是否相同。如果相同,则说明它们属于同一 union
  • combineUnion 函数:合并两个 union。传入两个节点,它会使用 findRoot 函数查找它们的根节点,然后将其中一个节点的根节点设置为另一个节点的根节点。这样,它们就属于同一 union

准备最小生成树的数据结构

javascript 复制代码
// 定义一个空数组,用于存储最小生成树的边
const minTreeEdges = [];

// 定义一个空集合,用于存储最小生成树中的节点
const minTreeNodes = new Set();

// 定义一个函数,用于将新节点添加到最小生成树中
const addNodeToMinTree = (newNode, otherNode, power) => {
    // 如果新节点的索引在 minTreeEdges 中不存在,则初始化一个空数组
    minTreeEdges[newNode] = minTreeEdges[newNode] || [];

    // 将新节点的邻接边添加到 minTreeEdges 中
    minTreeEdges[newNode].push([otherNode, power]);

    // 将新节点和邻接节点添加到 minTreeNodes 中
    minTreeNodes.add(newNode);
    minTreeNodes.add(otherNode);
};

minTreeEdges:用来存储最小生成树的边

minTreeNodes:用于存储最小生成树中的节点。像上面准备的无向图的数据一样。

还定义了一个函数 addNodeToMinTree,用于将新节点添加到最小生成树中。它接受三个参数:newNode(新节点)、otherNode(邻接节点)和 power(边权)

  • 如果新节点的索引在 minTreeEdges 中不存在,则初始化一个空数组
  • 将新节点的邻接边添加到 minTreeEdges
  • 将新节点和邻接节点添加到 minTreeNodes

kruskal

javascript 复制代码
// 定义一个函数,用于实现 Kruskal 算法
const kruskal = (graphEdges) => {
  /** @typedef {number} nodeValue */
  /** @typedef {number} power */

  /** @type {[nodeValue, nodeValue, power ]} */
  // 将图的边转换为数组形式,每个元素都是一个包含三个元素的数组:[node1, node2, power]
  let array = Object.entries(graphEdges)
    .reduce((res, [node, edges]) => {
      res.push(...edges.map((edge) => [node, ...edge]));
      return res;
    }, []);

  // 对数组进行排序,按照边权从小到大排序
  array.sort((pre, next) => pre[2] - next[2]);

  // 对数组中的每一条边进行处理
  array.map(([node1, node2, power]) => {
    // 将节点转换为数字类型
    node1 = Number(node1);
    node2 = Number(node2);

    // 如果两个节点不属于同一 union,则合并它们
    if (!checkSameUnion(unionArray, node1, node2)) {
      // 将两个节点的根节点合并
      combineUnion(unionArray, node1, node2);

      // 将新边添加到最小生成树的边数组中
      addNodeToMinTree(node1, node2, power);
    }
  });
};

首先准备一个数组,这个数组里面存放着所有边,并且按照边的权值升序。

按照 kruskal 的思想,从权值最小的边开始构建最小生成树。所以排序是必要的。

然后就开始遍历这个数组了,遍历过程的代码很简单,注释也很清晰,就不讲解了。

执行函数

javascript 复制代码
kruskal(graphEdges);

console.log(minTreeNodes, minTreeEdges);
// Set(6) { 0, 3, 5, 2, 4, 1 }
// [
//   [ [ 3, 1 ] ],
//   <2 empty items>,
//   [ [ 2, 4 ], [ 1, 5 ] ],
//   [ [ 1, 3 ] ],
//   [ [ 2, 2 ] ]
// ]

将生成的内容最后输出,其中所有的点都遍历到了,构建的边只有几条,那具体的最小生成树长什么样子呢?如下图:

可以和minTreeEdges输出内容一一对应。

minTreeEdges的解读方式和上文最开始准备的无向图的数据一致

将最小生成树转成邻接表

javascript 复制代码
/**
* 定义一个函数,用于生成一个邻接表
*
* @param {number[]} graphNodes 节点数组
* @param {number[][]} graphEdges 边数组
* @returns {nodeType[]} 返回图的节点数组
*/
const generateGraph = (graphNodes, graphEdges) => {
    // 将节点数组转换为对象,方便添加边
    const graph = graphNodes.map((item, index) => {
        const tempNode = { value: index, next: null };
        return tempNode;
    });

    // 定义一个函数,用于添加边
    const addNode = (graph, fromNode, toNode, power) => {
        // 添加从 fromNode 到 toNode 的边
        let currentNode = graph[fromNode];
        while (currentNode.next) {
            // 如果已经有边连接这两个节点,则跳过
            if (currentNode.next.value == toNode) return;
            currentNode = currentNode.next;
        }
        currentNode.next = { value: toNode, next: null, power };

        // 添加从 toNode 到 fromNode 的边
        currentNode = graph[toNode];
        while (currentNode.next) {
            currentNode = currentNode.next;
        }
        currentNode.next = { value: fromNode, next: null, power };
    };

    // 遍历边数组,将每条边添加到图中
    Object.entries(graphEdges).map(([value, edges]) => {
        edges.map(([toNode, power]) => {
            addNode(graph, value, toNode, power);
        });
    });

    // 返回图的节点数组
    return graphNodes;
};


const minTree = generateGraph([...minTreeNodes], minTreeEdges);

console.log(minTree);

这里将minTreeNodesminTreeEdges的数据转成邻接表的数据结构,就能在数据层面上更清晰了。

下面是最后输出的邻接表的 json 结构

数据有点长,代码栏可以展开和关闭哦

json 复制代码
[
  {
    value: 0,
    next: {
      value: 3,
      next: null,
      power: 1,
    },
  },
  {
    value: 1,
    next: {
      value: "3",
      next: {
        value: "4",
        next: null,
        power: 3,
      },
      power: 5,
    },
  },
  {
    value: 2,
    next: {
      value: "3",
      next: {
        value: "5",
        next: null,
        power: 2,
      },
      power: 4,
    },
  },
  {
    value: 3,
    next: {
      value: "0",
      next: {
        value: 2,
        next: {
          value: 1,
          next: null,
          power: 5,
        },
        power: 4,
      },
      power: 1,
    },
  },
  {
    value: 4,
    next: {
      value: 1,
      next: null,
      power: 3,
    },
  },
  {
    value: 5,
    next: {
      value: 2,
      next: null,
      power: 2,
    },
  },
]

完整代码

javascript 复制代码
//准备数据

/**@type {number[]} */
let graphNodes = Array(6)
  .fill(0)
  .map((item, index) => index);

/**@type {number[][]} */
const graphEdges = {
  0: [
    [1, 6],
    [3, 1],
    [2, 5],
  ],
  3: [
    [1, 5],
    [0, 1],
    [2, 4],
  ],
  4: [
    [1, 3],
    [3, 6],
    [5, 6],
  ],
  5: [
    [2, 2],
    [3, 4],
  ],
};

//并查集
const unionArray = Array(8)
  .fill(-1)
  .map((item, index) => index);

const findRoot = (unionArray, node) => {
  while (unionArray[node] !== node) node = unionArray[node];

  return node;
};

const checkSameUnion = (unionArray, node1, node2) => {
  return findRoot(unionArray, node1) == findRoot(unionArray, node2);
};

const combineUnion = (unionArray, node1, node2) => {
  const union1 = findRoot(unionArray, node1);
  const union2 = findRoot(unionArray, node2);
  if (union1 == union2) return;

  unionArray[union2] = union1;
};

//最小生成树
const minTreeEdges = [];
const minTreeNodes = new Set();
const addNodeToMinTree = (newNode, otherNode, power) => {
  minTreeEdges[newNode] = minTreeEdges[newNode] || [];
  minTreeEdges[newNode].push([otherNode, power]);
  minTreeNodes.add(newNode);
  minTreeNodes.add(otherNode);
};

//kruskal实现
const kruskal = (graphEdges) => {
  /** @typedef {number} nodeValue */
  /** @typedef {number} power */

  /** @type {[nodeValue, nodeValue, power ]} */
  let array = Object.entries(graphEdges)
    .reduce((res, [node, edges]) => {
      res.push(...edges.map((edge) => [node, ...edge]));
      return res;
    }, [])
    .sort((pre, next) => pre[2] - next[2]);

  array.map(([node1, node2, power]) => {
    node1 = Number(node1);
    node2 = Number(node2);
    if (!checkSameUnion(unionArray, node1, node2)) {
      combineUnion(unionArray, node1, node2);
      addNodeToMinTree(node1, node2, power);
    }
  });
};

kruskal(graphEdges);

const minTree = generateGraph([...minTreeNodes], minTreeEdges);

总结

这篇文章分享了分享了如何用 JS 代码实现 kruskal,文中过程清晰,代码也有详细的注释。相信你一定可以看得懂。

事实证明 kruskal 还是很简单的😄

相关推荐
前端Hardy3 分钟前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
小沈熬夜秃头中୧⍤⃝12 分钟前
【贪心算法】No.1---贪心算法(1)
算法·贪心算法
web Rookie33 分钟前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
工业甲酰苯胺43 分钟前
C# 单例模式的多种实现
javascript·单例模式·c#
木向44 分钟前
leetcode92:反转链表||
数据结构·c++·算法·leetcode·链表
阿阿越1 小时前
算法每日练 -- 双指针篇(持续更新中)
数据结构·c++·算法
skaiuijing1 小时前
Sparrow系列拓展篇:对调度层进行抽象并引入IPC机制信号量
c语言·算法·操作系统·调度算法·操作系统内核
Star Patrick1 小时前
算法训练(leetcode)二刷第十九天 | *39. 组合总和、*40. 组合总和 II、*131. 分割回文串
python·算法·leetcode
武子康2 小时前
大数据-214 数据挖掘 机器学习理论 - KMeans Python 实现 算法验证 sklearn n_clusters labels
大数据·人工智能·python·深度学习·算法·机器学习·数据挖掘
小爬虫程序猿3 小时前
如何利用Python解析API返回的数据结构?
数据结构·数据库·python