【问题标题】:JavaScript quicksortJavaScript 快速排序
【发布时间】:2011-07-08 08:05:14
【问题描述】:

我已经在网上浏览了一段时间,我想知道是否存在通常使用的快速排序的“稳定”事实上的实现?我可以自己写,但为什么要重新发明轮子……

【问题讨论】:

  • 小心使用 JavaScript .Sort(); ECMAscript 标准没有规定使用哪种排序算法,所以不同的浏览器实现的排序算法不同
  • 确实这就是我要自己写的原因。
  • 仅供参考,如果您自己编写它肯定会比本地方法慢很多。你绝对需要稳定的排序吗?
  • 顺便说一句,您要求快速排序的“稳定”实现,但快速排序并不是一种固有的稳定排序。高效的实现不会是稳定的。
  • 另外你为什么关心它是否是快速排序?看起来合并排序正在成为事实上的en.wikipedia.org/wiki/…

标签: javascript quicksort


【解决方案1】:

快速排序(递归)

function quicksort(array) {
  if (array.length <= 1) {
    return array;
  }

  var pivot = array[0];
  
  var left = []; 
  var right = [];

  for (var i = 1; i < array.length; i++) {
    array[i] < pivot ? left.push(array[i]) : right.push(array[i]);
  }

  return quicksort(left).concat(pivot, quicksort(right));
};

var unsorted = [23, 45, 16, 37, 3, 99, 22];
var sorted = quicksort(unsorted);

console.log('Sorted array', sorted);

【讨论】:

    【解决方案2】:

    您可以使用 decorate-sort-undecorate 模式轻松“稳定”不稳定的排序

    function stableSort(v, f)
    {
        if (f === undefined) {
            f = function(a, b) {
                a = ""+a; b = ""+b;
                return a < b ? -1 : (a > b ? 1 : 0);
            }
        }
        var dv = [];
        for (var i=0; i<v.length; i++) {
            dv[i] = [v[i], i];
        }
        dv.sort(function(a, b){
                  return f(a[0], b[0]) || (a[1] - b[1]);
                });
        for (var i=0; i<v.length; i++) {
            v[i] = dv[i][0];
        }
    }
    

    这个想法是将索引添加为最后一个排序项,这样现在没有两个元素“相同”,如果其他所有元素都相同,则原始索引将成为区分因素。

    【讨论】:

    【解决方案3】:
    1. 将您的对象放入一个数组中。
    2. 致电Array.sort()。速度非常快。

      var array = [3,7,2,8,2,782,7,29,1,3,0,34];
      array.sort();
      console.log(array); // prints [0, 1, 2, 2, 29, 3, 3, 34, 7, 7, 782, 8]
      

    为什么按字典顺序打印?这就是array.sort() 默认的工作方式,例如如果您不提供比较器功能。让我们解决这个问题。

        var array = [3,7,2,8,2,782,7,29,1,3,0,34];
        array.sort(function (a, b)
        {
            return a-b;
        });
        console.log(array); // prints [0, 1, 2, 2, 3, 3, 7, 7, 8, 29, 34, 782]
    

    【讨论】:

    • 调用Array.sort(function (a, b){return a - b;});进行数字排序。
    • 这不能保证稳定的排序,它是特定于浏览器实现的
    • Matt,正如 K Ivanov 所说,array.sort 取决于浏览器,无法保证。我正在寻找一些我可以完全控制的代码。
    • @flavour404:如果你想拥有完全的控制权,写你自己的函数。
    • 顺便说一句,维基百科说:快速排序(也称为“分区交换排序”)是一种比较排序,在有效的实现中,它不是一种稳定的排序。编辑:刚刚看到你也对OP的问题发表了评论;))
    【解决方案4】:

    功能等效

    为了庆祝功能性 Javascript,这似乎是流行的东西

    目前,尤其是考虑到 ES6+ 出色的语法糖添加。箭头函数和解构我提出了一个与快速排序函数非常简洁、简短的函数等效项。我没有测试它的性能或将它与内置的快速排序功能进行比较,但它可能会帮助那些努力理解快速排序的实际用途的人。鉴于其声明性,很容易看出 发生了什么,而不是 如何 工作。

    这里是没有cmets的JSBin版本https://jsbin.com/zenajud/edit?js,console

    function quickSortF(arr) {
        // Base case
        if (!arr.length) return []
    
        // This is a ES6 addition, it uses destructuring to pull out the 
        // first value and the rest, similar to how other functional languages
        // such as Haskell, Scala do it. You can then use the variables as 
        // normal below
        const [head, ...tail] = arr,
              // here we are using the arrow functions, and taking full 
              // advantage of the concise syntax, the verbose version of
              // function(e) => { return e < head } is the same thing
              // so we end up with the partition part, two arrays,
              // one smaller than the pivot and one bigger than the 
              // pivot, in this case is the head variable
              left = tail.filter( e => e < head),
              right = tail.filter( e => e >= head)
    
           // this is the conquer bit of divide-and-conquer
           // recursively run through each left and right array
           // until we hit the if condition which returns an empty
           // array. These results are all connected using concat,
           // and we get our sorted array.
           return quickSortF(left).concat(head, quickSortF(right))           
    
    }
    
    const q7 = quickSortF([11,8,14,3,6,2,7]) 
    //[2, 3, 6, 7, 8, 11, 14]
    const q8 =  quickSortF([11,8,14,3,6,2,1, 7])
    //[1, 2, 3, 6, 7, 8, 11, 14]
    const q9 = quickSortF([16,11,9,7,6,5,3, 2])
    //[2, 3, 5, 6, 7, 9, 11, 16]
    
    console.log(q7,q8,q9)
    

    如果尚不清楚发生了什么,cmets 应该提供足够的资源。没有 cmets 的实际代码非常短,您可能已经注意到我不是分号的粉丝。 :)

    【讨论】:

    • 应该指出的是,这种实现并没有提供与传统快速排序相同的性能保证——它使用了 2 倍的数组访问量(.filter 遍历整个数组)并且还提供不执行数组的初始洗牌。
    【解决方案5】:

    快速排序 (ES6)

    function quickSort(arr) {
      if (arr.length < 2) {
        return arr;
      }
      const pivot = arr[Math.floor(Math.random() * arr.length)];
    
      let left = [];
      let right = [];
      let equal = [];
    
      for (let val of arr) {
        if (val < pivot) {
          left.push(val);
        } else if (val > pivot) {
          right.push(val);
        } else {
          equal.push(val);
        }
      }
      return [
        ...quickSort(left),
        ...equal,
        ...quickSort(right)
      ];
    }
    

    几点说明:

    • 随机枢轴使算法保持高效,即使数据已排序。
    • 尽管使用Array.filter 而不是使用for of 循环很不错,就像这里的一些答案一样,它会增加时间复杂度(尽管可以使用Array.reduce)。

    【讨论】:

    • 这是一种快速排序,但不稳定。 en.wikipedia.org/wiki/Sorting_algorithm#Stability
    • @PeterBrand 正如您所说,这是一种快速排序。我从来没有说过这是一个“稳定”的。非常欢迎您提出自己的版本。
    • OP 专门要求提供“稳定”版本,而这正是我所寻找的。搜索该主题将我带到这里。我必须通读您的代码才能确定它是否回答了问题。不需要我提供答案,它已由 @6502 在此线程中提供。
    • 如果您可以在上述任何情况下在 for 循环中检查它们,为什么还要引入 equal 并增加空间复杂度...
    【解决方案6】:

    在此博客http://www.nczonline.net/blog/2012/11/27/computer-science-in-javascript-quicksort/ 中指出 Array.sort 内部实现快速排序或归并排序。

    快速排序通常被认为是高效且快速的,因此也是 被 V8 用作数组上 Array.prototype.sort() 的实现 超过23个项目。对于少于 23 个项目,V8 使用插入 排序[2]。合并排序是快速排序的竞争对手,因为它也是 高效且快速,但具有稳定的额外好处。这是 为什么 Mozilla 和 Safari 使用它来实现 Array.prototype.sort().

    在使用 Array.sort 时,您应该在 Chrome 中返回 -1 0 1 而不是 true 或 false。

    arr.sort(function(a,b){
      return a<b;
    });
    // maybe--> [21, 0, 3, 11, 4, 5, 6, 7, 8, 9, 10, 1, 2, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22]
    arr.sort(function(a,b){
      return a > b ? -1 : a < b ? 1 : 0;
    });
    // --> [22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    

    【讨论】:

    【解决方案7】:

    var array = [8, 2, 5, 7, 4, 3, 12, 6, 19, 11, 10, 13, 9];
    quickSort(array, 0, array.length -1);
    document.write(array);
    
    
    function  quickSort(arr, left, right)
    {
    	var i = left;
    	var j = right;
    	var tmp;
    	pivotidx = (left + right) / 2; 
    	var pivot = parseInt(arr[pivotidx.toFixed()]);  
    	/* partition */
    	while (i <= j)
    	{
    		while (parseInt(arr[i]) < pivot)
    		i++;
    		while (parseInt(arr[j]) > pivot)
    			j--;
    		if (i <= j)
    		{
    			tmp = arr[i];
    			arr[i] = arr[j];
    			arr[j] = tmp;
    			i++;
    			j--;
    		}
    	}
    
    	/* recursion */
    	if (left < j)
    		quickSort(arr, left, j);
    	if (i < right)
    		quickSort(arr, i, right);
    	return arr;
    }

    【讨论】:

    • 分区功能真的有效吗?我在 guru99 中找到了类似的代码,并在 python 中尝试了分区函数。它不太奏效。我正在发布 python 代码和输入/输出。
    • def partition2(array, left, right): pivot = array[math.floor((left + right) / 2)] i = left j = right while i pivot: j = j-1 if i
    • 输入:[1, 4, 2, 8, 3, 9, 123, 5, 232, 67, 44, 100, 44, 33, 45, 56, 28, 45, 67, 44 ],输出:[1、4、2、8、3、9、44、5、67、45、44、28、44、33、45、56、100、67、232、123]
    【解决方案8】:

    使用 ES6 休息,传播:

    smaller = (a, list) => list.filter(x => x <= a)
    larger = (a, list) => list.filter(x => x > a)
    qsort = ([x, ...list]) => (!isNaN(x))
        ? [...qsort(smaller(x, list)), x, ...qsort(larger(x, list))]
        : []
    

    【讨论】:

      【解决方案9】:

      此算法的运行速度几乎与默认实现一样快 Array.prototype.sort 在 chrome 中。

      function quickSort(t){
          _quickSort(t,0,t.length-1,0,t.length-1);
      }
      
      function _quickSort(t, s, e, sp, ep){   
          if( s>=e )  return;
          while( sp<ep && t[sp]<t[e] ) sp++;  
          if( sp==e )
              _quickSort(t,s,e-1,s,e-1);  
          else{
              while(t[ep]>=t[e] && sp<ep ) ep--;      
              if( sp==ep ){
                  var temp = t[sp];
                  t[sp] = t[e];
                  t[e] = temp;
                  if( s!=sp ){
                      _quickSort(t,s,sp-1,s,sp-1);
                  }
                  _quickSort(t,sp+1,e,sp+1,e);            
              }else{
                  var temp = t[sp];
                  t[sp] = t[ep];
                  t[ep] = temp;
                  _quickSort(t,s,e,sp+1,ep);
              }
          }
      }
      

      快速排序时间(毫秒):738
      javaScriptSort 时间(毫秒):603

      var m = randTxT(5000,500,-1000,1000);
      VS(m);
      
      function VS(M){
          var t;
          t = Date.now();
          for(var i=0;i<M.length;i++){
              quickSort(M[i].slice());
          }console.log("quickSort time (ms): "+(Date.now()-t));
      
          t = Date.now();
          for(var i=0;i<M.length;i++){
              M[i].slice().sort(compare);
          }console.log("javaScriptSort time (ms): "+(Date.now()-t));
      }
      
      function compare(a, b) {
          if( a<b )
              return -1;
          if( a==b )
              return 0;
          return 1;
      }
      
      function randT(n,min,max){
          var res = [], i=0;
          while( i<n ){
              res.push( Math.floor(Math.random()*(max-min+1)+min) );
              i++;
          }
          return res; 
      }
      function randTxT(n,m,min,max){
          var res = [], i=0;
          while( i<n ){
              res.push( randT(m,min,max) );
              i++;
          }
          return res; 
      }
      

      【讨论】:

        【解决方案10】:

        另一个快速排序演示,无特定原因将数组的中间作为枢轴。

        const QuickSort = function (A, start, end) {
            // 
            if (start >= end) {
                return;
            }
            // return index of the pivot
            var pIndex = Partition(A, start, end);
            // partition left side
            QuickSort(A, start, pIndex - 1);
            // partition right side
            QuickSort(A, pIndex + 1, end);
        }
        
        const Partition = function (A, start, end) {
            if (A.length > 1 == false) {
                return 0;
            }
            let pivotIndex = Math.ceil((start + end) / 2);
            let pivotValue = A[pivotIndex];
            for (var i = 0; i < A.length; i++) {
                var leftValue = A[i];
                // 
                if (i < pivotIndex) {
                    if (leftValue > pivotValue) {
                        A[pivotIndex] = leftValue;
                        A[i] = pivotValue;
                        pivotIndex = i;
                    }
                }
                else if (i > pivotIndex) {
                    if (leftValue < pivotValue) {
                        A[pivotIndex] = leftValue;
                        A[i] = pivotValue;
                        pivotIndex = i;
                    }
                }
            }
            return pivotIndex;
        
        }
        
        const QuickSortTest = function () {
            const arrTest = [3, 5, 6, 22, 7, 1, 8, 9];
            QuickSort(arrTest, 0, arrTest.length - 1);
            console.log("arrTest", arrTest);
        }
        // 
        QuickSortTest();
        

        【讨论】:

          【解决方案11】:

          我真的想过这个问题。所以首先我找到了正常的搜索模式并写了。

          let QuickSort = (arr, low, high) => {
              if (low < high) {
                  p = Partition(arr, low, high);
                  QuickSort(arr, low, p - 1);
                  QuickSort(arr, p + 1, high);
              }
              return arr.A;
          }
          
          let Partition = (arr, low, high) => {
              let pivot = arr.A[high];
              let i = low;
              for (let j = low; j <= high; j++) {
                  if (arr.A[j] < pivot) {
                      [arr.A[i], arr.A[j]] = [arr.A[j], arr.A[i]];
                      i++;
                  }
              }
              [arr.A[i], arr.A[high]] = [arr.A[high], arr.A[i]];
              return i;
          }
          
          let arr = { A/* POINTER */: [33, 22, 88, 23, 45, 0, 44, 11] };
          let res = QuickSort(arr, 0, arr.A.length - 1);
          console.log(res);
          

          结果是[0, 11, 22, 23, 33, 44, 45, 88] 但它不稳定;所以我检查了其他答案,@6502 的想法对我来说很有趣,“两个项目不必相同”才能区分。 好吧,我有一个解决方案,但它不是最佳的。我们可以将项目的索引保存在一个单独的数组中。 在这个想法中,内存消耗几乎会翻倍

          arr.A => 数字数组

          arr.I => A的每一项相关的索引

          influencer => 这应该是一个非常非常小的数字;我想以此作为区分相似项目的一个因素。

          所以我们可以这样改变分区:

          let Partition = (arr, low, high) => {
              let pivot = arr.A[high];
              let index = arr.I[high];
              let i = low;
              for (let j = low; j <= high; j++) {
                  if (arr.A[j] + (arr.I[j] * influencer) < pivot + (index * influencer)) {
                      [arr.A[i], arr.A[j]] = [arr.A[j], arr.A[i]];
                      [arr.I[i], arr.I[j]] = [arr.I[j], arr.I[i]];
                      i++;
                  }
              }
              [arr.A[i], arr.A[high]] = [arr.A[high], arr.A[i]];
              [arr.I[i], arr.I[high]] = [arr.I[high], arr.I[i]];
              return i;
          }
          
          let influencer = 0.0000001;
          
          let arr = {
              I/* INDEXES */: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
              A/* POINTER */: [33, 22, 88, 33, 23, 45, 33, 89, 44, 11]
          };
          let res = QuickSort(arr, 0, arr.A.length - 1);
          console.log(res);
          

          结果:

          I: [19, 11, 14, 10, 13, 16, 18, 15, 12, 17],
          A: [11, 22, 23, 33, 33, 33, 44, 45, 88, 89]
          

          【讨论】:

            【解决方案12】:

            试试我的解决方案

            const quickSort = (arr) => {
            
                // base case
                if(arr.length < 2) return arr;
            
                // recurisve case
            
                // pick a random pivot
                let pivotIndex = Math.floor(Math.random() * arr.length);
                let pivot = arr[pivotIndex];
                let left = [];
                let right = [];
            
                // make  array of the elements less than pivot and greater than it
                for(let i = 0; i < arr.length; i++) {
                    if(i === pivotIndex) {
                        continue;
                    }
            
                    if(arr[i] < pivot) {
                        left.push(arr[i])
                    } else {
                        right.push(arr[i])
                    }
                }
                
                // call the recursive case again
                return quickSort(left).concat([pivot], quickSort(right));
            } 
            

            测试时

            quickSort([7, 5, 3, 2, 8, 1, 5])   // returns [[1, 2, 3, 5, 5, 7, 8]]
            

            【讨论】:

              【解决方案13】:

              就是这样!!!

              function typeCheck(a, b){
                if(typeof a === typeof b){
                  return true;
                }else{
                  return false;
                }
              }
              
              function qSort(arr){
                if(arr.length === 0){
                  return [];
                }
              
                var leftArr = [];
                var rightArr = [];
                var pivot = arr[0];
              
                for(var i = 1; i < arr.length; i++){
                  if(typeCheck(arr[i], parseInt(0))){
                    if(arr[i] < pivot){
                      leftArr.push(arr[i]);
                    }else { rightArr.push(arr[i]) } 
                  }else{
                    throw new Error("All must be integers");
                  }
                }
              
                return qSort(leftArr).concat(pivot, qSort(rightArr));
              
              }
              
              var test = [];
              
              for(var i = 0; i < 10; i++){
                test[i] = Math.floor(Math.random() * 100 + 2);
              }
              
              console.log(test);
              console.log(qSort(test));
              

              【讨论】:

                【解决方案14】:

                超薄版:

                function swap(arr,a,b){
                    let temp = arr[a]
                    arr[a] = arr[b]
                    arr[b] = temp
                    return 1
                }
                
                function qS(arr, first, last){
                    if(first > last) return
                
                    let p = first
                    for(let i = p; i < last; i++)
                        if(arr[i] < arr[last]) 
                            p += swap(arr, i, p)
                
                    swap(arr, p, last)
                
                    qS(arr, first, p - 1)
                    qS(arr, p + 1, last)
                }
                

                用随机值数组测试,似乎总是比 Array.sort() 快

                【讨论】:

                【解决方案15】:
                quickSort = (array, left, right) => {
                    if (left >= right) {
                        return;
                    }
                    const pivot = array[Math.trunc((left + right) / 2)];
                    const index = partition(array, left, right, pivot);
                    quickSort(array, left, index - 1);
                    quickSort(array, index, right);
                }
                
                partition = (array, left, right, pivot) => {
                    while (left <= right) {
                        while (array[left] < pivot) {
                            left++;
                        }
                        while (array[right] > pivot) {
                            right--;
                        }
                        if (left <= right) {
                            swap(array, left, right);
                            left++;
                            right--;
                        }
                    }
                    return left;
                }
                
                swap = (array, left, right) => {
                    let temp = array[left];
                    array[left] = array[right];
                    array[right] = temp;
                }
                let array = [1, 5, 2, 3, 5, 766, 64, 7678, 21, 567];
                quickSort(array, 0, array.length - 1);
                console.log('final Array: ', array);
                

                【讨论】:

                【解决方案16】:

                更紧凑且易于理解的快速排序实现

                const quicksort = arr =>
                  arr.length <= 1
                    ? arr
                    : [
                        ...quicksort(arr.slice(1).filter((el) => el < arr[0])),
                        arr[0],
                        ...quicksort(arr.slice(1).filter((el) => el >= arr[0])),
                      ];
                

                【讨论】:

                • 欢迎来到 Stack Overflow。没有任何解释的代码转储很少有帮助。 Stack Overflow 是关于学习的,而不是提供 sn-ps 来盲目复制和粘贴。请edit您的问题并解释它如何回答所提出的具体问题。见How to Answer。另请注意,这个问题已有 10 多年的历史,并且有 17 个现有答案。在这种情况下,解释您的答案如何比现有答案有所改进尤为重要。
                【解决方案17】:

                最快的实现

                const quickSort = array =>
                  (function qsort(arr, start, end) {
                    if (start >= end) return arr;
                    let swapPos = start;
                
                    for (let i = start; i <= end; i++) {
                      if (arr[i] <= arr[end]) {
                        [arr[swapPos], arr[i]] = [arr[i], arr[swapPos]];
                        swapPos++;
                      }
                    }
                    qsort(arr, start, --swapPos - 1);
                    qsort(arr, swapPos + 1, end);
                
                    return arr;
                  })(Array.from(array), 0, array.length - 1);
                

                【讨论】:

                  【解决方案18】:

                  这个不可变的功能快速排序怎么样:

                  const quicksort = (arr, comp, iArr = arr) => {
                    if (arr.length < 2) {
                      return arr;
                    }
                    const isInitial = arr.length === iArr.length;
                    const arrIndexes = isInitial ? Object.keys(arr) : arr;
                    const compF = typeof comp === 'function'
                    ? comp : (left, right) => left < right ? -1 : right < left ? 1 : 0;
                    const [pivotIndex, ...indexesSansPivot] = arrIndexes;
                    const indexSortReducer = isLeftOfPivot => [
                      (acc, index) => isLeftOfPivot === (compF(iArr[index], iArr[pivotIndex]) === -1)
                      ? acc.concat(index) : acc,
                      []
                    ];
                    const ret = quicksort(indexesSansPivot.reduce(...indexSortReducer(true)), compF, iArr)
                    .concat(pivotIndex)
                    .concat(quicksort(indexesSansPivot.reduce(...indexSortReducer(false)), compF, iArr));
                    return isInitial ? ret.reduce((acc, index) => acc.concat([arr[index]]), []) : ret;
                  };
                  

                  作为奖励,它支持可选的比较功能,可以对每个属性/属性的对象数组进行排序,并且在处理较大的值/对象时不会变慢。

                  首先快速排序原始数组键,然后返回原始数组的排序副本。

                  【讨论】:

                    猜你喜欢
                    • 2012-01-07
                    • 2021-10-01
                    • 1970-01-01
                    • 2022-10-31
                    • 2020-08-14
                    • 2016-12-23
                    • 1970-01-01
                    • 1970-01-01
                    • 2014-09-17
                    相关资源
                    最近更新 更多