【问题标题】:Javascript Radix SortJavascript 基数排序
【发布时间】:2016-07-30 12:46:59
【问题描述】:

我已经在网上浏览了一段时间,我想知道是否存在普遍使用的 Radix Sort 的“稳定”事实上的实现?

基数排序的两种分类是最低有效位 (LSD) 基数排序和最高有效位 (MSD) 基数排序。

寻找 LSD 或 MSD 的例子。

【问题讨论】:

  • 这里不在主题范围内。请访问help center 了解原因。这可能是 CODEREVIEW 更好的匹配
  • 刚刚更新了问题,谢谢
  • 实现基数排序的标准方法是 LSD。自 1950 年代卡片分拣机出现以来,情况就一直如此。使用 LSD,在每个基数排序步骤之后,可以将这些 bin 连接起来以进行下一步。使用 MSD 时,bins 必须保持分开,所以如果以 10 为基础进行分类,第一步是 10 个 bin,第二步是 100 个 bin,第三步是 1000 个 bin,...,所以通常不使用。

标签: javascript algorithm sorting


【解决方案1】:

我的实现

// get the digits in the number by index
const getDigit = (num, index) => {
    return Math.floor(Math.abs(num) / Math.pow(10, index)) % 10;
}

// get the number of digits in a number
const getNumberOfDigits = (num) => {
    if(num === 0) return 1;

    return Math.floor(Math.log10(Math.abs(num))) + 1; 
}

// get the max Number of digits in arr
const getMaxNumOfDigits = (arr) => {
    let max = 0;

    for(let item of arr) {
        max = Math.max(max, getNumberOfDigits(item))
    }

    return max;
}

// sorting the numbers
const radixSort = (arr) => {
    let maxIteration = getMaxNumOfDigits(arr),
        buckets;

    for (let i = 0; i < maxIteration; i++) {
        // set bucket to arr of 10 empty sub arrs
        buckets = Array.from({length: 10},  () => []);

        for(let item of arr) {
            // put the number in the right bucket position
            buckets[getDigit(item, i)].push(item);
        }
        
        // re-collect from the buckets
        arr = [].concat(...buckets);
    }

    return arr;
}

// get the digits in the number by index
const getDigit = (num, index) => {
    return Math.floor(Math.abs(num) / Math.pow(10, index)) % 10;
}

// get the number of digits in a number
const getNumberOfDigits = (num) => {
    if(num === 0) return 1;

    return Math.floor(Math.log10(Math.abs(num))) + 1; 
}

// get the max Number of digits in arr
const getMaxNumOfDigits = (arr) => {
    let max = 0;

    for(let item of arr) {
        max = Math.max(max, getNumberOfDigits(item))
    }

    return max;
}

// sorting the numbers
const radixSort = (arr) => {
    let maxIteration = getMaxNumOfDigits(arr),
        buckets;

    for (let i = 0; i < maxIteration; i++) {
        // set bucket to arr of 10 empty sub arrs
        buckets = Array.from({length: 10},  () => []);

        for(let item of arr) {
            // put the number in the right bucket position
            buckets[getDigit(item, i)].push(item);
        }
        
        // re-collect from the buckets
        arr = [].concat(...buckets);
    }

    return arr;
    }


radixSort([9, 212, 55, 19, 111, 3])


console.log(radixSort([9, 212, 55, 19, 111, 3]))

【讨论】:

    【解决方案2】:

    我确信所有这些答案都有效,但我非常相信通过分解来解释引擎盖下发生的事情。因此,这里是辅助方法的答案:

    // helper
    function getDigit(num, place) {
        return Math.floor(Math.abs(num) / Math.pow(10, place)) % 10;
    }
    
    // helper
    function digitCount(num) {
        if (num === 0) {
            return 1;
        }
        return Math.floor(Math.log10(Math.abs(num))) + 1;
    }
    
    // helper
    function mostDigits(nums) {
        let max = 0;
        for (let num of nums) {
            max = Math.max(max, digitCount(num));
        }
        return max;
    }
    
    function radixSort(nums) {
        let maxDigits = mostDigits(nums);
        for (let k = 0; k < maxDigits; k++) {
            let buckets = Array.from({length: 10}, () => []);
            for (let num of nums) {
                let digit = getDigit(num, k);
                buckets[digit].push(num);
            }
            nums = [].concat(...buckets);
        }
        return nums;
    }
    

    【讨论】:

      【解决方案3】:

      通过按位运算进行 LSD 基数排序可能是这样的:

      const initialMask = 0b1111;
      const bits = 4;
      
      const getBuckets = () => Array.from(
        { length: (2 * initialMask) + 1 },
        () => [],
      );
      
      function radixSort(array) {
        let max = 0;
        array.forEach(n => {
          const abs = Math.abs(n);
          if (abs > max) max = abs;
        });
      
        if (max >= 0x80000000) {
          throw new Error('cannot perform bitwise operations on numbers >= 0x80000000');
        }
      
        for (
          let mask = initialMask,
            shifted = 0,
            buckets = getBuckets();
          true;
          mask = (mask << bits),
            shifted = (shifted + bits),
            buckets = getBuckets()
        ) {
          array.forEach(n => {
            const digit = mask & Math.abs(n);
            const bucket = (Math.sign(n) * (digit >> shifted)) + initialMask;
            buckets[bucket].push(n);
          });
      
          let i = 0;
          buckets.forEach(bucket => bucket.forEach(n => {
            array[i] = n;
            i += 1;
          }));
          if ((max ^ mask) <= mask) break;
        }
      }
      
      const getArray = () => Array.from(
        { length: 1e6 },
        () => Math.floor(Math.random() * 0x80000000) * Math.sign(Math.random() - 0.5),
      );
      
      const isSorted = array => {
        for (let i = 1; i < array.length; i += 1) {
          if (array[i - 1] > array[i]) return false;
        }
        return true;
      }
      
      const radixArray = getArray();
      const nativeArray = radixArray.slice();
      
      const radixStart = +new Date();
      radixSort(radixArray);
      const radixEnd = +new Date();
      
      const nativeStart = +new Date();
      nativeArray.sort();
      const nativeEnd = +new Date();
      
      document.write(`
        <dl>
          <dt>Sorted array in</dt>
          <dd>${radixEnd - radixStart}ms</dd>
        
          <dt>Properly sorted</dt>
          <dd>${isSorted(radixArray)}</dd>
      
          <dt>Sorted with Array.prototype.sort in</dt>
          <dd>${nativeEnd - nativeStart}ms</dd>
        </dl>
       `);

      这是怎么回事?

      我们按基数 8 排序(0b1111 有助于概念化按位运算)。

      我们创建0b1111 * 2 + 1桶,即集合[-0b1111 … 0b1111]中的项目数

      我们使用“掩码”来获取给定数字的每个基数 8 位,例如

      如果n = 0b101000101010n &amp; 0b1111 给我们0b1010,这是n 的第一个基数8 位。

      对于每次迭代,我们会得到n &amp; 0b11110000,然后是n &amp; 0b111100000000,这会隔离每个连续的以 8 位为基数的数字。

      对于n &amp; 0b11110000,我们得到0b00100000,我们需要0b0010,所以我们执行右移4位。下一次迭代将移动 8 位,依此类推。

      考虑到负值,我们基本上同时执行两个基数排序:负值反向排序,正值按正常顺序排序。如果数字是负数,我们说数字 7 应该在 0,6 在 1,5 在 2,等等。

      如果它是正数,我们说基数 7 应该在索引 14 处,6 在 13 处,等等。

      最后的检查 - (max ^ mask) &lt;= mask - 确定掩码是否采用最大值的最高有效位。如果有,则对数组进行排序。

      当然,基数排序只能处理整数。

      如果您需要使用大于0x80000000 的数字,您可以使用字符串来实现。

      【讨论】:

        【解决方案4】:

        基数排序 (LSD)

        function radixSort(arr) {
            const base = 10;
            let divider = 1;
            let maxVal = Number.NEGATIVE_INFINITY;
        
            while (divider === 1 || divider <= maxVal) {
                const buckets = [...Array(10)].map(() => []);
        
                for (let val of arr) {
                    buckets[Math.floor((val / divider) % base)].push(val);
                    maxVal = val > maxVal ? val : maxVal;
                }
        
                arr = [].concat(...buckets);
                divider *= base;
            }
            return arr;
        }
        

        免责声明:它仅适用于正整数。

        • 对于混合的负整数和正整数,请检查 this 版本。
        • 我避免使用Math.max,因为它会为非常大的数组使用大量资源。

        【讨论】:

          【解决方案5】:

          使用下面的代码,您可以传递包含大量项目的数组。

          var counter = [
            []
          ]; // Radix sort Array container to hold arrays from 0th digit to 9th digits
          
          function radixSortLSD(array) {
            var max = 0,
              mod = 10,
              dev = 1; //max
            for (var i = 0; i < array.length; i++) {
              if (array[i] > max) {
                max = array[i];
              }
            }
            // determine the large item length
            var maxDigitLength = (max + '').length;
            for (var i = 0; i < maxDigitLength; i++, dev *= 10, mod *= 10) {
              for (var j = 0; j < array.length; j++) {
                var bucket = Math.floor((array[j] % mod) / dev); // Formula to get the significant digit
                if (counter[bucket] == undefined) {
                  counter[bucket] = [];
                }
                counter[bucket].push(array[j]);
              }
              var pos = 0;
              for (var j = 0; j < counter.length; j++) {
                var value = undefined;
                if (counter[j] != undefined) {
                  while ((value = counter[j].shift()) != undefined) {
                    array[pos++] = value;
                  }
                }
              }
            }
            console.log("ARRAY: " + array);
          };
          
          var sampleArray = [1, 121, 99553435535353534, 345, 0];
          radixSortLSD(sampleArray);

          【讨论】:

            【解决方案6】:

            以下函数对 Uint32 值进行 LSB 基数排序。顺便说一下,它比内置的排序功能要快。

            它使用类型化数组来提高性能,但如果你传递普通数组,只要它们只包含 32 位值,它就可以正常工作:

            function radixSortUint32(input) {
              const arrayConstr = input.length < (1 << 16) ?
                Uint16Array :
                Uint32Array;
              const numberOfBins = 256 * 4;
              let count = new arrayConstr(numberOfBins);
            
              let output = new Uint32Array(input.length);
            
              // count all bytes in one pass
              for (let i = 0; i < input.length; i++) {
                let val = input[i];
                count[val & 0xFF]++;
                count[((val >> 8) & 0xFF) + 256]++;
                count[((val >> 16) & 0xFF) + 512]++;
                count[((val >> 24) & 0xFF) + 768]++;
              }
            
              // create summed array
              for (let j = 0; j < 4; j++) {
                let t = 0,
                  sum = 0,
                  offset = j * 256;
                for (let i = 0; i < 256; i++) {
                  t = count[i + offset];
                  count[i + offset] = sum;
                  sum += t;
                }
              }
            
              for (let i = 0; i < input.length; i++) {
                let val = input[i];
                output[count[val & 0xFF]++] = val;
              }
              for (let i = 0; i < input.length; i++) {
                let val = output[i];
                input[count[((val >> 8) & 0xFF) + 256]++] = val;
              }
              for (let i = 0; i < input.length; i++) {
                let val = input[i];
                output[count[((val >> 16) & 0xFF) + 512]++] = val;
              }
              for (let i = 0; i < input.length; i++) {
                let val = output[i];
                input[count[((val >> 24) & 0xFF) + 768]++] = val;
              }
            
              return input;
            }
            

            这是您如何将上述内容重复用于Int32 值:

            function radixSortInt32(input) {
              // make use of ArrayBuffer to "reinterpret cast"
              // the Int32Array as a Uint32Array
              let uinput = input.buffer ?
                new Uint32Array(input.buffer):
                Uint32Array.from(input);
            
              // adjust to positive nrs
              for (let i = 0; i < uinput.length; i++) {
                uinput[i] += 0x80000000;
              }
            
              // re-use radixSortUint32
              radixSortUint32(uinput);
            
              // adjust back to signed nrs
              for (let i = 0; i < uinput.length; i++) {
                uinput[i] -= 0x80000000;
              }
            
              // for plain arrays, fake in-place behaviour
              if (input.buffer === undefined){
                for (let i = 0; i < input.length; i++){
                  input[i] = uinput[i];
                }
              }
            
              return input;
            }
            

            对于Float32 值还有一个类似的技巧:

            function radixSortFloat32(input) {
              // make use of ArrayBuffer to "reinterpret cast"
              // the Float32Array as a Uint32Array
              let uinput = input.buffer ?
                new Uint32Array(input.buffer) :
                new Uint32Array(Float32Array.from(input).buffer);
            
              // Similar to radixSortInt32, but uses a more complicated trick
              // See: http://stereopsis.com/radixSort.html
              for (let i = 0; i < uinput.length; i++) {
                if (uinput[i] & 0x80000000) {
                  uinput[i] ^= 0xFFFFFFFF;
                } else {
                  uinput[i] ^= 0x80000000;
                }
              }
            
              // re-use radixSortUint32
              radixSortUint32(uinput);
            
              // adjust back to original floating point nrs
              for (let i = 0; i < uinput.length; i++) {
                if (uinput[i] & 0x80000000) {
                  uinput[i] ^= 0x80000000;
                } else {
                  uinput[i] ^= 0xFFFFFFFF;
                }
              }
            
              if (input.buffer === undefined){
                let floatTemp = new Float32Array(uinput.buffer);
                for(let i = 0; i < input.length; i++){
                  input[i] = floatTemp[i];
                }
              }
            
              return input;
            }
            

            我制作了一组可以与所有 32 位或更少的 TypedArray 一起使用的函数。那就是:

            • Uint32Array
            • Int32Array
            • Float32Array
            • Uint16Array
            • Int16Array
            • Uint8Array
            • Int8Array
            • 您知道所有值都符合这些条件之一的任何普通数组

            Full gist here。稍后我可能会尝试使用 Float64,然后我们基本上将支持 all javascript 数字。

            TypedArray benchmarks shows radix beats the built-in sort function.

            It's faster with plain arrays too, although not quite as much because of the added overhead

            【讨论】:

              【解决方案7】:

              我在 CRLS 第 3 版第 8.3 节中遇到了基数排序

              这本书提供了基数排序的神秘起源。它将 MSD 版本描述为过时且棘手。它还建议 LSD 的实施。

              这里我提供了一个使用这种技术的基数排序实现。

              让我们从伪代码开始:

              /**
               * @param k: the max of input, used to create a range for our buckets
               * @param exp: 1, 10, 100, 1000, ... used to calculate the nth digit
               */
              Array.prototype.countingSort = function (k, exp) {
                  /* eslint consistent-this:0 */
                  /* self of course refers to this array */
                  const self = this;
              
                  /**
                   * let's say the this[i] = 123, if exp is 100 returns 1, if exp 10 returns 2, if exp is 1 returns 3
                   * @param i
                   * @returns {*}
                   */
                  function index(i) {
                      if (exp)
                          return Math.floor(self[i] / exp) % 10;
                      return i;
                  }
              
                  const LENGTH = this.length;
              
                  /* create an array of zeroes */
                  let C = Array.apply(null, new Array(k)).map(() => 0);
                  let B = [];
              
                  for (let i = 0; i < LENGTH; i++)
                      C[index(i)]++;
              
                  for (let i = 1; i < k; i++)
                      C[i] += C[i - 1];
              
                  for (let i = LENGTH - 1; i >= 0; i--) {
                      B[--C[index(i)]] = this[i];
                  }
              
                  B.forEach((e, i) => {
                      self[i] = e
                  });
              }
              

              这是唯一棘手的部分,其余的都很简单

              Array.prototype.radixSort = function () {
                  const MAX = Math.max.apply(null, this);
              
                  /* let's say the max is 1926, we should only use exponents 1, 10, 100, 1000 */
                  for (let exp = 1; MAX / exp > 1; exp *= 10) {
                      this.countingSort(10, exp);
                  }
              }
              

              下面是如何测试这个方法

              let a = [589, 111, 777, 65, 124, 852, 345, 888, 553, 654, 549, 448, 222, 165];
              a.radixSort()
              console.log(a);
              

              最后,正如书中提到的,这种特殊算法之所以有效,只是因为计数排序是一种就地排序算法,这意味着如果两个元素相同,则它们在输入数组中的出现顺序会被保留。

              【讨论】:

                【解决方案8】:

                我的版本更冗长,但即使对于大量项目也能快速执行:

                      var testArray = [ 331, 454, 230, 34, 343, 45, 59, 453, 345, 231, 9 ];
                
                  function radixBucketSort (arr) {
                    var idx1, idx2, idx3, len1, len2, radix, radixKey;
                    var radices = {}, buckets = {}, num, curr;
                    var currLen, radixStr, currBucket;
                
                    len1 = arr.length;
                    len2 = 10;  // radix sort uses ten buckets
                
                    // find the relevant radices to process for efficiency        
                    for (idx1 = 0;idx1 < len1;idx1++) {
                      radices[arr[idx1].toString().length] = 0;
                    }
                
                    // loop for each radix. For each radix we put all the items
                    // in buckets, and then pull them out of the buckets.
                    for (radix in radices) {          
                      // put each array item in a bucket based on its radix value
                      len1 = arr.length;
                      for (idx1 = 0;idx1 < len1;idx1++) {
                        curr = arr[idx1];
                        // item length is used to find its current radix value
                        currLen = curr.toString().length;
                        // only put the item in a radix bucket if the item
                        // key is as long as the radix
                        if (currLen >= radix) {
                          // radix starts from beginning of key, so need to
                          // adjust to get redix values from start of stringified key
                          radixKey = curr.toString()[currLen - radix];
                          // create the bucket if it does not already exist
                          if (!buckets.hasOwnProperty(radixKey)) {
                            buckets[radixKey] = [];
                          }
                          // put the array value in the bucket
                          buckets[radixKey].push(curr);          
                        } else {
                          if (!buckets.hasOwnProperty('0')) {
                            buckets['0'] = [];
                          }
                          buckets['0'].push(curr);          
                        }
                      }
                      // for current radix, items are in buckets, now put them
                      // back in the array based on their buckets
                      // this index moves us through the array as we insert items
                      idx1 = 0;
                      // go through all the buckets
                      for (idx2 = 0;idx2 < len2;idx2++) {
                        // only process buckets with items
                        if (buckets[idx2] != null) {
                          currBucket = buckets[idx2];
                          // insert all bucket items into array
                          len1 = currBucket.length;
                          for (idx3 = 0;idx3 < len1;idx3++) {
                            arr[idx1++] = currBucket[idx3];
                          }
                        }
                      }
                      buckets = {};
                    }
                  }
                  radixBucketSort(testArray);
                  console.dir(testArray);          
                

                【讨论】:

                  【解决方案9】:

                  Javascript LSD 排序:

                  var counter = [[]];
                  function sortLSD(array, maxDigitSymbols) {
                      var mod = 10;
                      var dev = 1;
                      for (var i = 0; i < maxDigitSymbols; i++, dev *= 10, mod *= 10) {
                          for (var j = 0; j < array.length; j++) {
                              var bucket = parseInt((array[j] % mod) / dev);
                              if (counter[bucket] == null ) {
                                  counter[bucket] = [];
                              }
                              counter[bucket].push(array[j]);
                          }
                          var pos = 0;
                          for (var j = 0; j < counter.length; j++) {
                              var value = null ;
                              if (counter[j] != null ) {
                                  while ((value = counter[j].shift()) != null ) {
                                      array[pos++] = value;
                                  }
                              }
                          }
                      }
                      return array;
                  }
                  var test = [22, 1,2,9,3,2,5,14,66];
                  console.log(sortLSD(test, 2));
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2016-10-16
                    • 1970-01-01
                    • 2016-02-12
                    • 2020-04-14
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多