树排序——使用二叉搜索树进行排序。时间复杂度– O(n²)。在这样的树中,左边的每个节点都有小于该节点的数字,右边的每个节点都有大于该节点的数字,当从根开始并从左到右打印值时,我们得到一个排序的数字列表。令人惊讶吧?
考虑二叉搜索树图:

Derrick Coetzee(公共领域)
尝试手动读取从左下角倒数第二个左侧节点开始的数字,对于左侧的每个节点 – 右侧的一个节点。
它看起来像这样:
- 左下角倒数第二个节点 – 3.
- 它有一个左分支 – 1.
- 取这个数字(1)
- 接下来我们采用顶点 3 (1, 3)
- 右侧是分支 6,但它包含分支。因此,我们以同样的方式阅读它。
- 节点 6 编号 4 的左分支 (1, 3, 4)
- 节点本身为 6 (1, 3, 4, 6)
- 右 7(1、3、4、6、7)
- 向上到根节点– 8(1,3,4,6,7,8)
- 我们以此类推将所有内容打印在右侧
- 我们得到了最终名单– 1、3、4、6、7、8、10、13、14
要在代码中实现该算法,您需要两个函数:
- 组装二叉搜索树
- 以正确的顺序打印二叉搜索树
二叉搜索树的组装方式与读取时相同,左侧或右侧的每个节点都会附加一个数字,具体取决于它是较少还是较多。
Lua 中的示例:
function Node:new(value, lhs, rhs)
output = {}
setmetatable(output, self)
self.__index = self
output.value = value
output.lhs = lhs
output.rhs = rhs
output.counter = 1
return output
end
function Node:Increment()
self.counter = self.counter + 1
end
function Node:Insert(value)
if self.lhs ~= nil and self.lhs.value > value then
self.lhs:Insert(value)
return
end
if self.rhs ~= nil and self.rhs.value < value then
self.rhs:Insert(value)
return
end
if self.value == value then
self:Increment()
return
elseif self.value > value then
if self.lhs == nil then
self.lhs = Node:new(value, nil, nil)
else
self.lhs:Insert(value)
end
return
else
if self.rhs == nil then
self.rhs = Node:new(value, nil, nil)
else
self.rhs:Insert(value)
end
return
end
end
function Node:InOrder(output)
if self.lhs ~= nil then
output = self.lhs:InOrder(output)
end
output = self:printSelf(output)
if self.rhs ~= nil then
output = self.rhs:InOrder(output)
end
return output
end
function Node:printSelf(output)
for i=0,self.counter-1 do
output = output .. tostring(self.value) .. " "
end
return output
end
function PrintArray(numbers)
output = ""
for i=0,#numbers do
output = output .. tostring(numbers[i]) .. " "
end
print(output)
end
function Treesort(numbers)
rootNode = Node:new(numbers[0], nil, nil)
for i=1,#numbers do
rootNode:Insert(numbers[i])
end
print(rootNode:InOrder(""))
end
numbersCount = 10
maxNumber = 9
numbers = {}
for i=0,numbersCount-1 do
numbers[i] = math.random(0, maxNumber)
end
PrintArray(numbers)
Treesort(numbers)
Важный нюанс что для чисел которые равны вершине придумано множество интересных механизмов подцепления к ноде, я же просто добавил счетчик к классу вершины, при распечатке числа возвращаются по счетчику.
Ссылки
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/treesort
Источники
Convert Sorted Array to Binary Search Tree (LeetCode 108. Algorithm Explained) – YouTube
Sorting algorithms/Tree sort on a linked list – Rosetta Code
How to handle duplicates in Binary Search Tree? – GeeksforGeeks
Tree Sort | GeeksforGeeks – YouTube
桶排序
桶排序——按桶排序。该算法类似于计数排序,不同之处在于将数字收集到“桶”范围中,然后使用任何其他足够高效的排序算法对桶进行排序,最后一步是将“桶”展开减一,得到一个排序列表。
算法的时间复杂度为O(nk)。该算法在线性时间内适用于服从均匀分布规律的数据。简单来说,元素必须在一定范围内,不能有“尖峰”,例如0.0到1.0之间的数字。如果这些数字中有 4 或 999,那么根据庭院法则,这样的行不再被视为“偶数”。
Julia 中的实现示例:
buckets = Vector{Vector{Int}}()
for i in 0:bucketsCount - 1
bucket = Vector{Int}()
push!(buckets, bucket)
end
maxNumber = maximum(numbers)
for i in 0:length(numbers) - 1
bucketIndex = 1 + Int(floor(bucketsCount * numbers[1 + i] / (maxNumber + 1)))
push!(buckets[bucketIndex], numbers[1 + i])
end
for i in 0:length(buckets) - 1
bucketIndex = 1 + i
buckets[bucketIndex] = sort(buckets[bucketIndex])
end
flat = [(buckets...)...]
print(flat, "\n")
end
numbersCount = 10
maxNumber = 10
numbers = rand(1:maxNumber, numbersCount)
print(numbers,"\n")
bucketsCount = 10
bucketSort(numbers, bucketsCount)
На производительность алгоритма также влияет число ведер, для большего количества чисел лучше взять большее число ведер (Algorithms in a nutshell by George T. Heineman)
Ссылки
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/bucketSort
Источники
https://www.youtube.com/watch?v=VuXbEb5ywrU
https://www.youtube.com/watch?v=ELrhrrCjDOA
https://medium.com/karuna-sehgal/an-introduction-to-bucket-sort-62aa5325d124
https://www.geeksforgeeks.org/bucket-sort-2/
https://ru.wikipedia.org/wiki/%D0%91%D0%BB%D0%BE%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0
https://www.youtube.com/watch?v=LPrF9yEKTks
https://en.wikipedia.org/wiki/Bucket_sort
https://julialang.org/
https://www.oreilly.com/library/view/algorithms-in-a/9780596516246/ch04s08.html
基数排序
Radix Sort – 基数排序。该算法与计数排序类似,不进行元素比较;而是将元素逐个字符分组到“桶”(桶)中,桶是通过当前数字字符的索引来选择的。时间复杂度– O(nd)。
它的工作原理是这样的:
- 输入为数字 6、12、44、9
- 我们将创建 10 个列表桶 (0-9),我们将在其中逐位添加/排序数字。
下一个:
- 使用计数器 i 开始循环,直到达到数字中的最大字符数
- 通过索引 i 从右到左,我们为每个数字获取一个符号;如果没有符号,则假设它为零
- 将符号转换为数字
- 按索引 – 数字选择一个存储桶,并将整个数字放在那里
- 搜索完数字后,将所有桶转换回数字列表
- 获取按排名排序的数字
- 重复直到所有数字都消失
Scala 中的基数排序示例:
import scala.util.Random.nextInt
object RadixSort {
def main(args: Array[String]) = {
var maxNumber = 200
var numbersCount = 30
var maxLength = maxNumber.toString.length() - 1
var referenceNumbers = LazyList.continually(nextInt(maxNumber + 1)).take(numbersCount).toList
var numbers = referenceNumbers
var buckets = List.fill(10)(ListBuffer[Int]())
for( i <- 0 to maxLength) { numbers.foreach( number => {
var numberString = number.toString
if (numberString.length() > i) {
var index = numberString.length() - i - 1
var character = numberString.charAt(index).toString
var characterInteger = character.toInt
buckets.apply(characterInteger) += number
}
else {
buckets.apply(0) += number
}
}
)
numbers = buckets.flatten
buckets.foreach(x => x.clear())
}
println(referenceNumbers)
println(numbers)
println(s"Validation result: ${numbers == referenceNumbers.sorted}")
}
}
该算法还有一个并行执行的版本,例如在 GPU 上;还有一个排序选项,这一定非常有趣并且真正令人惊叹!
链接
https://gitlab .com/demensdeum/algorithms/-/blob/master/sortAlgorithms/radixSort/radixSort.scala
来源
<一href="https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D0% B4%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA% D0%B0"目标=“_空白” rel="noopener">https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D 0%B4%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0% BA%D0%B0
https://www.geeksforgeeks.org/radix-sort/
https://www.youtube.com/watch?v=toAlAJKojos
https://github.com/gyatskov/radix-sort
堆排序
堆排序——金字塔排序。算法的时间复杂度– O(n log n),快吧?我将这种排序称为掉落卵石的排序。在我看来,最简单的解释方法就是视觉化。
输入是数字列表,例如:
5、0、7、2、3、9、4
从左到右,创建一个数据结构 – 二叉树,或者我称之为“二叉树”。金字塔。金字塔元素最多可以有两个子元素,但只能有一个顶部元素。
让我们制作一棵二叉树:
⠀⠀5
⠀0⠀7
2 3 9 4
如果你长时间观察金字塔,你会发现这些只是一个数组中的数字,一个接一个地出现,每一层的元素数量都乘以二。
接下来,有趣的事情开始了;让我们使用落下的卵石方法(heapify)对金字塔进行排序。排序可以从最后一层(2 3 9 4)开始,但是没有意义,因为下面没有任何地板可以让您跌倒。
因此,我们开始从倒数第二层(0 7)删除元素
⠀⠀5
⠀0⠀7
2 3 9 4
从右边选择第一个落下的元素,在我们的例子中是7,然后我们看看它下面的是什么,在它下面是9和4,九大于四,九也大于七!我们将 7 放到 9 上,然后将 9 提升到 7 的位置。
⠀⠀5
⠀0⠀9
2 3 7 4
接下来,我们知道七已经无处可去,我们继续看数字 0,它位于左边倒数第二层:
⠀⠀5
⠀0⠀9
2 3 7 4
让我们看看下面是什么– 2和3,二小于三,三大于零,所以我们交换零和三:
⠀⠀5
⠀3⠀9
2 0 7 4
当你到达地板的尽头时,走到上面的地板上,如果可以的话,把所有东西都扔在那里。
结果是一个数据结构——堆,即最大堆,因为顶部是最大的元素:
⠀⠀9
⠀3⠀7
2 0 5 4
如果将其返回为数组表示形式,您将得到一个列表:
[9、3、7、2、0、5、4]
由此我们可以得出结论,通过交换第一个和最后一个元素,我们得到最终排序位置的第一个数字,即 9 应该位于排序列表的末尾,交换位置:
[4、3、7、2、0、5、9]
让我们看一下二叉树:
⠀⠀4
⠀3⠀7
2 0 5 9
结果是树的下部已排序的情况,只需将 4 放到正确的位置,重复算法,但不考虑已经排序的数字,即 9:
⠀⠀4
⠀3⠀7
2 0 5 9
⠀⠀7
⠀3⠀4
2 0 5 9
⠀⠀7
⠀3⠀5
2 0 4 9
事实证明,我们在下降了 4 个数字后,又筹集了继 9 个数字之后的下一个最大数字—— 7. 交换最后一个未排序的数字(4)和最大的数字(7)
⠀⠀4
⠀3⠀5
2 0 7 9
事实证明,现在我们有两个数字处于正确的最终位置:
4、3、5、2、0、7、9
接下来我们重复排序算法,忽略已经排序的,最后我们得到一个 堆类型:
⠀⠀0
⠀2⠀3
4 5 7 9
或者作为列表:
0、2、3、4、5、7、9
实施
算法通常分为三个函数:
- 创建堆
- 筛选算法(heapify)
- 替换最后一个未排序元素和第一个元素
堆是通过使用heapify函数从右到左遍历二叉树的倒数第二行,直到数组的末尾来创建的。接下来在循环中,进行第一次数字替换,之后第一个元素落入/保持在原位,因此最大的元素落入第一位,重复该循环,参与者减少一个,因为每次传递后,排序后的数字保留在列表的末尾。
Ruby 中的堆排序示例:
module Colors
BLUE = "\033[94m"
RED = "\033[31m"
STOP = "\033[0m"
end
def heapsort(rawNumbers)
numbers = rawNumbers.dup
def swap(numbers, from, to)
temp = numbers[from]
numbers[from] = numbers[to]
numbers[to] = temp
end
def heapify(numbers)
count = numbers.length()
lastParentNode = (count - 2) / 2
for start in lastParentNode.downto(0)
siftDown(numbers, start, count - 1)
start -= 1
end
if DEMO
puts "--- heapify ends ---"
end
end
def siftDown(numbers, start, rightBound)
cursor = start
printBinaryHeap(numbers, cursor, rightBound)
def calculateLhsChildIndex(cursor)
return cursor * 2 + 1
end
def calculateRhsChildIndex(cursor)
return cursor * 2 + 2
end
while calculateLhsChildIndex(cursor) <= rightBound
lhsChildIndex = calculateLhsChildIndex(cursor)
rhsChildIndex = calculateRhsChildIndex(cursor)
lhsNumber = numbers[lhsChildIndex]
biggerChildIndex = lhsChildIndex
if rhsChildIndex <= rightBound
rhsNumber = numbers[rhsChildIndex]
if lhsNumber < rhsNumber
biggerChildIndex = rhsChildIndex
end
end
if numbers[cursor] < numbers[biggerChildIndex]
swap(numbers, cursor, biggerChildIndex)
cursor = biggerChildIndex
else
break
end
printBinaryHeap(numbers, cursor, rightBound)
end
printBinaryHeap(numbers, cursor, rightBound)
end
def printBinaryHeap(numbers, nodeIndex = -1, rightBound = -1)
if DEMO == false
return
end
perLineWidth = (numbers.length() * 4).to_i
linesCount = Math.log2(numbers.length()).ceil()
xPrinterCount = 1
cursor = 0
spacing = 3
for y in (0..linesCount)
line = perLineWidth.times.map { " " }
spacing = spacing == 3 ? 4 : 3
printIndex = (perLineWidth / 2) - (spacing * xPrinterCount) / 2
for x in (0..xPrinterCount - 1)
if cursor >= numbers.length
break
end
if nodeIndex != -1 && cursor == nodeIndex
line[printIndex] = "%s%s%s" % [Colors::RED, numbers[cursor].to_s, Colors::STOP]
elsif rightBound != -1 && cursor > rightBound
line[printIndex] = "%s%s%s" % [Colors::BLUE, numbers[cursor].to_s, Colors::STOP]
else
line[printIndex] = numbers[cursor].to_s
end
cursor += 1
printIndex += spacing
end
print line.join()
xPrinterCount *= 2
print "\n"
end
end
heapify(numbers)
rightBound = numbers.length() - 1
while rightBound > 0
swap(numbers, 0, rightBound)
rightBound -= 1
siftDown(numbers, 0, rightBound)
end
return numbers
end
numbersCount = 14
maximalNumber = 10
numbers = numbersCount.times.map { Random.rand(maximalNumber) }
print numbers
print "\n---\n"
start = Time.now
sortedNumbers = heapsort(numbers)
finish = Time.now
heapSortTime = start - finish
start = Time.now
referenceSortedNumbers = numbers.sort()
finish = Time.now
referenceSortTime = start - finish
print "Reference sort: "
print referenceSortedNumbers
print "\n"
print "Reference sort time: %f\n" % referenceSortTime
print "Heap sort: "
print sortedNumbers
print "\n"
if DEMO == false
print "Heap sort time: %f\n" % heapSortTime
else
print "Disable DEMO for performance measure\n"
end
if sortedNumbers != referenceSortedNumbers
puts "Validation failed"
exit 1
else
puts "Validation success"
exit 0
end
如果没有可视化,这个算法不容易理解,所以我建议的第一件事是编写一个函数来打印二叉树的当前视图。
链接
https://gitlab.com/demensdeum/algorithms/-/blob/master/sortAlgorithms/heapsort/heapsort.rb
来源
http://rosettacode.org/wiki/Sorting_algorithms/Heapsort
https://www.youtube.com/watch?v=LbB357_RwlY
https://habr.com/ru/company/ otus/博客/460087/
https://ru.wikipedia.org/wiki/Pyramid_sort
https://neerc.ifmo.ru/wiki /index.php?title=Heap_sort
https://wiki5.ru/wiki/Heapsort
https://wiki.c2.com/?HeapSort p>
<一href="https://ru.wikipedia.org/wiki/%D0%94%D0%B5%D1%80%D0%B5%D0%B2%D0%BE_(%D1%81%D1 %82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B %D1%85)" target="_blank" rel="noopener">https://ru.wikipedia.org/wiki/Tree(数据结构)
<一href="https://ru.wikipedia.org/wiki/%D0%9A%D1%83%D1%87%D0%B0_(%D1%81%D1%82%D1 %80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85 )” target="_blank" rel="noopener">https://ru.wikipedia.org/wiki/Heap(数据结构)
https://www.youtube.com/watch?v=2DmK_H7IdTo
https://www.youtube.com/watch?v=kU4KBD4NFtw
https://www.youtube.com/watch?v=DU1uG5310x0
https://www.youtube.com/watch?v =BzQGPA_v-vc
https://www.geeksforgeeks.org/二进制堆的数组表示/
https://habr.com/ru/post/112222/一个>
https://www.cs.usfca。 edu/~galles/visualization/BST.html
https://www.youtube.com/watch?v=EQzqHWtsKq4
<一href="https://medium.com/@dimko1/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1% 8B-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-heapsort-796ba965018b"目标=“_空白” rel="noopener">https://medium.com/@dimko1/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC% D1 %8B-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-heapsort-796ba965018b
https://ru.wikibrief.org/wiki/Heapsort
https://www.youtube.com/watch?v=GUUpmrTnNbw
快速排序
快速排序是一种分而治之的排序算法。递归地,我们逐个解析数字数组,将所选参考元素中的数字按照较小和较大的顺序放置,并将参考元素本身插入到它们之间的截止位置。经过几次递归迭代后,您将得到一个排序列表。时间复杂度 O(n2)。
方案:
- 我们首先从外部获取元素列表,即排序边界。第一步,排序边界将从头到尾。
- 检查起始边界和结束边界是否相交;如果发生这种情况,则该结束了
- 从列表中选择某个元素并将其命名为数据透视
- 将其向右移动到最后一个索引处的末尾,这样就不会妨碍
- 创建一个*较小数字*仍等于零的计数器
- 从左到右循环遍历列表,直到引用元素所在的最后一个索引(包括该索引)
- 我们将每个元素与参考元素进行比较
- 如果小于参考值,则根据较小数字的计数器的索引进行交换。增加较小数字的计数器。
- 当循环到达支持元素时,我们停止并根据较小数字的计数器将支持元素与元素交换。
- 我们分别针对列表左侧较小的部分和列表右侧较大的部分分别运行该算法。
- 因此,由于检查点 2,所有递归迭代都将开始停止
- 获取排序列表
快速排序是由莫斯科国立大学的科学家 Charles Anthony Richard Hoare 发明的,他在学习俄语后,在 Kolmogorov 学院学习了计算机翻译以及概率论。 1960年,因政治危机离开苏联。
Rust 中的示例实现:
use rand::Rng;
fn swap(numbers: &mut [i64], from: usize, to: usize) {
let temp = numbers[from];
numbers[from] = numbers[to];
numbers[to] = temp;
}
fn quicksort(numbers: &mut [i64], left: usize, right: usize) {
if left >= right {
return
}
let length = right - left;
if length <= 1 {
return
}
let pivot_index = left + (length / 2);
let pivot = numbers[pivot_index];
let last_index = right - 1;
swap(numbers, pivot_index, last_index);
let mut less_insert_index = left;
for i in left..last_index {
if numbers[i] < pivot {
swap(numbers, i, less_insert_index);
less_insert_index += 1;
}
}
swap(numbers, last_index, less_insert_index);
quicksort(numbers, left, less_insert_index);
quicksort(numbers, less_insert_index + 1, right);
}
fn main() {
let mut numbers = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let mut reference_numbers = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let mut rng = rand::thread_rng();
for i in 0..numbers.len() {
numbers[i] = rng.gen_range(-10..10);
reference_numbers[i] = numbers[i];
}
reference_numbers.sort();
println!("Numbers {:?}", numbers);
let length = numbers.len();
quicksort(&mut numbers, 0, length);
println!("Numbers {:?}", numbers);
println!("Reference numbers {:?}", reference_numbers);
if numbers != reference_numbers {
println!("Validation failed");
std::process::exit(1);
}
else {
println!("Validation success!");
std::process::exit(0);
}
}
如果不清楚,我建议观看圣地亚哥大学 Rob Edwards 的视频 https://www.youtube.com/watch?v=ZHVk2blR45Q 最简单、一步步展示了算法的本质和实现。
链接
https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/quickSort
来源
https://www.youtube.com/watch?v =4s-aG6yGGLU
https://www.youtube.com/watch?v=ywWBy6J5gz8
https://www.youtube.com/watch?v=Hoixgm4-P4M
https://ru.wikipedia.org/wiki/Быстрая_сортировка
https://www.youtube.com/watch?v=Hoixgm4-P4M
https://www.youtube.com/watch?v=XE4VP_8Y0BU
https://www.youtube.com/watch?v=MZaf_9IZCrc
https://www.youtube.com/watch?v=ZHVk2blR45Q
http://rosettacode.org/wiki/Sorting_algorithms/Quicksort
https://www.youtube.com/watch?v=4s-aG6yGGLU
https://www.youtube.com/watch?v=dQw4w9WgXcQ
https://www.youtube.com/watch?v=maibrCbZWKw
https://www.geeksforgeeks.org/quick-sort/
https://www.youtube.com/watch?v=uXBnyYuwPe8
二元插入排序
二分插入排序是插入排序的一种变体,其中使用二分查找来确定插入位置。该算法的时间复杂度为O(n2)
该算法的工作原理如下:
- 循环从零开始到列表末尾
- 在循环中选择一个数字进行排序,该数字存储在单独的变量中
- 二分查找查找索引以将此数字插入左侧的数字
- 一旦找到索引,左侧的数字就会从插入索引开始向右移动一位。在此过程中,需要排序的数字将被删除。
- 之前保存的号码将插入到插入索引处
- 循环结束时,整个列表将被排序
在二分查找过程中,有可能找不到该数字并且不会返回索引。由于二分查找的特殊性,会找到与要查找的数字最接近的数字,然后要返回索引,需要将其与要查找的数字进行比较,如果要查找的数字较小,则要返回的索引应为左侧的索引,如果大于或等于右侧的索引。
执行代码:
import (
"fmt"
"math/rand"
"time"
)
const numbersCount = 20
const maximalNumber = 100
func binarySearch(numbers []int, item int, low int, high int) int {
for high > low {
center := (low + high) / 2
if numbers[center] < item { low = center + 1 } else if numbers[center] > item {
high = center - 1
} else {
return center
}
}
if numbers[low] < item {
return low + 1
} else {
return low
}
}
func main() {
rand.Seed(time.Now().Unix())
var numbers [numbersCount]int
for i := 0; i < numbersCount; i++ {
numbers[i] = rand.Intn(maximalNumber)
}
fmt.Println(numbers)
for i := 1; i < len(numbers); i++ { searchAreaLastIndex := i - 1 insertNumber := numbers[i] insertIndex := binarySearch(numbers[:], insertNumber, 0, searchAreaLastIndex) for x := searchAreaLastIndex; x >= insertIndex; x-- {
numbers[x+1] = numbers[x]
}
numbers[insertIndex] = insertNumber
}
fmt.Println(numbers)
}
链接
来源
https://www.geeksforgeeks.org/binary-insertion-排序/
https://www.youtube.com/watch?v=-OVB5pOZJug
希尔排序
希尔排序 – 插入排序的一种变体,对数字数组进行初步组合。
我们需要记住插入排序是如何工作的:
1.一个循环从零开始到循环结束,因此数组被分成两部分
2. 对于左侧部分,启动第二个循环,从右到左比较元素,删除右侧较小的元素,直到找到左侧较小的元素
3. 在两个循环结束时,我们得到一个排序列表
很久以前,计算机科学家 Donald Schell 想知道如何改进插入排序算法。他还想出了这样的想法:首先分两个周期遍历数组,但在一定距离内,逐渐减少“梳子”,直到变成常规的插入排序算法。一切真的就是这么简单,没有陷阱,在上面的两个循环中我们添加了另一个循环,在这个循环中我们逐渐减小了“梳子”的尺寸。您唯一需要做的就是在比较时检查距离,使其不会超出数组。
一个真正有趣的主题是选择用于更改第一个循环每次迭代的比较长度的序列。有趣的是,算法的性能取决于它。
已知选项和时间复杂度表可以在这里找到:https: //en .wikipedia.org/wiki/Shellsort#Gap_sequences
不同的人参与计算理想距离;显然,这个话题对他们来说很有趣。他们不能只运行 Ruby 并调用最快的 sort() 算法吗?
总的来说,这些奇怪的人写的论文主题是计算 Shell 算法的“梳子”的距离/间隙。我简单地使用了他们的工作结果并检查了 5 种类型的序列:Hibbard、Knuth-Pratt、Chiura、Sedgwick。
import time
import random
from functools import reduce
import math
DEMO_MODE = False
if input("Demo Mode Y/N? ").upper() == "Y":
DEMO_MODE = True
class Colors:
BLUE = '\033[94m'
RED = '\033[31m'
END = '\033[0m'
def swap(list, lhs, rhs):
list[lhs], list[rhs] = list[rhs], list[lhs]
return list
def colorPrintoutStep(numbers: List[int], lhs: int, rhs: int):
for index, number in enumerate(numbers):
if index == lhs:
print(f"{Colors.BLUE}", end = "")
elif index == rhs:
print(f"{Colors.RED}", end = "")
print(f"{number},", end = "")
if index == lhs or index == rhs:
print(f"{Colors.END}", end = "")
if index == lhs or index == rhs:
print(f"{Colors.END}", end = "")
print("\n")
input(">")
def ShellSortLoop(numbers: List[int], distanceSequence: List[int]):
distanceSequenceIterator = reversed(distanceSequence)
while distance:= next(distanceSequenceIterator, None):
for sortArea in range(0, len(numbers)):
for rhs in reversed(range(distance, sortArea + 1)):
lhs = rhs - distance
if DEMO_MODE:
print(f"Distance: {distance}")
colorPrintoutStep(numbers, lhs, rhs)
if numbers[lhs] > numbers[rhs]:
swap(numbers, lhs, rhs)
else:
break
def ShellSort(numbers: List[int]):
global ShellSequence
ShellSortLoop(numbers, ShellSequence)
def HibbardSort(numbers: List[int]):
global HibbardSequence
ShellSortLoop(numbers, HibbardSequence)
def ShellPlusKnuttPrattSort(numbers: List[int]):
global KnuttPrattSequence
ShellSortLoop(numbers, KnuttPrattSequence)
def ShellPlusCiuraSort(numbers: List[int]):
global CiuraSequence
ShellSortLoop(numbers, CiuraSequence)
def ShellPlusSedgewickSort(numbers: List[int]):
global SedgewickSequence
ShellSortLoop(numbers, SedgewickSequence)
def insertionSort(numbers: List[int]):
global insertionSortDistanceSequence
ShellSortLoop(numbers, insertionSortDistanceSequence)
def defaultSort(numbers: List[int]):
numbers.sort()
def measureExecution(inputNumbers: List[int], algorithmName: str, algorithm):
if DEMO_MODE:
print(f"{algorithmName} started")
numbers = inputNumbers.copy()
startTime = time.perf_counter()
algorithm(numbers)
endTime = time.perf_counter()
print(f"{algorithmName} performance: {endTime - startTime}")
def sortedNumbersAsString(inputNumbers: List[int], algorithm) -> str:
numbers = inputNumbers.copy()
algorithm(numbers)
return str(numbers)
if DEMO_MODE:
maximalNumber = 10
numbersCount = 10
else:
maximalNumber = 10
numbersCount = random.randint(10000, 20000)
randomNumbers = [random.randrange(1, maximalNumber) for i in range(numbersCount)]
ShellSequenceGenerator = lambda n: reduce(lambda x, _: x + [int(x[-1]/2)], range(int(math.log(numbersCount, 2))), [int(numbersCount / 2)])
ShellSequence = ShellSequenceGenerator(randomNumbers)
ShellSequence.reverse()
ShellSequence.pop()
HibbardSequence = [
0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095,
8191, 16383, 32767, 65535, 131071, 262143, 524287, 1048575,
2097151, 4194303, 8388607, 16777215, 33554431, 67108863, 134217727,
268435455, 536870911, 1073741823, 2147483647, 4294967295, 8589934591
]
KnuttPrattSequence = [
1, 4, 13, 40, 121, 364, 1093, 3280, 9841, 29524, 88573, 265720,
797161, 2391484, 7174453, 21523360, 64570081, 193710244, 581130733,
1743392200, 5230176601, 15690529804, 47071589413
]
CiuraSequence = [
1, 4, 10, 23, 57, 132, 301, 701, 1750, 4376,
10941, 27353, 68383, 170958, 427396, 1068491,
2671228, 6678071, 16695178, 41737946, 104344866,
260862166, 652155416, 1630388541
]
SedgewickSequence = [
1, 5, 19, 41, 109, 209, 505, 929, 2161, 3905,
8929, 16001, 36289, 64769, 146305, 260609, 587521,
1045505, 2354689, 4188161, 9427969, 16764929, 37730305,
67084289, 150958081, 268386305, 603906049, 1073643521,
2415771649, 4294770689, 9663381505, 17179475969
]
insertionSortDistanceSequence = [1]
algorithms = {
"Default Python Sort": defaultSort,
"Shell Sort": ShellSort,
"Shell + Hibbard" : HibbardSort,
"Shell + Prat, Knutt": ShellPlusKnuttPrattSort,
"Shell + Ciura Sort": ShellPlusCiuraSort,
"Shell + Sedgewick Sort": ShellPlusSedgewickSort,
"Insertion Sort": insertionSort
}
for name, algorithm in algorithms.items():
measureExecution(randomNumbers, name, algorithm)
reference = sortedNumbersAsString(randomNumbers, defaultSort)
for name, algorithm in algorithms.items():
if sortedNumbersAsString(randomNumbers, algorithm) != reference:
print("Sorting validation failed")
exit(1)
print("Sorting validation success")
exit(0)
在我的实现中,对于一组随机数字,最快的差距是 Sedgwick 和 Hibbard。
mypy
我还想提一下 Python 3 的静态类型分析器——我的。有助于解决动态类型语言固有的问题,即它消除了将某些内容粘贴在不需要的地方的可能性。
正如经验丰富的程序员所说,“当你拥有一支专业团队时,就不需要静态类型”,有一天我们都会成为专业人士,我们将编写与机器完全统一和理解的代码,但现在您可以使用类似的实用程序和静态类型语言。
链接
https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/shellSort
http://mypy-lang.org/
来源
https://dl.acm.org/doi/10.1145/368370.368387
https://en.wikipedia.org/wiki/Shellsort
http://rosettacode.org/wiki/Sorting_algorithms/Shell_sort
https://ru.wikipedia.org/wiki/Сортировка_Шелла
https://neerc.ifmo.ru/wiki/index.php?title=Сортировка_Шелла
https://twitter.com/gvanrossum/status/700741601966985216
双选排序
双选择排序 – 选择排序的子类型,看起来它的速度应该是它的两倍。普通算法对数字列表进行双重循环,找到最小数字并与上一层循环指向的当前数字交换位置。双选择排序查找最小和最大数字,然后替换上一层循环所指向的两位数字。左边和右边两个数字。当在列表中间找到需要替换的数字的光标时,整个狂欢就结束了,这样,视觉中心左右就得到了排序后的数字。
该算法的时间复杂度类似于选择排序– O(n2),但据说加速度为 30 %。
边缘状态
已经到了这个阶段,你可以想象一下碰撞的瞬间,比如当左光标的数字(最小的数字)指向列表中的最大的数字,那么最小的数字重新排列,重新排列最大数量的立即崩溃。因此,该算法的所有实现都包含检查此类情况并用正确的索引替换索引。在我的实现中,一次检查就足够了:
maximalNumberIndex = minimalNumberIndex;
}
Реализация на Cito
Cito – язык либ, язык транслятор. На нем можно писать для C, C++, C#, Java, JavaScript, Python, Swift, TypeScript, OpenCL C, при этом совершенно ничего не зная про эти языки. Исходный код на языке Cito транслируется в исходный код на поддерживаемых языках, далее можно использовать как библиотеку, либо напрямую, исправив сгенеренный код руками. Эдакий Write once – translate to anything.
Double Selection Sort на cito:
{
public static int[] sort(int[]# numbers, int length)
{
int[]# sortedNumbers = new int[length];
for (int i = 0; i < length; i++) {
sortedNumbers[i] = numbers[i];
}
for (int leftCursor = 0; leftCursor < length / 2; leftCursor++) {
int minimalNumberIndex = leftCursor;
int minimalNumber = sortedNumbers[leftCursor];
int rightCursor = length - (leftCursor + 1);
int maximalNumberIndex = rightCursor;
int maximalNumber = sortedNumbers[maximalNumberIndex];
for (int cursor = leftCursor; cursor <= rightCursor; cursor++) { int cursorNumber = sortedNumbers[cursor]; if (minimalNumber > cursorNumber) {
minimalNumber = cursorNumber;
minimalNumberIndex = cursor;
}
if (maximalNumber < cursorNumber) {
maximalNumber = cursorNumber;
maximalNumberIndex = cursor;
}
}
if (leftCursor == maximalNumberIndex) {
maximalNumberIndex = minimalNumberIndex;
}
int fromNumber = sortedNumbers[leftCursor];
int toNumber = sortedNumbers[minimalNumberIndex];
sortedNumbers[minimalNumberIndex] = fromNumber;
sortedNumbers[leftCursor] = toNumber;
fromNumber = sortedNumbers[rightCursor];
toNumber = sortedNumbers[maximalNumberIndex];
sortedNumbers[maximalNumberIndex] = fromNumber;
sortedNumbers[rightCursor] = toNumber;
}
return sortedNumbers;
}
}
链接
https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/doubleSelectionSort
https://github.com/pfusik/cito
来源
https://www.researchgate.net/publication/330084245_Improved_Double_Selection_Sort_using_Algorithm
http://algolab.valemak.com/selection-double
https://www.geeksforgeeks.org/sorting-algorithm-slightly-improves-selection-sort/
鸡尾酒调酒器分类
鸡尾酒调酒器分类–摇床排序,双向冒泡排序的一种变体。
该算法的工作原理如下:
- 选择循环中搜索的初始方向(通常是从左到右)
- 接下来在循环中成对检查数字
- 如果下一个元素更大,则交换它们
- 完成后,搜索过程会以相反的方向重新开始
- 重复搜索,直到没有更多的排列
该算法的时间复杂度与bubble类似– O(n2)。
PHP 实现示例:
<?php
function cocktailShakeSort($numbers)
{
echo implode(",", $numbers),"\n";
$direction = false;
$sorted = false;
do {
$direction = !$direction;
$firstIndex = $direction == true ? 0 : count($numbers) - 1;
$lastIndex = $direction == true ? count($numbers) - 1 : 0;
$sorted = true;
for (
$i = $firstIndex;
$direction == true ? $i < $lastIndex : $i > $lastIndex;
$direction == true ? $i++ : $i--
) {
$lhsIndex = $direction ? $i : $i - 1;
$rhsIndex = $direction ? $i + 1 : $i;
$lhs = $numbers[$lhsIndex];
$rhs = $numbers[$rhsIndex];
if ($lhs > $rhs) {
$numbers[$lhsIndex] = $rhs;
$numbers[$rhsIndex] = $lhs;
$sorted = false;
}
}
} while ($sorted == false);
echo implode(",", $numbers);
}
$numbers = [2, 1, 4, 3, 69, 35, 55, 7, 7, 2, 6, 203, 9];
cocktailShakeSort($numbers);
?>
Ссылки
Источники
https://www.youtube.com/watch?v=njClLBoEbfI
https://www.geeksforgeeks.org/cocktail-sort/
https://rosettacode.org/wiki/Sorting_algorithms/Cocktail_sort
睡眠排序
睡眠排序–睡眠排序,确定性奇怪排序算法的另一个代表。
它的工作原理如下:
- 循环遍历元素列表
- 为每个循环启动一个单独的线程
- 线程休眠一段时间–元素值和睡眠后的值输出
- 循环结束时,等待线程最长的睡眠完成,显示排序后的列表
C 中睡眠排序算法的示例代码:
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
typedef struct {
int number;
} ThreadPayload;
void *sortNumber(void *args) {
ThreadPayload *payload = (ThreadPayload*) args;
const int number = payload->number;
free(payload);
usleep(number * 1000);
printf("%d ", number);
return NULL;
}
int main(int argc, char *argv[]) {
const int numbers[] = {2, 42, 1, 87, 7, 9, 5, 35};
const int length = sizeof(numbers) / sizeof(int);
int maximal = 0;
pthread_t maximalThreadID;
printf("Sorting: ");
for (int i = 0; i < length; i++) { pthread_t threadID; int number = numbers[i]; printf("%d ", number); ThreadPayload *payload = malloc(sizeof(ThreadPayload)); payload->number = number;
pthread_create(&threadID, NULL, sortNumber, (void *) payload);
if (maximal < number) {
maximal = number;
maximalThreadID = threadID;
}
}
printf("\n");
printf("Sorted: ");
pthread_join(maximalThreadID, NULL);
printf("\n");
return 0;
}
在此实现中,我在微秒上使用了 usleep 函数,其值乘以 1000,即以毫秒为单位。
算法的时间复杂度– O(很长)
链接
https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/sleepSort
来源
https://codoholicconfessions.wordpress。 com/2017/05/21/strangest-sorting-algorithms/
https://twitter.com/javascriptdaily/status/856267407106682880?lang=en
https://stackoverflow.com/questions/6474318/what-is-the-time-complexity-of-the-sleep-sort
斯大林排序
斯大林排序– sort through,有数据丢失的排序算法之一。
该算法非常高效且高效,时间复杂度为O(n)。
它的工作原理如下:
- 循环数组,将当前元素与下一个元素进行比较
- 如果下一个元素小于当前元素,则将其删除
- 因此,我们在 O(n) 时间内得到了一个排序数组
算法输出示例:
Gulag: [1, 3, 2, 4, 6, 42, 4, 8, 5, 0, 35, 10]
Element 2 sent to Gulag
Element 4 sent to Gulag
Element 8 sent to Gulag
Element 5 sent to Gulag
Element 0 sent to Gulag
Element 35 sent to Gulag
Element 10 sent to Gulag
Numbers: [1, 3, 4, 6, 42]
Gulag: [2, 4, 8, 5, 0, 35, 10]
Python 3 代码:
gulag = []
print(f"Numbers: {numbers}")
print(f"Gulag: {numbers}")
i = 0
maximal = numbers[0]
while i < len(numbers):
element = numbers[i]
if maximal > element:
print(f"Element {element} sent to Gulag")
gulag.append(element)
del numbers[i]
else:
maximal = element
i += 1
print(f"Numbers: {numbers}")
print(f"Gulag: {gulag}")
缺点包括数据丢失,但如果我们朝着乌托邦式的、理想的、O(n) 的排序列表发展,那还能怎样呢?
链接
https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/stalinSort
来源
https://github.com/gustavo-depaula/stalin-sort
https://www.youtube.com/shorts/juRL-Xn-E00
https://www.youtube.com/watch?v=L78i2YcyYfk
选择排序
选择排序选择排序算法。选择什么?但是最少数量!!!
算法的时间复杂度– O(n2)
该算法的工作原理如下:
- 我们从左到右循环遍历数组,记住当前的起始索引和索引处的数字,我们称之为数字 A
- 在循环内,我们运行另一个循环从左到右,寻找小于 A 的内容
- 当我们找到较小的那个时,我们会记住索引,现在较小的那个就变成了数字A
- 当内循环结束时,交换起始索引处的数字和数字A
- 完成上层循环后,我们得到一个排序数组
算法执行示例:
(29, 49, 66, 35, 7, 12, 80)
29 > 7
(7, 49, 66, 35, 29, 12, 80)
Round 1 ENDED
Round 2
(7, 49, 66, 35, 29, 12, 80)
49 > 35
35 > 29
29 > 12
(7, 12, 66, 35, 29, 49, 80)
Round 2 ENDED
Round 3
(7, 12, 66, 35, 29, 49, 80)
66 > 35
35 > 29
(7, 12, 29, 35, 66, 49, 80)
Round 3 ENDED
Round 4
(7, 12, 29, 35, 66, 49, 80)
(7, 12, 29, 35, 66, 49, 80)
Round 4 ENDED
Round 5
(7, 12, 29, 35, 66, 49, 80)
66 > 49
(7, 12, 29, 35, 49, 66, 80)
Round 5 ENDED
Round 6
(7, 12, 29, 35, 49, 66, 80)
(7, 12, 29, 35, 49, 66, 80)
Round 6 ENDED
Sorted: (7, 12, 29, 35, 49, 66, 80)
尚未在 Rosetta 代码上找到 Objective-C 实现 ,我自己写道:
#include <Foundation/Foundation.h>
@implementation SelectionSort
- (void)performSort:(NSMutableArray *)numbers
{
NSLog(@"%@", numbers);
for (int startIndex = 0; startIndex < numbers.count-1; startIndex++) {
int minimalNumberIndex = startIndex;
for (int i = startIndex + 1; i < numbers.count; i++) {
id lhs = [numbers objectAtIndex: minimalNumberIndex];
id rhs = [numbers objectAtIndex: i];
if ([lhs isGreaterThan: rhs]) {
minimalNumberIndex = i;
}
}
id temporary = [numbers objectAtIndex: minimalNumberIndex];
[numbers setObject: [numbers objectAtIndex: startIndex]
atIndexedSubscript: minimalNumberIndex];
[numbers setObject: temporary
atIndexedSubscript: startIndex];
}
NSLog(@"%@", numbers);
}
@end
Собрать и запустить можно либо на MacOS/Xcode, либо на любой операционной системе поддерживающей GNUstep, например у меня собирается Clang на Arch Linux.
Скрипт сборки:
main.m \
-lobjc \
`gnustep-config --objc-flags` \
`gnustep-config --objc-libs` \
-I /usr/include/GNUstepBase \
-I /usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include/ \
-lgnustep-base \
-o SelectionSort \
Links
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/selectionSort
Sources
https://rosettacode.org/wiki/Sorting_algorithms/Selection_sort
https://ru.wikipedia.org/wiki/Сортировка_выбором
https://en.wikipedia.org/wiki/Selection_sort
https://www.youtube.com/watch?v=LJ7GYbX7qpM
计数排序
计数排序–计数排序算法。按照?是的!就这样!
该算法至少涉及两个数组,第一个 –要排序的整数列表,第二个 –大小 = (最大数 – 最小数) + 1 的数组,最初仅包含零。接下来,从第一个数组中取出数字,并使用数字元素获取第二个数组中的索引,该索引加一。遍历整个列表后,我们将得到一个完全填充的第二个数组,其中包含第一个数组中数字的重复次数。 该算法有严重的开销——第二个数组还包含不在第一个列表中的数字的零,即所谓的。内存开销
收到第二个数组后,我们迭代它并按索引写入数字的排序版本,将计数器递减至零。最初,零计数器被忽略。
计数排序算法的非优化操作示例:
- 输入数组 1,9,1,4,6,4,4
- 那么要计数的数组将为 0,1,2,3,4,5,6,7,8,9(最小数字 0,最大数字 9)
- 计数器总数为 0,2,0,0,3,0,1,0,0,1
- 排序数组总数 1,1,4,4,4,6,9
Python 3 中的算法代码:
numbers = [42, 89, 69, 777, 22, 35, 42, 69, 42, 90, 777]
minimal = min(numbers)
maximal = max(numbers)
countListRange = maximal - minimal
countListRange += 1
countList = [0] * countListRange
print(numbers)
print(f"Minimal number: {minimal}")
print(f"Maximal number: {maximal}")
print(f"Count list size: {countListRange}")
for number in numbers:
index = number - minimal
countList[index] += 1
replacingIndex = 0
for index, count in enumerate(countList):
for i in range(count):
outputNumber = minimal + index
numbers[replacingIndex] = outputNumber
replacingIndex += 1
print(numbers)
Из-за использования двух массивов, временная сложность алгоритма O(n + k)
Ссылки
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/countingSort
Источники
https://www.youtube.com/watch?v=6dk_csyWif0
https://www.youtube.com/watch?v=OKd534EWcdk
https://en.wikipedia.org/wiki/Counting_sort
https://rosettacode.org/wiki/Sorting_algorithms/Counting_sort
https://pro-prof.com/forums/topic/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC
博戈排序
伪排序或沼泽排序,最无用的排序算法之一。
它的工作原理如下:
1.输入是数字数组
2. 数字数组被随机打乱(shuffle)
3. 检查数组是否已排序
4. 如果没有排序,则数组再次打乱
5. 重复所有这些操作,直到数组随机排序。
正如你所看到的,这个算法的性能很糟糕,聪明的人相信即使是 O(n * n!) 即有可能你会为了混沌之神的荣耀掷骰子多年,数组永远无法排序,或者也许它会排序?
实施
为了在 TypeScript 中实现它,我需要实现以下功能:
1. 打乱对象数组
2.数组比较
3. 生成一个从零到数字范围内的随机数(原文如此!)
4.打印进度,因为排序似乎永无休止地继续
下面是TypeScript实现代码:
const randomInteger = (maximal: number) => Math.floor(Math.random() * maximal);
const isEqual = (lhs: any[], rhs: any[]) => lhs.every((val, index) => val === rhs[index]);
const shuffle = (array: any[]) => {
for (var i = 0; i < array.length; i++) { var destination = randomInteger(array.length-1); var temp = array[i]; array[i] = array[destination]; array[destination] = temp; } } let numbers: number[] = Array.from({length: 10}, ()=>randomInteger(10));
const originalNumbers = [...numbers];
const sortedNumbers = [...numbers].sort();
let numberOfRuns = 1;
do {
if (numberOfRuns % 1000 == 0) {
printoutProcess(originalNumbers, numbers, numberOfRuns);
}
shuffle(numbers);
numberOfRuns++;
} while (isEqual(numbers, sortedNumbers) == false)
console.log(`Success!`);
console.log(`Run number: ${numberOfRuns}`)
console.log(`Original numbers: ${originalNumbers}`);
console.log(`Current numbers: ${originalNumbers}`);
console.log(`Sorted numbers: ${sortedNumbers}`);
Для отладки можно использовать VSCode и плагин TypeScript Debugger от kakumei.
Как долго
Вывод работы алгоритма:
src/bogosort.ts:1
Still trying to sort: 5,4,8,7,5,0,2,9,7,2, current shuffle 8,7,0,2,4,7,2,5,9,5, try number: 145000
src/bogosort.ts:2
Still trying to sort: 5,4,8,7,5,0,2,9,7,2, current shuffle 7,5,2,4,9,8,0,5,2,7, try number: 146000
src/bogosort.ts:2
Still trying to sort: 5,4,8,7,5,0,2,9,7,2, current shuffle 0,2,7,4,9,5,7,5,8,2, try number: 147000
src/bogosort.ts:2
Still trying to sort: 5,4,8,7,5,0,2,9,7,2, current shuffle 5,9,7,8,5,4,2,7,0,2, try number: 148000
src/bogosort.ts:2
Success!
src/bogosort.ts:24
Run number: 148798
src/bogosort.ts:25
Original numbers: 5,4,8,7,5,0,2,9,7,2
src/bogosort.ts:26
Current numbers: 5,4,8,7,5,0,2,9,7,2
src/bogosort.ts:27
Sorted numbers: 0,2,2,4,5,5,7,7,8,9
Для массива из 10 чисел Богосорт перемешивал исходный массив 148798 раз, многовато да?
Алгоритм можно использовать как учебный, для понимания возможностей языка с которым предстоит работать на рынке. Лично я был удивлен узнав что в ванильных JS и TS до сих пор нет своего алгоритма перемешивания массивов, генерации целого числа в диапазоне, доступа к хэшам объектов для быстрого сравнения.
Ссылки
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/bogosort
https://www.typescriptlang.org/
https://marketplace.visualstudio.com/items?itemName=kakumei.ts-debug
Источники
https://www.youtube.com/watch?v=r2N3scbd_jg
https://en.wikipedia.org/wiki/Bogosort
RGB图像转灰度
在这篇文章中,我将描述将 RGB 缓冲区转换为灰度(灰度)的算法。
这样做非常简单,缓冲区的每个像素颜色通道都根据一定的公式进行转换,输出是灰度图像。
平均法:
red = average;
green = average;
blue = average;
Складываем 3 цветовых канала и делим на 3.


Однако существует еще один метод – метод средневзвешенный, он учитывает цветовосприятие человека:
red = luminance;
green = luminance;
blue = luminance;


Какой метод лучше использовать? Да какой вам больше подходит для конкретной задачи. Далее сравнение методов с помощью тестовой цветовой сетки:



Пример реализации на JavaScript + HTML 5
image,
canvas,
weightedAverage
) {
const context = canvas.getContext('2d');
const imageWeight = image.width;
const imageHeight = image.height;
canvas.width = imageWeight;
canvas.height = imageHeight;
context.drawImage(image, 0, 0);
let pixels = context
.getImageData(
0,
0,
imageWeight,
imageHeight
);
for (let y = 0; y & lt; pixels.height; y++) {
for (let x = 0; x & lt; pixels.width; x++) {
const i = (y * 4) * pixels.width + x * 4;
let red = pixels.data[i];
let green = pixels.data[i + 1];
let blue = pixels.data[i + 2]
const average = (red + green + blue) / 3;
const luminance = 0.2126 * red +
0.7152 * green +
0.0722 * blue;
red = weightedAverage ? luminance : average;
green = weightedAverage ? luminance : average;
blue = weightedAverage ? luminance : average;
pixels.data[i] = red;
pixels.data[i + 1] = green;
pixels.data[i + 2] = blue;
}
}
context
.putImageData(
pixels,
0,
0,
0,
0,
pixels.width,
pixels.height
);
}
Источники
https://www.baeldung.com/cs/convert-rgb-to-grayscale
https://twitter.com/mudasobwa/status/1528046455587495940
https://rosettacode.org/wiki/Grayscale_image
Ссылки
http://papugi.demensdeum.repl.co/
Благодарности
Спасибо Aleksei Matiushkin (https://twitter.com/mudasobwa) за наводку на Rosetta Code
最长公共子串
在这篇文章中,我将描述一种解决最大公共子串问题的算法。假设我们正在尝试解密加密的二进制数据,首先让我们尝试通过搜索最大的子字符串来找到常见模式。
输入字符串示例:
adasDATAHEADER??jpjjwerthhkjbcvkDATAHEADER??kkasdf
我们正在寻找重复两次的字符串:
数据头??
前缀
首先,我们编写一个方法来比较两个字符串的前缀,让它返回结果字符串,其中左前缀的字符与右前缀的字符相等。
例如,对于以下行:
val lhs = "asdfWUKI"
val rhs = "asdfIKUW"
结果字符串 –阿斯达夫
Kotlin 中的示例:
fun longestPrefix(lhs: String, rhs: String): String {
val maximalLength = min(lhs.length-1, rhs.length -1)
for (i in 0..maximalLength) {
val xChar = lhs.take(i)
val yChar = rhs.take(i)
if (xChar != yChar) {
return lhs.substring(0, i-1)
}
}
return lhs.substring(0,maximalLength)
}
暴力破解
当事情进展不顺利时,你应该诉诸暴力。使用longestPrefix方法,我们将在两个循环中遍历字符串,第一个循环从i到末尾,第二个从i + 1到末尾,将它们传递以搜索最大的前缀。该算法的时间复杂度约为O(n^2) ~ O(n*^3)。
Kotlin 中的示例:
fun searchLongestRepeatedSubstring(searchString: String): String {
var longestRepeatedSubstring = ""
for (x in 0..searchString.length-1) {
val lhs = searchString.substring(x)
for (y in x+1..searchString.length-1) {
val rhs = searchString.substring(y)
val longestPrefix = longestPrefix(lhs, rhs)
if (longestRepeatedSubstring.length < longestPrefix.length) {
longestRepeatedSubstring = longestPrefix
}
}
}
return longestRepeatedSubstring
}
后缀数组
为了更优雅的解决方案,我们需要一个工具——一种名为“后缀数组”的数据结构。该数据结构是一个循环填充的子字符串数组,其中每个子字符串从该行的下一个字符开始到末尾。
例如,对于该行:
adasDATAHEADER??
后缀数组如下所示:
adasDATAHEADER??
dasDATAHEADER??
asDATAHEADER??
sDATAHEADER??
DATAHEADER??
ATAHEADER??
TAHEADER??
AHEADER??
HEADER??
EADER??
ADER??
DER??
ER??
R??
??
?
我们通过排序来解决
让我们对后缀数组进行排序,然后循环遍历当前元素在左手(左)、下一个在右手(右)的所有元素,并使用longestPrefix计算最长前缀方法。
Kotlin 中的示例:
fun searchLongestRepeatedSubstring(searchString: String): String {
val suffixTree = suffixArray(searchString)
val sortedSuffixTree = suffixTree.sorted()
var longestRepeatedSubstring = ""
for (i in 0..sortedSuffixTree.count() - 2) {
val lhs = sortedSuffixTree[i]
val rhs = sortedSuffixTree[i+1]
val longestPrefix = longestPrefix(lhs, rhs)
if (longestRepeatedSubstring.length < longestPrefix.length) {
longestRepeatedSubstring = longestPrefix
}
}
return longestRepeatedSubstring
}
该算法的时间复杂度为 O(N log N),这比直接解决方案要好得多。
来源
https://en.wikipedia.org/wiki/Longest_common_substring_problem
源代码
https://gitlab.com/demensdeum/algorithms
插入排序、归并排序
插入排序
插入排序–每个元素都会与列表中的前一个元素进行比较,并将元素与较大的元素(如果有)交换,否则内部比较循环停止。由于元素是从第一个到最后一个排序的,因此每个元素都会与已经排序的列表进行比较,这“可能”减少了总体运行时间。该算法的时间复杂度为O(n^2),即与气泡多样性相同。
合并排序
合并排序–该列表被分成一个元素的组,然后这些组被成对地“合并”并同时进行比较。在我的实现中,当合并对时,将左侧的元素与右侧的元素进行比较,然后移动到结果列表;如果左侧的元素消失,则将右侧的所有元素添加到结果列表中; list(不需要进行额外的比较,因为组中的所有元素都会经过排序迭代)< br />该算法的工作非常容易并行化;合并对的阶段可以在线程中执行,等待调度程序中的迭代结束。
单线程执行算法的输出:
["John", "Alice", "Mike", "#1", "Артем", "20", "60", "60", "DoubleTrouble"]
[["John"], ["Alice"], ["Mike"], ["#1"], ["Артем"], ["20"], ["60"], ["60"], ["DoubleTrouble"]]
[["Alice", "John"], ["#1", "Mike"], ["20", "Артем"], ["60", "60"], ["DoubleTrouble"]]
[["#1", "Alice", "John", "Mike"], ["20", "60", "60", "Артем"], ["DoubleTrouble"]]
[["#1", "20", "60", "60", "Alice", "John", "Mike", "Артем"], ["DoubleTrouble"]]
["#1", "20", "60", "60", "Alice", "DoubleTrouble", "John", "Mike", "Артем"]
多线程执行算法的输出:
["John", "Alice", "Mike", "#1", "Артем", "20", "60", "60", "DoubleTrouble"]
[["John"], ["Alice"], ["Mike"], ["#1"], ["Артем"], ["20"], ["60"], ["60"], ["DoubleTrouble"]]
[["20", "Артем"], ["Alice", "John"], ["60", "60"], ["#1", "Mike"], ["DoubleTrouble"]]
[["#1", "60", "60", "Mike"], ["20", "Alice", "John", "Артем"], ["DoubleTrouble"]]
[["DoubleTrouble"], ["#1", "20", "60", "60", "Alice", "John", "Mike", "Артем"]]
["#1", "20", "60", "60", "Alice", "DoubleTrouble", "John", "Mike", "Артем"]
算法时间复杂度为O(n*log(n)),略优于O(n^2)
来源
https://en.wikipedia.org/wiki/Insertion_sort
https://en.wikipedia.org/wiki/Merge_sort
源代码
https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/insertionSort
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/mergeSort
Erlang 中的冒泡排序
冒泡排序非常无聊,但是如果您尝试用电信的函数式语言来实现它,它就会变得更有趣——埃尔兰。
我们有一个数字列表,我们需要对其进行排序。冒泡排序算法遍历整个列表,逐对迭代和比较数字。在检查过程中,会发生以下情况:将较小的数字添加到输出列表中,或者在当前列表中交换数字;如果右侧的数字较小,则继续搜索迭代中的下一个数字。重复此遍历,直到列表中不再有替换为止。
实际上,由于该算法的时间复杂度很高,因此不值得使用。 O(n^2);我在 Erlang 中以命令式的方式实现了它,但如果你有兴趣,你可以寻找更好的选择:
-module(bubbleSort).
-export([main/1]).
startBubbleSort([CurrentHead|Tail]) ->
compareHeads(CurrentHead, Tail, [], [CurrentHead|Tail]).
compareHeads(CurrentHead, [NextHead|Tail], [], OriginalList) ->
if
CurrentHead < NextHead ->
compareHeads(NextHead, Tail, [CurrentHead], OriginalList);
true ->
compareHeads(CurrentHead, Tail, [NextHead], OriginalList)
end;
compareHeads(CurrentHead, [NextHead|Tail], OriginalOutputList, OriginalList) ->
if
CurrentHead < NextHead ->
OutputList = OriginalOutputList ++ [CurrentHead],
compareHeads(NextHead, Tail, OutputList, OriginalList);
true ->
OutputList = OriginalOutputList ++ [NextHead],
compareHeads(CurrentHead, Tail, OutputList, OriginalList)
end;
compareHeads(CurrentHead, [], OriginalOutputList, OriginalList) ->
OutputList = OriginalOutputList ++ [CurrentHead],
if
OriginalList == OutputList ->
io:format("OutputList: ~w~n", [OutputList]);
true ->
startBubbleSort(OutputList)
end.
main(_) ->
UnsortedList = [69,7,4,44,2,9,10,6,26,1],
startBubbleSort(UnsortedList).
安装和启动
在 Ubuntu 中,Erlang 的安装非常简单;只需在终端中输入 sudo apt install erlang 即可。在这种语言中,每个文件必须是一个模块,具有可以外部使用的函数列表——出口。该语言的有趣特征包括没有变量,只有常量,没有 OOP 标准语法(这并不妨碍 OOP 技术的使用),当然还有基于参与者模型的无锁并行计算。
您可以通过交互式 erl 控制台运行该模块,一个接一个地运行命令,或者更简单地通过 escript bubbleSort.erl 运行该模块;对于不同的情况,文件看起来会有所不同,例如,对于 escript,您需要创建一个主函数来启动它。
来源
https://www.erlang.org/
https://habr.com/ru/post/197364/
源代码
https://gitlab.com/ demensdeum/algorithms/blob/master/bubbleSort/bubbleSort.erl
字典序比较算法
字典字符串比较算法的工作原理非常简单;循环比较字符代码,如果字符不相等则返回结果。
C 语言的示例可以在这里找到:
https://github.com/gcc-mirror/gcc/blob/master/libiberty/memcmp.c
应该考虑到您需要比较单个静态编码中的字符,例如在 Swift 中我在 UTF-32 中使用了逐字符比较。使用 memcmp 的数组排序选项将完全适用于单字节字符,在其他情况下(可变长度编码),顺序可能不正确。我不排除基于变长编码实现的可能性,但很可能会复杂一个数量级。
该算法的时间复杂度在最好情况下为 O(1),在平均和最坏情况下为 O(n)
来源
https://ru.wikipedia.org/wiki/Lexicography_order
来源
https://gitlab.com/demensdeum /algorithms/blob/master/lexiCompare/lexiCompare.swift
二分查找
假设我们需要查明电子邮件地址“demensdeum@gmail.com”是否包含在允许接收信件的电子邮件地址列表中.
让我们从第一个元素到最后一个元素遍历整个列表,检查该元素是否等于指定的地址–让我们实现一个线性搜索算法。但这需要很长时间,不是吗?
要回答这个问题,请使用“算法的时间复杂度”,“O”符号。最坏情况下线性搜索的操作时间等于数组元素的第n个数量,我们用“O”符号来写它–在)。接下来,我们需要解释一下,对于任何已知的算法,都存在三个性能指标——最好情况、最坏情况和平均情况的执行时间。例如,邮件地址“demensdeum@gmail.com”位于数组的第一个索引中,那么就会在第一步中找到它根据该算法,执行时间至多是“最佳”。 O(1);如果在列表的末尾,那么这是最坏的情况– O(n)
但是软件实现的细节,硬件性能,它们应该影响大O吗?现在吸一口气,想象一下时间复杂度的计算是针对某种抽象的理想机计算的,其中只有这个算法,没有别的。
算法
好吧,事实证明线性搜索相当慢,让我们尝试使用二分搜索。首先,应该澄清的是,我们不会使用二进制数据;由于其工作的特殊性,因此给该方法起了这个名字。最初我们将数组排序为 字典顺序,然后算法取整个数组的范围,获取范围的中间元素,比较按字典顺序,并根据比较的结果,决定使用哪个范围来进一步搜索–当前的上半部分或下半部分。也就是说,在每个搜索步骤中,都会从两个可能的“结果”中做出决定。二元逻辑。重复此步骤,直到找到或未找到该单词(出现范围的下索引和上索引的交集)。
该算法的性能–最好的情况是立即在数组中间找到一个元素 O(1),枚举的最坏情况是 O(log n)
陷阱
在实现二分查找时,我不仅遇到了编程语言库中字典比较缺乏标准化这个有趣的问题,甚至还发现缺乏统一的实现标准JavaScript 内的 localeCompare。 ECMAScript 标准允许该函数有不同的实现,这就是为什么当使用 localeCompare 进行排序时,可以在不同的 JavaScript 引擎上观察到完全不同的结果。
因此,为了使算法正确工作,有必要排序并仅使用相同的字典序比较算法,否则什么都不起作用。但是,例如,如果您尝试在 Scala 中对数组进行排序,并使用 Nodejs 进行搜索,而不实现您自己的排序/排序,那么除了对人性的失望之外,什么也没有等待您。
来源
<一href="https://ru.stackoverflow.com/questions/489888/%D0%A7%D1%82%D0%BE-%D1%82%D0%B0%D0%BA%D0%BE%D0%B5 -% D0%BB%D0%B5%D0%BA%D1%81%D0%B8%D0%BA%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1% 87%D0%B5%D1%81%D0%BA%D0%BE%D0 %B5-%D1%81%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8-%D1%87% D1%82%D0%BE-%D0%BE%D0%BD%D0%BE- %D1%81%D0%BE%D0%B1%D0%BE%D0%B9-%D0%BF%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B0% D0%B2%D0%BB%D1%8F%D0%B5%D1%82" target="_blank" rel="noopener">什么是词典比较,它代表什么?
Почему для вычисления сложности алгоритмов используется log N вместо lb N?
Двоичный поиск
Знай сложности алгоритмов
https://stackoverflow.com/questions/52941016/sorting-in-localecompare-in-javascript
源代码
https://gitlab.com/demensdeum/algorithms
