📜  使用 JavaScript 的堆排序可视化(1)

📅  最后修改于: 2023-12-03 14:49:41.353000             🧑  作者: Mango

使用 JavaScript 的堆排序可视化
介绍

堆排序是一种高效的排序算法,它将待排序的元素构造成一个二叉堆,从而实现排序。该算法的核心思想是通过调整堆的结构来完成排序。

在 JavaScript 中,堆排序可以使用数组来实现。因为 JavaScript 的数组支持动态扩容,在排序过程中不用担心数组容量不足的问题。

本文将介绍如何使用 JavaScript 实现堆排序,并通过可视化展示算法执行的过程。

实现

堆排序

堆排序分为两个步骤:建堆和排序。建堆需要先将数组构造成一个二叉堆,排序则需要不断地取出堆顶元素并调整堆的结构。

function heapSort(arr) {
  // 建堆
  buildHeap(arr);
  
  // 排序
  for (let i = arr.length - 1; i > 0; i--) {
    // 将堆顶元素与尾部元素交换
    swap(arr, 0, i);
    // 调整堆结构
    heapify(arr, 0, i - 1);
  }

  return arr;
}

建堆

建堆需要从最后一个非叶子节点开始,依次对节点进行堆化操作。堆化操作包括两个步骤:先将当前节点与其左右子节点中的最大值交换,再将交换后的节点与其子节点中的最大值交换,直到达到堆底部为止。

function buildHeap(arr) {
  const len = arr.length;
  const lastNodeIndex = len >> 1;
  for (let i = lastNodeIndex; i >= 0; i--) {
    heapify(arr, i, len - 1);
  }
}

堆化

堆化可以被认为是建堆中子过程的处理方式。它依赖于子树的状态,将一个子树调整为符合堆要求的状态。堆化操作包括两个步骤:先将当前节点与其左右子节点中的最大值交换,再将交换后的节点与其子节点中的最大值交换,直到达到堆底部为止。

function heapify(arr, nodeIndex, lastNodeIndex) {
  let leftNodeIndex = 2 * nodeIndex + 1;
  let rightNodeIndex = 2 * nodeIndex + 2;
  let maxIndex = nodeIndex;

  if (leftNodeIndex <= lastNodeIndex && arr[leftNodeIndex] > arr[maxIndex]) {
    maxIndex = leftNodeIndex;
  }
  
  if (rightNodeIndex <= lastNodeIndex && arr[rightNodeIndex] > arr[maxIndex]) {
    maxIndex = rightNodeIndex;
  }

  if (maxIndex !== nodeIndex) {
    swap(arr, nodeIndex, maxIndex);
    heapify(arr, maxIndex, lastNodeIndex);
  }
}

swap

swap 函数用于交换数组中两个元素的值。

function swap(arr, i, j) {
  const temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}
可视化

下面将使用 D3.js 来可视化堆排序的执行过程。

D3.js 是一个 JavaScript 库,它可以帮助我们快速地创建交互式的数据可视化,支持多种数据可视化类型,包括折线图、条形图、散点图等。

function visualize(arr) {
  // 创建 SVG 元素
  const svg = d3.select('#chart')
              .append('svg')
              .attr('width', '900')
              .attr('height', '400');

  // 计算元素位置
  const margin = { top: 50, right: 50, bottom: 50, left: 50 };
  const width = 900 - margin.left - margin.right;
  const height = 400 - margin.top - margin.bottom;
  const xScale = d3.scaleLinear()
                  .domain([0, arr.length - 1])
                  .range([0, width]);
  const yScale = d3.scaleLinear()
                  .domain([0, d3.max(arr)])
                  .range([height, 0]);

  // 绘制矩形
  svg.selectAll('rect')
      .data(arr)
      .enter()
      .append('rect')
      .attr('x', (d, i) => xScale(i))
      .attr('y', (d) => yScale(d))
      .attr('width', 20)
      .attr('height', (d) => height - yScale(d))
      .attr('fill', 'steelblue');

  // 执行排序并可视化
  heapSortVisualization(arr, svg, xScale, yScale);
}

function heapSortVisualization(arr, svg, xScale, yScale) {
  const len = arr.length;
  const lastNodeIndex = len >> 1;
  let tempArr = [...arr];

  // 建堆可视化
  for (let i = lastNodeIndex; i >= 0; i--) {
    setTimeout(() => {
      heapifyVisualization(tempArr, svg, xScale, yScale, i, len - 1, true);
    }, (i + 1) * 1000);
  }

  // 排序可视化
  for (let i = len - 1; i > 0; i--) {
    setTimeout(() => {
      swap(tempArr, 0, i);
      heapifyVisualization(tempArr, svg, xScale, yScale, 0, i - 1, false);
      updateRect(tempArr, svg, xScale, yScale);
    }, (len - i) * 1000);
  }
}

function heapifyVisualization(arr, svg, xScale, yScale, nodeIndex, lastNodeIndex, isBuild) {
  let leftNodeIndex = 2 * nodeIndex + 1;
  let rightNodeIndex = 2 * nodeIndex + 2;
  let maxIndex = nodeIndex;

  if (leftNodeIndex <= lastNodeIndex && arr[leftNodeIndex] > arr[maxIndex]) {
    maxIndex = leftNodeIndex;
  }
  
  if (rightNodeIndex <= lastNodeIndex && arr[rightNodeIndex] > arr[maxIndex]) {
    maxIndex = rightNodeIndex;
  }

  if (maxIndex !== nodeIndex) {
    swap(arr, nodeIndex, maxIndex);

    if (isBuild) {
      updateRect(arr, svg, xScale, yScale);
    } else {
      setTimeout(() => {
        updateRect(arr, svg, xScale, yScale);
      }, 500);
    }
    
    heapifyVisualization(arr, svg, xScale, yScale, maxIndex, lastNodeIndex, isBuild);
  }
}

function updateRect(arr, svg, xScale, yScale) {
  svg.selectAll('rect')
      .data(arr)
      .transition()
      .duration(500)
      .attr('y', (d) => yScale(d))
      .attr('height', (d) => yScale(0) - yScale(d));
}

效果如下:

堆排序可视化

总结

使用 JavaScript 实现堆排序是一个比较简单的过程。通过可视化可以更好地理解算法的执行过程。D3.js 是一个非常强大的数据可视化库,可以帮助我们快速地绘制各种类型的图表,并提供了一些趁手的交互功能。