Chapters

Hide chapters

Data Structures & Algorithms in Dart

First Edition · Flutter · Dart 2.15 · VS Code 1.63

Section VI: Challenge Solutions

Section 6: 20 chapters
Show chapters Hide chapters

17. Radix Sort
Written by Kelvin Lau & Jonathan Sande

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In this chapter, you’ll look at a completely different model of sorting. So far, you’ve been relying on comparisons to determine the sorting order. Radix sort is a non-comparative algorithm for sorting integers. The word radix means base, as in the base of a number system. For example, decimal is base 10 and binary is base 2. You can use any base with a radix sort, but to keep things simple, this chapter will focus on sorting base-10 integers.

Radix sort relies on the position of digits within a number. For example, there are four digits in the number 1772. The least significant digit is 2 since this is in the ones place. The most significant digit is 1 since this is the thousands-place value, greater than any other place value in this particular number. The following diagram shows the place values of a four-digit number:

1 thousands 7 hundreds 7 tens 2 ones

There are multiple implementations of radix sort that focus on different problems. One type sorts by the least significant digit (LSD) and another by the most significant digit (MSD).

You’ll learn about both LSD- and MSD-radix sorting in this chapter.

Sorting by Least Significant Digit

Before you implement a radix sort in code, go through the following example to get an intuitive idea of how the sort works.

Example

An LSD-radix sort starts by looking at the least significant digits in a list of numbers. For example, the image below shows the numbers 88, 410, 1772 and 20 vertically aligned with a box drawn around the ones place:

5 2 7 8 5 1 2 1 0 7 6

Round One

You start by making ten buckets to sort the numbers into. You can think of this like sorting fruit. You put the apples in the apple bucket, the oranges in the oranges bucket, and the pears in the pear bucket.

6 7 9 8 1 3 7 4 9 0

3 31 027 1742 88 2 3 3 8 0 2 7 2 4

7 0 9 0 3 0 3 2 8 7 4

Round Two

For round two, you look at the next significant digit, the tens-place value:

6 5 8 4 0 1 0 1 2 0 9

3 2 6 6 6 5 7 3 8 1

8 58 36 5 8 0 1 2 0 3 2 2 147 5522

8 6 3 5 3 9 5 6 6 9 0

Round Three

This time look at the hundreds place. Since 20 and 88 are less than 100, you can use 0s for the hundreds place:

5 4 9 4 1 6 5 8 2 8 5 5 3

4 0 9 9 8 1 0 9 5 2 956 68 24 3432

5 1 8 1 0 7 7 9 3 4 4 0 0

Round Four

In this algorithm of radix sort, there’s one round for every significant digit. Since 1722 has four digits, this fourth round will ensure that the thousands-place value is also sorted. Pad any numbers less than one thousand with zeros in front:

8 6 2 9 1 6 5 4 3 2 7 7 7 0 8 4

3 6 3 0 4 7 1 8 7 8 4086 99 72 567

9 1 9 3 3 4 1 3 5 9 9

Implementation

To implement LSD-radix sort, you’ll use a while loop for each round as you step through the place values. Your ten buckets will be ten integer lists.

extension RadixSort on List<int> {
  void radixSort() {
    const base = 10;
    var done = false;
    // 1
    var place = 1;
    while (!done) {
      done = true;
      // 2
      final buckets = List.generate(base, (_) => <int>[]);
      forEach((number) {
        // 3
        final remainingPart = number ~/ place;
        final digit = remainingPart % base;
        // 4
        buckets[digit].add(number);
        if (remainingPart ~/ base > 0) {
          done = false;
        }
      });
      // 5
      place *= base;
      clear();
      addAll(buckets.expand((element) => element));
    }
  }
}

Testing it Out

With that, you’ve implemented your first non-comparative sorting algorithm. Head back to bin/start.dart and replace the contents of the file with the following code:

import 'package:starter/radix_sort.dart';

void main() {
  final list = [88, 410, 1772, 20];
  print("Original list: $list");
  list.radixSort();
  print("Radix sorted: $list");
}
Original: [88, 410, 1772, 20]
Radix sorted: [20, 88, 410, 1772]

Sorting by Most Significant Digit

The MSD-radix sort uses the most significant digit to sort a list. You might think that’s a little strange when you look at a list of numbers sorted in this way:

0 0 4 2 7 0 7 0 6 4 3 9 2 4 5 2 2

j o o z j q e f e h i i v j e a o a b

Example

Start with the following unsorted list:

[46, 500, 459, 1345, 13, 999]

Beginning the Sort

As with LSD-radix sort, you first create ten buckets. However, this time you add the numbers to the buckets according to their most significant digit, that is, the first digit on their left:

2 5 7 7 3 5 6 0 5 4 85 1749 933 916 024 55

Recursively Sorting 1345 and 13

In the previous image, the ones bucket and the fours bucket both have more than one element. Start with the ones bucket, which contains 1345 and 13. Take those numbers and perform another bucket sort, this time on the second most significant digit:

2 4 9 7 6 0 4 4 0 3 82 0047

7 5 9 6 6 2 6 5 9 4 jkaayitn 07 7162

Recursively Sorting 46 and 459

Now you need to do the same thing with 46 and 459. Perform a bucket sort on the second digit. That gives the following:

2 0 3 4 8 4 9 1 6 6 868 32

Combining All the Buckets

There were no other buckets with more than one number from the original round, so you’re finished. Combine all of the buckets and sorted sublists into the final sorted result of [13, 1345, 459, 46, 500, 999]:

8 0 6 6 6 4 8 8 0 8 8 2 2 5 6 6 6

Implementation

To start off, you’ll write a few helper methods.

Getting the Number of Digits

Open lib/radix_sort.dart again and add the following int extension to the file:

extension Digits on int {
  static const _base = 10;

  int digits() {
    var count = 0;
    var number = this;
    while (number != 0) {
      count += 1;
      number ~/= _base;
    }
    return count;
  }
}
print(13.digits());    // 2
print(999.digits());   // 3
print(1345.digits());  // 4

Finding the Digit at Some Position

Add the following import to lib/radix_sort.dart:

import 'dart:math';
int? digitAt(int position) {
  if (position >= digits()) {
    return null;
  }
  var number = this;
  while (number ~/ pow(_base, position + 1) != 0) {
    number ~/= _base;
  }
  return number % _base;
}
print(1345.digitAt(0)); // 1
print(1345.digitAt(1)); // 3
print(1345.digitAt(2)); // 4
print(1345.digitAt(3)); // 5
print(1345.digitAt(4)); // null
print(1345.digitAt(5)); // null

Finding the Max Digits in the List

Go back to lib/radix_sort.dart and add a new List extension with the following method:

extension MsdRadixSort on List<int> {
  int maxDigits() {
    if (isEmpty) return 0;
    return reduce(max).digits();
  }

  // more to come
}
final list = [46, 500, 459, 1345, 13, 999];
print(list.maxDigits()); // 4

Adding the Recursive Sort Methods

Go back to the MsdRadixSort extension on List in lib/radix_sort.dart. You’re going to add two more methods to this extension. One will be public and the other a private recursive helper method.

void lexicographicalSort() {
  final sorted = _msdRadixSorted(this, 0);
  clear();
  addAll(sorted);
}

// more to come
// 1
List<int> _msdRadixSorted(List<int> list, int position) {
  // 2
  if (list.length < 2 || position >= list.maxDigits()) {
    return list;
  }
  // 3
  final buckets = List.generate(10, (_) => <int>[]);
  // 4
  var priorityBucket = <int>[];

  // more to come
}
for (var number in list) {
  final digit = number.digitAt(position);
  if (digit == null) {
    priorityBucket.add(number);
    continue;
  }
  buckets[digit].add(number);
}

// more to come
// 1
final bucketOrder = buckets.reduce((result, bucket) {
  if (bucket.isEmpty) return result;
  // 2
  final sorted = _msdRadixSorted(bucket, position + 1);
  return result..addAll(sorted);
});
// 3
return priorityBucket..addAll(bucketOrder);

Testing it Out

Open bin/starter.dart again and run the following code in main:

final list = [46, 500, 459, 1345, 13, 999];
list.lexicographicalSort();
print(list);
[13, 1345, 459, 46, 500, 999]

Challenges

The challenges below will help strengthen your understanding of radix sorts. You can find the answers in the Challenge Solutions section or in the supplementary materials that accompany this book.

Challenge 1: What Are in the Buckets?

Add a print statement to your radixSort implementation so that it’ll tell you what’s in the buckets after each round of sorting.

var list = [46, 500, 459, 1345, 13, 999];

Challenge 2: Unique Characters

Write a function that returns the total number of unique characters used in a list of words.

final words = ['done', 'ad', 'eel', 'zoo', 'adept', 'do'];

Challenge 3: Optimization

Given the following list:

[88, 410, 1772, 20, 123456789876543210]

Key Points

  • Unlike the other sorting algorithms you’ve implemented in previous chapters, radix sort doesn’t rely on comparing two values. It leverages bucket sort, which is a way to sort numbers by their digits.
  • The word radix means base, as in base-10 or base-2 numbering systems. The internal bucket sort will use one bucket for each base.
  • Radix sort can be one of the fastest sorting algorithms for sorting values with positional notation.
  • A least-significant-digit (LSD) radix sort begins sorting with the right-most digit.
  • Another way to implement radix sort is the most-significant-digit (MSD) form. This form sorts by prioritizing the left-most digits over the lesser ones to the right. It’s best illustrated by the sorting behavior of the String type.
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 accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now