Skip to content

Commit 0c1f685

Browse files
committed
Add counting sort.
1 parent b1a613e commit 0c1f685

File tree

5 files changed

+202
-0
lines changed

5 files changed

+202
-0
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ a set of rules that precisely defines a sequence of operations.
8080
* [Merge Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/merge-sort)
8181
* [Quicksort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/quick-sort) - in-place and non-in-place implementations
8282
* [Shellsort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/shell-sort)
83+
* [Counting Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/counting-sort)
8384
* **Tree**
8485
* [Depth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/depth-first-search) (DFS)
8586
* [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/breadth-first-search) (BFS)
@@ -225,3 +226,4 @@ Below is the list of some of the most used Big O notations and their performance
225226
| **Merge sort** | n log(n) | n log(n) | n log(n) | n | Yes |
226227
| **Quick sort** | n log(n) | n log(n) | n^2 | log(n) | No |
227228
| **Shell sort** | n log(n) | depends on gap sequence | n (log(n))^2 | 1 | No |
229+
| **Counting sort** | n + r | n + r | n + r | n + r | Yes |

src/algorithms/sorting/SortTester.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export class SortTester {
1111
expect(sorter.sort([1])).toEqual([1]);
1212
expect(sorter.sort([1, 2])).toEqual([1, 2]);
1313
expect(sorter.sort([2, 1])).toEqual([1, 2]);
14+
expect(sorter.sort([3, 4, 2, 1, 0, 0, 4, 3, 4, 2])).toEqual([0, 0, 1, 2, 2, 3, 3, 4, 4, 4]);
1415
expect(sorter.sort(sortedArr)).toEqual(sortedArr);
1516
expect(sorter.sort(reverseArr)).toEqual(sortedArr);
1617
expect(sorter.sort(notSortedArr)).toEqual(sortedArr);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import Sort from '../Sort';
2+
3+
export default class CountingSort extends Sort {
4+
/**
5+
* @param {number[]} originalArray
6+
* @param {number} [biggestElement]
7+
*/
8+
sort(originalArray, biggestElement = 0) {
9+
// Detect biggest element in array in order to build in order to build
10+
// number bucket array later.
11+
let detectedBiggestElement = biggestElement;
12+
if (!detectedBiggestElement) {
13+
originalArray.forEach((element) => {
14+
// Visit element.
15+
this.callbacks.visitingCallback(element);
16+
17+
if (this.comparator.greaterThan(element, detectedBiggestElement)) {
18+
detectedBiggestElement = element;
19+
}
20+
});
21+
}
22+
23+
// Init buckets array.
24+
// This array will hold frequency of each number from originalArray.
25+
const buckets = Array(detectedBiggestElement + 1).fill(0);
26+
originalArray.forEach((element) => {
27+
// Visit element.
28+
this.callbacks.visitingCallback(element);
29+
30+
buckets[element] += 1;
31+
});
32+
33+
// Add previous frequencies to the current one for each number in bucket
34+
// to detect how many numbers less then current one should be standing to
35+
// the left of current one.
36+
for (let bucketIndex = 1; bucketIndex < buckets.length; bucketIndex += 1) {
37+
buckets[bucketIndex] += buckets[bucketIndex - 1];
38+
}
39+
40+
// Now let's shift frequencies to the right so that they show correct numbers.
41+
// I.e. if we won't shift right than the value of buckets[5] will display how many
42+
// elements less than 5 should be placed to the left of 5 in sorted array
43+
// INCLUDING 5th. After shifting though this number will not include 5th anymore.
44+
buckets.pop();
45+
buckets.unshift(0);
46+
47+
// Now let's assemble sorted array.
48+
const sortedArray = Array(originalArray.length).fill(null);
49+
for (let elementIndex = 0; elementIndex < originalArray.length; elementIndex += 1) {
50+
// Get the element that we want to put into correct sorted position.
51+
const element = originalArray[elementIndex];
52+
53+
// Visit element.
54+
this.callbacks.visitingCallback(element);
55+
56+
// Get correct position of this element in sorted array.
57+
const elementSortedPosition = buckets[element];
58+
59+
// Put element into correct position in sorted array.
60+
sortedArray[elementSortedPosition] = element;
61+
62+
// Increase position of current element in the bucket for future correct placements.
63+
buckets[element] += 1;
64+
}
65+
66+
// Return sorted array.
67+
return sortedArray;
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Counting Sort
2+
3+
In computer science, **counting sort** is an algorithm for sorting
4+
a collection of objects according to keys that are small integers;
5+
that is, it is an integer sorting algorithm. It operates by
6+
counting the number of objects that have each distinct key value,
7+
and using arithmetic on those counts to determine the positions
8+
of each key value in the output sequence. Its running time is
9+
linear in the number of items and the difference between the
10+
maximum and minimum key values, so it is only suitable for direct
11+
use in situations where the variation in keys is not significantly
12+
greater than the number of items. However, it is often used as a
13+
subroutine in another sorting algorithm, radix sort, that can
14+
handle larger keys more efficiently.
15+
16+
Because counting sort uses key values as indexes into an array,
17+
it is not a comparison sort, and the `Ω(n log n)` lower bound for
18+
comparison sorting does not apply to it. Bucket sort may be used
19+
for many of the same tasks as counting sort, with a similar time
20+
analysis; however, compared to counting sort, bucket sort requires
21+
linked lists, dynamic arrays or a large amount of preallocated
22+
memory to hold the sets of items within each bucket, whereas
23+
counting sort instead stores a single number (the count of items)
24+
per bucket.
25+
26+
Counting sorting works best when the range of numbers for each array
27+
element is very small.
28+
29+
## Algorithm
30+
31+
**Step I**
32+
33+
In first step we calculate the count of all the elements of the
34+
input array `A`. Then Store the result in the count array `C`.
35+
The way we count is depected below.
36+
37+
![Counting Sort](https://3.bp.blogspot.com/-jJchly1BkTc/WLGqCFDdvCI/AAAAAAAAAHA/luljAlz2ptMndIZNH0KLTTuQMNsfzDeFQCLcB/s1600/CSortUpdatedStepI.gif)
38+
39+
**Step II**
40+
41+
In second step we calculate how many elements exist in the input
42+
array `A` which are less than or equals for the given index.
43+
`Ci` = numbers of elements less than or equals to `i` in input array.
44+
45+
![Counting Sort](https://1.bp.blogspot.com/-1vFu-VIRa9Y/WLHGuZkdF3I/AAAAAAAAAHs/8jKu2dbQee4ap9xlVcNsILrclqw0UxAVACLcB/s1600/Step-II.png)
46+
47+
**Step III**
48+
49+
In this step we place the input array `A` element at sorted
50+
position by taking help of constructed count array `C` ,i.e what
51+
we constructed in step two. We used the result array `B` to store
52+
the sorted elements. Here we handled the index of `B` start from
53+
zero.
54+
55+
![Counting Sort](https://1.bp.blogspot.com/-xPqylngqASY/WLGq3p9n9vI/AAAAAAAAAHM/JHdtXAkJY8wYzDMBXxqarjmhpPhM0u8MACLcB/s1600/ResultArrayCS.gif)
56+
57+
## References
58+
59+
- [Wikipedia](https://en.wikipedia.org/wiki/Counting_sort)
60+
- [YouTube](https://www.youtube.com/watch?v=OKd534EWcdk&index=61&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8)
61+
- [EfficientAlgorithms](https://efficientalgorithms.blogspot.com/2016/09/lenear-sorting-counting-sort.html)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import CountingSort from '../CountingSort';
2+
import {
3+
equalArr,
4+
notSortedArr,
5+
reverseArr,
6+
sortedArr,
7+
SortTester,
8+
} from '../../SortTester';
9+
10+
// Complexity constants.
11+
const SORTED_ARRAY_VISITING_COUNT = 60;
12+
const NOT_SORTED_ARRAY_VISITING_COUNT = 60;
13+
const REVERSE_SORTED_ARRAY_VISITING_COUNT = 60;
14+
const EQUAL_ARRAY_VISITING_COUNT = 60;
15+
16+
describe('CountingSort', () => {
17+
it('should sort array', () => {
18+
SortTester.testSort(CountingSort);
19+
});
20+
21+
it('should allow to use specify maximum integer value in array to make sorting faster', () => {
22+
const visitingCallback = jest.fn();
23+
const sorter = new CountingSort({ visitingCallback });
24+
25+
// Detect biggest number in array in prior.
26+
const biggestElement = notSortedArr.reduce((accumulator, element) => {
27+
return element > accumulator ? element : accumulator;
28+
}, 0);
29+
30+
const sortedArray = sorter.sort(notSortedArr, biggestElement);
31+
32+
expect(sortedArray).toEqual(sortedArr);
33+
// Normally visitingCallback is being called 60 times but in this case
34+
// it should be called only 40 times.
35+
expect(visitingCallback).toHaveBeenCalledTimes(40);
36+
});
37+
38+
it('should visit EQUAL array element specified number of times', () => {
39+
SortTester.testAlgorithmTimeComplexity(
40+
CountingSort,
41+
equalArr,
42+
EQUAL_ARRAY_VISITING_COUNT,
43+
);
44+
});
45+
46+
it('should visit SORTED array element specified number of times', () => {
47+
SortTester.testAlgorithmTimeComplexity(
48+
CountingSort,
49+
sortedArr,
50+
SORTED_ARRAY_VISITING_COUNT,
51+
);
52+
});
53+
54+
it('should visit NOT SORTED array element specified number of times', () => {
55+
SortTester.testAlgorithmTimeComplexity(
56+
CountingSort,
57+
notSortedArr,
58+
NOT_SORTED_ARRAY_VISITING_COUNT,
59+
);
60+
});
61+
62+
it('should visit REVERSE SORTED array element specified number of times', () => {
63+
SortTester.testAlgorithmTimeComplexity(
64+
CountingSort,
65+
reverseArr,
66+
REVERSE_SORTED_ARRAY_VISITING_COUNT,
67+
);
68+
});
69+
});

0 commit comments

Comments
 (0)