图的最小生成树问题

39 阅读2分钟

图的最小生成树问题

表示一张图,有点集合边集合

树是有向无环图

关于最小生成树问题有两种算法

  • 普利姆算法(加点法)
  • 克鲁斯卡尔算法(加边法)

普利姆算法(加点法)

  1. 现任选一个点为起点
  2. 找到以当前选中的点为起点的最短路径的边
  3. 如果这个边的另一端还未联通,那么就联通它
  4. 如果这个边的另一端已联通,则看倒数第二短的边
  5. 重复2--4直到所有的点全部联通为止
var max = 100000;
// 点的集合
var pointSet = [];
// 边的集合
var distance = [
  [0, 4, 7, max, max],
  [4, 0, 8, 6, max],
  [7, 8, 0, 5, max],
  [max, 6, 5, 0, 7],
  [max, max, max, 7, 0],
];

function Node(value) {
  this.value = value;
  this.neighbor = [];
}

var a = new Node("A");
var b = new Node("B");
var c = new Node("C");
var d = new Node("D");
var e = new Node("E");
pointSet.push(a);
pointSet.push(b);
pointSet.push(c);
pointSet.push(d);
pointSet.push(e);

function getIndex(str) {
  for (var i = 0; i < pointSet.length; i++) {
    if (str == pointSet[i].value) return i; //找到第几行
  }
  // 找不到返回-1
  return -1;
}

// 获取链接距离最短的点
function getMinDisNode(pointSet, distance, alreadySet) {
  // 传入点,边,以及已经链接的点
  var fromNode = null; //起点
  var toNode = null; //终点
  var minDis = max; //距离
  //   根据已有的点为起点,找其他点
  for (var i = 0; i < alreadySet.length; i++) {
    // 遍历起点
    var nowRowIndex = getIndex(alreadySet[i].value); //记录当前行的序号
    for (var j = 0; j < distance[nowRowIndex].length; j++) {
      // 遍历序号对应的距离的行
      var thisNode = pointSet[j]; //二维数组中当前行的当前列的点
      if (
        alreadySet.indexOf(thisNode) < 0 && //当前节点未链接,不存在
        distance[nowRowIndex][j] < minDis //距离也是当前最短
      ) {
        fromNode = alreadySet[i];
        toNode = thisNode;
        minDis = distance[nowRowIndex][j];
      }
    }
  }
  //   将起点和终点相连接
  fromNode.neighbor.push(toNode);
  toNode.neighbor.push(fromNode);
  return toNode;
}

// 普利姆算法
function Prim(pointSet, distance, start) {
  var alreadySet = [];
  // 传入一个起始点
  alreadySet.push(start);
  //根据这个点开始找边
  while (true) {
    var minDisNode = getMinDisNode(pointSet, distance, alreadySet);
    alreadySet.push(minDisNode);
    if (alreadySet.length == pointSet.length) {
      // 所有的点都连接了
      break;
    }
  }
}

Prim(pointSet, distance, c);
console.log(pointSet);

克鲁斯卡尔算法(加边法)

  1. 选择最短的边进行链接,
  2. 保证这个边的两端至少有一个点是新的点
  3. 或者这个点是把两个已连接的点(组成的段)连接起来
  4. 重复1--3直到全部链接
var max = 100000;
// 点的集合
var pointSet = [];
// 边的集合
var distance = [
  [0, 4, 7, max, max],
  [4, 0, 8, 6, max],
  [7, 8, 0, 5, max],
  [max, 6, 5, 0, 7],
  [max, max, max, 7, 0],
];

function Node(value) {
  this.value = value;
  this.neighbor = [];
}

var a = new Node("A");
var b = new Node("B");
var c = new Node("C");
var d = new Node("D");
var e = new Node("E");
pointSet.push(a);
pointSet.push(b);
pointSet.push(c);
pointSet.push(d);
pointSet.push(e);

// 判断是否可以连接
function canLink(resultList, tempBegin, tempEnd) {
  var begin = null;
  var end = null;
  for (var i = 0; i < resultList.length; i++) {
    if (resultList[i].indexOf(tempBegin) > -1) {
      begin = resultList[i];
    }
    if (resultList[i].indexOf(tempEnd) > -1) {
      end = resultList[i];
    }
  }
  // 两个点都是新的--可以连接
  // 一个在部落,一个不在部落里--可以连接
  // 两个在不同的部落--可以连接
  // 在同一个部落--不可以
  if (begin != null && end != null && begin == end) {
    return false;
  }
  return true;
}

// 连接
function link(resultList, tempBegin, tempEnd) {
  var begin = null;
  var end = null;
  for (var i = 0; i < resultList.length; i++) {
    if (resultList[i].indexOf(tempBegin) > -1) {
      begin = resultList[i];
    }
    if (resultList[i].indexOf(tempEnd) > -1) {
      end = resultList[i];
    }
  }
  // 两个都是新的点
  if (begin == null && end == null) {
    var newArr = [];
    newArr.push(tempBegin);
    newArr.push(tempEnd);
    resultList.push(newArr);
  }
  //begin不在部落,end在部落
  else if (begin == null && end != null) {
    end.push(tempBegin);
  } else if (begin != null && end == null) {
    begin.push(tempEnd);
  } else if (
    // 两个不同的部落
    begin != null &&
    end != null &&
    begin != end
  ) {
    // 部落合并
    var allin = begin.concat(end);
    // 删除它
    var remove = resultList.indexOf(end);
    resultList.splice(remove, 1);
    var removeBegin = resultList.indexOf(begin);
    resultList.splice(removeBegin, 1);
    resultList.push(allin);
  }
  tempBegin.neighbor.push(tempEnd);
  tempEnd.neighbor.push(tempBegin);
}
//克鲁斯卡尔算法
// 不用给起始点
function Kruskar(pointSet, distance) {
  var resultList = []; //这是一个二维数组,用来表示有多少个部落
  while (true) {
    var begin = null;
    var end = null;
    var minDis = max;
    for (var i = 0; i < distance.length; i++) {
      for (var j = 0; j < distance[i].length; j++) {
        // 二维数组,遍历第i行的第j个数
        var tempBegin = pointSet[i];
        var tempEnd = pointSet[j];
        if (
          i != j && //这里的值都为0
          distance[i][j] < minDis &&
          canLink(resultList, tempBegin, tempEnd)
        ) {
          // 条件都满足
          minDis = distance[i][j];
          begin = tempBegin;
          end = tempEnd;
        }
      }
    }
    link(resultList, begin, end);
    if (resultList.length == 1 && resultList[0].length == pointSet.length) {
      break;
    }
  }
}
Kruskar(pointSet, distance);
console.log(pointSet);