📜  双向链表上快速排序的 Javascript 程序(1)

📅  最后修改于: 2023-12-03 15:37:05.322000             🧑  作者: Mango

双向链表上快速排序的 Javascript 程序

在数据结构和算法中,快速排序是一种高效的排序算法。在该算法中,通过选择一个“pivot”元素,将待排序的数组分为两个部分,其中一个部分的元素都小于pivot,另一个部分都大于pivot。然后,递归地对两个子列表进行排序,直到整个列表有序。由于分割列表非常快,因此快速排序是一种高效的排序算法。

本文将介绍如何使用 Javascript 实现双向链表上的快速排序。我们将包括如何实现链表节点、链表、快速排序以及用于测试的代码。让我们开始!

链表节点

链表中的每个节点都包含一个值和一个指向前一个节点的指针以及一个指向下一个节点的指针。下面是一个简单的 Node 类,表示一个单个的链表节点。

class Node {
  constructor(value, prev = null, next = null) {
    this.value = value;
    this.prev = prev;
    this.next = next;
  }
}
链表

链表通过指向它的头节点来启动。头节点是链表的入口点。链表提供了用于插入,删除和访问元素的方法。下面是一个简单的 LinkedList 类。

class LinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
    this.size = 0;
  }

  add(value) {
    const node = new Node(value);

    if (this.head === null) {
      this.head = node;
      this.tail = node;
    } else {
      node.prev = this.tail;
      this.tail.next = node;
      this.tail = node;
    }

    this.size++;
  }

  *[Symbol.iterator]() {
    let node = this.head;

    while (node !== null) {
      yield node.value;
      node = node.next;
    }
  }
}
快速排序

快速排序算法实现在链表上有点不同。由于链表是不连续的,因此我们不能通过从中间分割列表来获得最佳性能。因此,我们选择第一个元素作为中间元素,并进行快速排序。

为了方便起见,我们编写了一个递归函数,用于对链表进行排序。该函数将快速排序算法应用于链表,并返回已排序链表的头节点。

function quickSortLinkedList(list) {
  if (list === null || list.head === null) {
    return list;
  }

  // Partition the list using the first element as the middle element.
  const [left, middle, right] = partitionLinkedList(list);

  // Recursively sort the left and right partitions.
  const sortedLeft = quickSortLinkedList(left);
  const sortedRight = quickSortLinkedList(right);

  // Concatenate the left partition, middle element, and right partition.
  const sortedList = concatenateLinkedList(sortedLeft, middle, sortedRight);

  return sortedList;
}
分隔链表

下面是 partitionLinkedList 函数的实现。该函数将链表分成三个部分:

  1. 左边包含所有小于中间元素的元素。
  2. 中间是中间元素本身。
  3. 右侧包含所有大于中间元素的元素。
function partitionLinkedList(list) {
  const pivot = list.head;
  let left = null;
  let middle = pivot;
  let right = null;

  let node = list.head.next;
  while (node !== null) {
    const next = node.next;

    if (node.value < pivot.value) {
      node.prev = left;
      node.next = (left === null) ? null : left.next;
      if (left !== null) {
        left.next.prev = node;
        left.next = node;
      } else {
        list.head = node;
      }
      left = node;
    } else if (node.value > pivot.value) {
      node.prev = right;
      node.next = (right === null) ? null : right.next;
      if (right !== null) {
        right.next.prev = node;
        right.next = node;
      } else {
        list.tail = node;
      }
      right = node;
    } else {
      node.prev = middle;
      node.next = (middle === null) ? null : middle.next;
      if (middle !== null) {
        middle.next.prev = node;
        middle.next = node;
      } else {
        list.head = node;
        list.tail = node;
      }
      middle = node;
    }

    node = next;
  }

  return [new LinkedList(left), new LinkedList(middle), new LinkedList(right)];
}
连接链表

下面是 concatenateLinkedList 函数的实现。该函数将三个链接列表(leftmiddleright)组合成一个链接列表。函数从左到右连接每个部分的节点。

function concatenateLinkedList(leftList, middleList, rightList) {
  if (middleList === null || middleList.head === null) {
    return concatenateLinkedList(concatenateLinkedList(leftList, null, null), concatenateLinkedList(null, null, rightList), null);
  }

  let result = middleList;
  let tail = middleList.tail;

  if (leftList !== null && leftList.head !== null) {
    leftList.tail.next = middleList.head;
    middleList.head.prev = leftList.tail;
    result = leftList;
  }

  if (rightList !== null && rightList.head !== null) {
    middleList.tail.next = rightList.head;
    rightList.head.prev = middleList.tail;
    tail = rightList.tail;
  }

  result.head.prev = null;
  tail.next = null;

  return result;
}
测试

为了测试我们的代码,我们将使用 Jest JavaScript 测试框架。首先,我们需要安装 Jest 并编写一个简单的测试文件。

环境设置

以下是必要的依赖项,使用npm install命令安装。

npm install jest --save-dev
npm install babel-jest @babel/core @babel/preset-env --save-dev

需要创建一个文件 .babelrc 包含以下内容:

{
  "presets": ["@babel/preset-env"]
}

我们创建了一个 test.js 文件,用于测试我们实现的链表和快速排序程序。

const LinkedList = require('./linked-list');
const quickSortLinkedList = require('./quick-sort-linked-list');

describe('LinkedList', () => {
  test('Constructor', () => {
    const linkedList = new LinkedList();
    expect(linkedList.head).toBeNull();
    expect(linkedList.tail).toBeNull();
    expect(linkedList.size).toBe(0);
  });

  test('Add', () => {
    const linkedList = new LinkedList();

    linkedList.add(1);
    linkedList.add(2);
    linkedList.add(3);

    expect([...linkedList]).toEqual([1, 2, 3]);
    expect(linkedList.size).toBe(3);
  });
});

describe('QuickSortLinkedList', () => {
  test('Empty List', () => {
    const emptyList = new LinkedList();
    const sortedList = quickSortLinkedList(emptyList);

    expect(sortedList).toBe(emptyList);
  });

  test('Single Item', () => {
    const list = new LinkedList([1]);
    const sortedList = quickSortLinkedList(list);

    expect([...sortedList]).toEqual([1]);
    expect(sortedList.size).toBe(1);
  });

  test('Multiple Items', () => {
    const list = new LinkedList();
    list.add(3);
    list.add(2);
    list.add(1);
    list.add(5);
    list.add(4);

    const sortedList = quickSortLinkedList(list);

    expect([...sortedList]).toEqual([1, 2, 3, 4, 5]);
    expect(sortedList.size).toBe(5);
  });
});
工具函数

下面是测试代码中使用的一些工具函数。

function toArray(list) {
  return [...list];
}

function fromArray(arr) {
  const linkedList = new LinkedList();
  arr.forEach((value) => linkedList.add(value));
  return linkedList;
}
运行测试

使用以下命令运行测试。

npm test

如果一切正常,您将在控制台上看到测试结果。

PASS  ./test.js
  LinkedList
    ✓ Constructor (3ms)
    ✓ Add
  QuickSortLinkedList
    ✓ Empty List
    ✓ Single Item
    ✓ Multiple Items (1ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        1.002s, estimated 2s
Ran all test suites.
结论

在本文中,我们学习了如何在 Javascript 中实现链表和快速排序算法。链表通过链接节点而不是在内存中分配连续的空间来存储元素,因此适用于某些算法。快速排序算法通过选择一个元素将待排序列表分成两个部分,递归地应用到每个部分,并最终合并以获得整个有序列表。

我们使用 Jest 框架编写了一些测试,以验证我们的代码是否按预期工作。完成测试后,我们可以确定我们的代码按预期工作。