【问题标题】:Radix sort implementation in RR中的基数排序实现
【发布时间】:2016-11-17 09:57:07
【问题描述】:

您如何在基数 R(例如)中为以下向量实现 Radix sort

vec <- c(25, 478, 34, 9021, 6, 9947, 504, 22)

总的来说,基数排序做了以下事情:

  • 根据unit 位置排序: 9021 22 34 504 25 6 9947 478
  • 根据ten 位置排序: 504 6 9021 22 25 34 9947 478
  • 根据hundred 位置排序: 6 9021 22 25 34 478 504 9947
  • 根据thousand 位置排序: 6 22 25 34 478 504 9021 9947

等等。当然,vec 只是一个示例,该解决方案能够处理包含任意长度数字的任意长度数据。

输出将是vec 升序(或降序)排序。也就是说,

6   22   25   34  478  504 9021 9947

【问题讨论】:

  • 除非您特别希望在 R 中实现基数排序,否则在 R 3.3.0 中包含基数排序(来自“data.table”)——sort(vec, method = "radix")
  • @alexis_laz 是的,我对实现算法特别好奇。它的原理在算法上很漂亮。
  • 作为一种简单的、更多计数的排序方式,参见rep(seq_len(max(vec)), tabulate(vec))(其中,对于大整数,它将需要大量内存),它基本上只是将整数放入桶中并选择非零元素
  • @alexis_laz 也不错,但归零失败。
  • 你是对的,是的。使用零,您可以使用像 +/- 1 (rep(seq_len(max(vec) + 1), tabulate(vec + 1)) - 1) 这样的廉价技巧,尽管可能会有更多缺陷(除了内存)

标签: r sorting vector radix-sort


【解决方案1】:

这是我自己的解决方案:

f_radixSort <- function(x){
    mx <- nchar(max(x))
    for (i in 1:mx)
        x <- x[order(x%%(10^i))]
    return(x)
}

和样本调用以及逐步排序的打印。

f_radixSort(vec)

# units
# [1] 9021   22   34  504   25    6 9947  478

# tens
# [1]  504    6 9021   22   25   34 9947  478

# hundreds
# [1]    6 9021   22   25   34  478  504 9947

# thousands
# [1]    6   22   25   34  478  504 9021 9947

# ten thousands
# [1]    6   22   25   34  478  504 9021 9947

还有一个简短的BENCHMARKING(我没有使用data.table 进行排序,因为我不知道谁的原理,此外,我询问了base R 中的答案):

library(microbenchmark)
vec <- c(25, 478, 34, 9021, 6, 9947, 504, 22)

all(radix(vec)==f_radixSort(vec))
# [1] TRUE

microbenchmark(radix(vec), f_radixSort(vec))

# Unit: microseconds
             # expr     min      lq      mean   median       uq      max neval
       # radix(vec) 857.239 915.230 980.39907 943.4745 1005.071 2081.051   100
 # f_radixSort(vec)  39.061  42.216  52.28206  51.0810   54.686  111.775   100

# ========================================================
set.seed(200)
vec<-sample(10000,5000)

all(radix(vec)==f_radixSort(vec))
# [1] TRUE

microbenchmark(radix(vec), f_radixSort(vec))

# Unit: milliseconds
             # expr      min       lq     mean   median       uq       max neval
       # radix(vec) 6.724506 7.003191 8.135387 7.877256 8.195904 52.786763   100
 # f_radixSort(vec) 2.132132 2.167436 2.302167 2.200337 2.268544  4.009464   100

【讨论】:

  • 非常好的解决方案,但是当您调用order 时,这仍然算作真正的基数排序吗?据我了解,基数不是按照传统方法排序的。它将每个数字分成“桶”,在这些桶中保留它们的原始顺序,然后以正确的顺序组合桶。因此我使用a&lt;-... b&lt;-... 的循环实现
  • 实际上,我想它确实有效。 order 不进行任何排序。下单就行了……
  • 但是,您只需跳到循环的最终迭代即可获得相同的结果:all(vec[order(vec%%10^nchar(max(vec)))] == f_radixSort(vec))
  • @BryanGoggin 即使vec[order(vec)] 也会简单地给出排序后的数字,但它不符合算法的原则。
【解决方案2】:

我知道data.table 实现了开箱即用的基数排序,因此您可以使用该包,例如,只需设置键即可对数据进行排序:

library(data.table)

vec <- c(25, 478, 34, 9021, 6, 9947, 504, 22)

f1<-function(vec){
  DT<-data.table(vec)
setkey(DT, vec)
DT
}

f1(vec)

    vec
1:    6
2:   22
3:   25
4:   34
5:  478
6:  504
7: 9021
8: 9947

我想你可以自己实现该算法,但它在 R 中可能会很慢。函数看起来像这样:

library(stringr)
library(dplyr)
library(tidyr)

radix<-function(numbers){
  digits<-nchar(max(numbers))
  numbers<-str_pad(numbers, digits, pad = "0")
  rad<-data.frame(matrix(0, ncol = digits, nrow = length(numbers)))

  for(i in 1:digits){
    rad[,i] <- str_sub(numbers, i,i)
  }

  for(z in rev(1:ncol(rad))){
    a <- which(rad[,z] ==  0 )
    b <- which(rad[,z] ==  1 )
    c <- which(rad[,z] ==  2 ) 
    d <- which(rad[,z] ==  3 )
    e <- which(rad[,z] ==  4 ) 
    f <- which(rad[,z] ==  5 )
    g <- which(rad[,z] ==  6 ) 
    h <- which(rad[,z] ==  7 )
    i <- which(rad[,z] ==  8 ) 
    j <- which(rad[,z] ==  9 )

    k<-c(a,b,c,d,e,f,g,h,i,j)
    rad<-rad[k,]
  }

  rad<-rad %>% unite_(col = "num", from = colnames(rad), sep = "")
  return(as.numeric(rad$num))
}

它可以被清理/加速,但据我所知,这是基数排序:

radix(vec)
[1]    6   22   25   34  478  504 9021 9947

比较速度:

microbenchmark(f1(vec), radix(vec))

Unit: microseconds
      expr    min     lq mean median     uq     max neval
   f1(vec)  290.6  314.8  335    327  349.1   524.1   100
radix(vec) 1062.8 1121.7 1458   1163 1250.5 24407.9   100

速度比较大:

set.seed(200)
more<-sample(10000,5000)
microbenchmark(f1(more), radix(more))

       expr     min      lq  mean  median      uq     max neval
   f1(more)   539.3   565.5   623   622.2   664.8   769.7   100
radix(more) 10457.8 10668.0 11683 11133.7 12298.3 25010.6   100

【讨论】:

  • 感谢您的解决方案。反正我投了赞成票。但是f1 和您的radix 函数是否都遵循基数排序原则对数据进行排序?是否可以打印排序的每个步骤以进行验证?
  • 我相信该函数会按最低有效数字进行基数排序,即从 1s 数字开始,然后是 10s 等。您可以在每个循环结束时添加一个 print 调用来观察它的工作情况。它将从右到左排序。
  • 您不需要函数中的第一个 for 循环,因为您可以将 numbers 以矢量化方式转换为数据框或矩阵。
  • 抛开算法的速度,它需要的空间也相当可观,这是一个矩阵,其中nrow=length of vecncol=length of maximum number in vec
【解决方案3】:

我的解决方案看起来像这样 - 请耐心等待,我是初学者 ;-) 但结果是正确的:

radixSort <- function(sortvec) {
  mx <- nchar(max(sortvec))
  ## for all digits up to the number of digits in the longest number:  
  for (i in 1:mx){
    ## empty the 10 buckets
    bucket <- list()
    ## for all 10 buckets:
    for (bucketnumber in 1:10){
      ## fill each bucket with the appropriate numbers
      bucket[[bucketnumber]] <- sortvec[dig(sortvec, i)==(bucketnumber-1)]
    }
    ## empty the sorted vector
    sortvec <- c()
    ## fill the sorted vector with the the contents of buckets 1-10
    for (k in 1:10){
      sortvec <- c(sortvec, bucket[[k]])
    }
  }
  return(sortvec)
}

dig <- function(x, st) {
  ## returns the value of digit #st in number x, e.g. dig(3456, 2) returns 5
  remainder <- x%%(10^st)
  divisor <- 10^(st-1)
  return(trunc(remainder/divisor))
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-01-24
    • 2021-03-10
    • 1970-01-01
    • 1970-01-01
    • 2014-03-23
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多