Skip to content

Commit 11c606c

Browse files
committed
feat: Add Duval's algorithm for the lexicographically smallest rotation in a sequence.
1 parent 15e3fed commit 11c606c

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

strings/duval.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* @file duval.cpp
3+
* @brief Implementation of [Duval's algorithm](https://en.wikipedia.org/wiki/Lyndon_word).
4+
*
5+
* @details
6+
* Duval's algorithm is an algorithm to find the lexicographically smallest
7+
* rotation of a string. It is based on the concept of Lyndon words.
8+
* Lyndon words are defined as the lexicographically smallest string in a
9+
* rotation equivalence class. A rotation equivalence class is a set of strings
10+
* that can be obtained by rotating a string. For example, the rotation
11+
* equivalence class of "abc" is {"abc", "bca", "cab"}. The lexicographically
12+
* smallest string in this class is "abc".
13+
* Duval's algorithm works by finding the lexicographically smallest Lyndon word
14+
* in a string. It does this by iterating over the string and finding the
15+
* smallest rotation of the string that is a Lyndon word. This is done by
16+
* comparing the string with its suffixes and finding the smallest suffix that
17+
* is lexicographically smaller than the string. This suffix is then added to
18+
* the result and the process is repeated with the remaining string.
19+
* The algorithm has a time complexity of O(n) where n is the length of the
20+
* string.
21+
*
22+
* @note While Lyndon word are described in the context of strings,
23+
* Duval's algorithm can be used to find the lexicographically smallest cyclic
24+
* shift of any sequence of comparable elements.
25+
*
26+
* @author [Amine Ghoussaini](https://github.com/aminegh20)
27+
*/
28+
29+
#include <array> ///< for std::array
30+
#include <cassert> ///< for assert
31+
#include <cstddef> ///< for std::size_t
32+
#include <deque> ///< for std::deque
33+
#include <iostream> ///< for std::cout and std::endl
34+
#include <string> ///< for std::string
35+
#include <vector> ///< for std::vector
36+
37+
namespace string {
38+
/**
39+
* @brief Find the lexicographically smallest cyclic shift of a sequence.
40+
* @tparam T type of the sequence
41+
* @param s the sequence
42+
* @returns the 0-indexed position of the least cyclic shift of the sequence
43+
*/
44+
template <typename T>
45+
size_t duval(const T& s) {
46+
std::size_t n = s.size();
47+
size_t i = 0, ans = 0;
48+
while (i < n) {
49+
ans = i;
50+
size_t j = i + 1, k = i;
51+
while (j < n + n && s[j % n] >= s[k % n]) {
52+
if (s[k % n] < s[j % n]) {
53+
k = i;
54+
} else {
55+
k++;
56+
}
57+
j++;
58+
}
59+
while (i <= k) {
60+
i += j - k;
61+
}
62+
}
63+
return ans;
64+
// returns 0-indexed position of the least cyclic shift
65+
}
66+
67+
} // namespace string
68+
69+
static void test() {
70+
using namespace string;
71+
72+
// Test 1
73+
std::string s1 = "abcab";
74+
assert(duval(s1) == 3);
75+
76+
// Test 2
77+
std::string s2 = "011100";
78+
assert(duval(s2) == 4);
79+
80+
// Test 3
81+
std::vector<int> v = {5, 2, 1, 3, 4};
82+
assert(duval(v) == 2);
83+
84+
// Test 4
85+
std::array<int, 5> a = {1, 2, 3, 4, 5};
86+
assert(duval(a) == 0);
87+
88+
// Test 5
89+
std::deque<char> d = {'a', 'z', 'c', 'a', 'b'};
90+
assert(duval(d) == 3);
91+
92+
std::cout << "All tests passed!" << std::endl;
93+
}
94+
95+
int main() {
96+
test();
97+
return 0;
98+
}

0 commit comments

Comments
 (0)