Chapters

Hide chapters

Data Structures & Algorithms in Dart

Second Edition · Flutter · Dart 3.0 · VS Code 1.78

Section VI: Challenge Solutions

Section 6: 21 chapters
Show chapters Hide chapters

19. Heapsort
Written by Jonathan Sande

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Heapsort is a comparison-based algorithm that sorts a list in ascending order using a heap. This chapter builds on the heap concepts presented in Chapter 14, “Heaps”.

Heapsort takes advantage of a heap being, by definition, a partially sorted binary tree with the following qualities:

  1. In a max-heap, all parent nodes are larger than or equal to their children.
  2. In a min-heap, all parent nodes are smaller than or equal to their children.

The diagram below shows a max- and min-heap with parent node values highlighted:

Max heap 4 1 5 8 10 Min heap 4 8 5 2 1

Once you have a heap, the sorting algorithm repeatedly removes the highest priority value from the top of the heap.

Example

A concrete example of how heapsort works will help to make things more clear.

Building a Heap

The first step in a heapsort algorithm is to create a heap from an unsorted list.

4 4 0 24 05 80 10 4 0

9 4 6 30 0 1 74 97 40

60 5 80 0 63 87 7 9 3

Sorting the List

Once you have a heap, you can go on to use its properties to sort the list in ascending order.

5 14 1 07 10 1 7 21 8

8 75 4 81 64 6 3 1 89

32 4 96 7 24 8 6 01 2

17 9 1 7 97 76 1 7 38

9 8 91 0 1 3 96 85 55 6 5 9 65 5 3 93 16 33

8 1 0 2 5 35 79 95 69 04 2 4 4 7 8 41 60 45

1 4 7 3 92 49 40 37 3 8 61 2 6 6 9 16 98 20

8 4 9 93 63 33 31 9 7 5 8 86 8 8 5 97 94 33

3 5 42 90 95 12 2 4 0 0 2 0 38 4 8 00 78 68

0 40 45 16 12 4 4 3 2 6 4 8 6 76 3 22 53 38

8 9 0 8 1 08 39 99 42

Implementation

You’re going to implement two versions of heapsort. The first one will use the Heap class you created in Chapter 14, “Heaps”. It’s quite easy to implement. However, it won’t follow the exact algorithm described in the example above. For your second implementation, though, you will follow this algorithm. Although the second implementation will take a little more work, space efficiency will improve.

Using Your Heap Class

Open the starter project for this chapter. In lib/heap.dart, you’ll find the Heap class you created in Chapter 14.

import 'heap.dart';

List<E> heapsort<E extends Comparable<E>>(List<E> list) {
  // 1
  final heap = Heap<E>(
    elements: list.toList(),
    priority: Priority.min,
  );
  // 2
  final sorted = <E>[];
  // 3
  while (!heap.isEmpty) {
    final value = heap.remove();
    sorted.add(value!);
  }
  return sorted;
}
import 'package:starter/heapsort.dart';

void main() {
  final sorted = heapsort<num>([6, 12, 2, 26, 8, 18, 21, 9, 5]);
  print(sorted);
}
[2, 5, 6, 8, 9, 12, 18, 21, 26]

Sorting in Place

In this section, you’ll rewrite heapsort without using the Heap class. The sort will take an input list and mutate that list in place in the manner described in the example section.

Creating an Extension

Since you want to mutate the list itself, you can create an extension method on List. Add the following code at the bottom of lib/heapsort.dart:

extension Heapsort<E extends Comparable<E>> on List<E> {

  // more to come
}

Preparing Private Helper Methods

Before implementing the main sort method, you need to add a few other helper methods. They’re just a copy-and-paste of what you wrote earlier when you made the Heap class.

int _leftChildIndex(int parentIndex) {
  return 2 * parentIndex + 1;
}

int _rightChildIndex(int parentIndex) {
  return 2 * parentIndex + 2;
}

void _swapValues(int indexA, int indexB) {
  final temp = this[indexA];
  this[indexA] = this[indexB];
  this[indexB] = temp;
}

Modifying the Sift Method

You also need a method to help you sift the root index down. This one is a little different than the one in Heap. Add the following code to Heapsort extension:

// 1
void _siftDown({required int start, required int end}) {
  var parent = start;
  while (true) {
    final left = _leftChildIndex(parent);
    final right = _rightChildIndex(parent);
    var chosen = parent;
    // 2
    if (left < end &&
        this[left].compareTo(this[chosen]) > 0) {
      chosen = left;
    }
    // 3
    if (right < end &&
        this[right].compareTo(this[chosen]) > 0) {
      chosen = right;
    }
    if (chosen == parent) return;
    _swapValues(parent, chosen);
    parent = chosen;
  }
}

Adding the Main Extension Method

Now you’re finally ready to perform the actual in-place heapsort. Add the following method to the Heapsort extension:

void heapsortInPlace() {
  if (isEmpty) return;
  // 1
  final start = length ~/ 2 - 1;
  for (var i = start; i >= 0; i--) {
    _siftDown(start: i, end: length);
  }
  // 2
  for (var end = length - 1; end > 0; end--) {
    _swapValues(0, end);
    _siftDown(start: 0, end: end);
  }
}

Testing it Out

To check that your in-place heapsort works, head back to bin/starter.dart and replace the contents of main with the following code:

final list = <num>[6, 12, 2, 26, 8, 18, 21, 9, 5];
print(list);
list.heapsortInPlace();
print(list);
[6, 12, 2, 26, 8, 18, 21, 9, 5]
[2, 5, 6, 8, 9, 12, 18, 21, 26]

Performance

The performance of heapsort is O(n log n) for its best, worst and average cases. This uniformity in performance is because you have to traverse the whole list once, and every time you swap elements, you must perform a down-sift, which is an O(log n) operation.

Challenges

Here are a couple of small challenges to test your knowledge of heapsort. You can find the answers in the Challenge Solutions section as well as in the supplementary materials that accompany the book.

Challenge 1: Theory

When performing heapsort in ascending order, which of these starting lists requires the fewest comparisons?

Challenge 2: Descending Order

The current implementations of heapsort in this chapter sort the elements in ascending order. How would you sort in descending order?

Key Points

  • Heapsort leverages the heap data structure to sort elements in a list.
  • The algorithm works by moving the values from the top of the heap to an ordered list. This can be performed in place if you use an index to separate the end of the heap from the sorted list elements.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now