Javascript Radix排序

时间:2016-04-09 06:14:46

标签: javascript algorithm sorting

我一直在网上浏览一段时间,我想知道是否通常使用Radix Sort的'稳定'的事实实现?

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

寻找LSD或MSD的例子。

8 个答案:

答案 0 :(得分:5)

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

      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);          

答案 1 :(得分:1)

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));

答案 2 :(得分:1)

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

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);

答案 3 :(得分:1)

以下函数对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位或更少的所有TypedArrays。那就是:

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

Full gist here。我以后可能会去Float64,然后基本上我们会支持所有 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

答案 4 :(得分:0)

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

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

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

让我们从伪代码开始:

counting sort

/**
 * @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);

最后,如书中所提到的,这种特殊算法的工作原理只是因为count-sort是一种就地排序算法,这意味着如果两个元素相关联,它们在输入数组中的出现顺序就会被保留。

答案 5 :(得分:0)

基数排序(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,因为它会为超大型数组使用大量资源。

答案 6 :(得分:0)

通过按位运算对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 & 0b1111给我们0b1010,这是n的第一个8位基数。

对于每次迭代,我们得到n & 0b11110000,然后得到n & 0b111100000000,它将每个连续的基数8位隔离开。

对于n & 0b11110000,我们得到0b00100000,从中我们想要0b0010,因此我们向右移4位。下一次迭代将移位8位,依此类推。

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

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

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

当然,基数排序只能与整数一起使用。

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

答案 7 :(得分:0)

我确信所有这些答案都能奏效,但是我坚决相信可以解释幕后的情况。因此,这是辅助方法的答案:

ref