快速排序

一、原理

       ☆思想:快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists),又是一种分而治之思想在排序算法上的典型应用;通过一趟排序将要排序的数据分割成独立的两部分,调整后其中一部分的所有数据比另外一部分的所有数据都要小,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,从而达到整个数据变成有序序列

       ☆过程:以递增为例,用数组表示,长度为n,有如下几个步骤;

  1. 设定一个分界值,通过该分界值将数组分成左右两部分;
  2. 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边,这个称为分区(partition)操作;
  3. 重复上述过程,可以使用递归;通过递归再对左右区间重复第二步,直到各区间只有一个数。  。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

二、实现代码

      JavaScript 代码实现挖坑法

function partition(arr, start, end) {
    var left = start,
    right = end,
    pivot = arr[left]; //基准值,在首端挖坑
    while (left < right) {
        //基准值在首端,从尾端开始遍历,寻找小于基准值元素
        while (right > left && arr[right] >= pivot) {
            right--;
        }
        //挖走右端元素填左坑
        arr[left] = arr[right];

        //从首端开始遍历,寻找大于基准值元素
        while (left < right && arr[left] <= pivot) {
            left++;
        }
        //挖走左端元素填右坑
        arr[right] = arr[left];
    }
    //基准值就位
    arr[right] = pivot;
    return right;
}

function qsort(arr, start, end) {
    if (start < end) {
        let pivot = partition(arr, start, end);
        qsort(arr, start, pivot - 1);
        qsort(arr, pivot + 1, end);
    }
}

 

      JavaScript 代码实现双指针法

 

function partition(arr, start, end) {
    var left = start,
    right = end,
    pivot = arr[left];
    while (left < right) {
        //基准值在首端,从尾端开始遍历,寻找小于基准值元素
        while (right > left && arr[right] >= pivot) {
            right--;
        }

        //从首端开始遍历,寻找大于基准值元素
        while (left < right && arr[left] <= pivot) {
            left++;
        }
        //交换左右元素
        swap(arr, left, right);
    }
    //如果指针落在比基准值小的元素,移到首端
    if (arr[right] < pivot) {
        arr[start] = arr[right];
    }
    //基准值就位
    arr[right] = pivot;
    return right;
}

function qsort(arr, start, end) {
    if (start < end) {
        let pivot = partition(arr, start, end);
        qsort(arr, start, pivot - 1);
        qsort(arr, pivot + 1, end);
    }
}

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

 

      JavaScript 代码实现单向遍历法,适用于单向链表

 

function partition(arr, start, end) {
    var base = start,
    bigger = base + 1;
    for (var i = bigger; i <= end; i++) {
        //从首端开始遍历,bigger指示比基准值大的元素索引
        //遍历i找到比基准值小的元素索引,和bigger交换元素,同时bigger后移一位
        //如果i和bigger重合,只把bigger后移一位
        if (arr[i] < arr[base]) {
            if (i != bigger) {
                swap(arr, i, bigger);
            }

            bigger++;
        }
    }
    //bigger越界减1,和基准值交换元素,bigger-1为分区索引
    swap(arr, base, bigger - 1);
    return bigger - 1;
}

function qsort(arr, start, end) {
    if (start < end) {
        let pivot = partition(arr, start, end);
        qsort(arr, start, pivot - 1);
        qsort(arr, pivot + 1, end);
    }
}
function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

 

      JavaScript 代码实现迭代法

 

function qsort(arr, start, end) {
    var base, bigger;
    var stack = [],
    top = 0;
    var sjson;
    stack[top++] = {
        start: start,
        end: end
    };

    while (top) {
        //出栈,以备分区
        sjson = stack[--top];
        jstart = sjson.start;
        jend = sjson.end;
        base = jstart;
        bigger = base + 1;
        for (var i = bigger; i <= jend; i++) {
            //从首端开始遍历,bigger指示比基准值大的元素索引
            //遍历i找到比基准值小的元素索引,和bigger交换元素,同时bigger后移一位
            //如果i和bigger重合,只把bigger后移一位
            if (arr[i] < arr[base]) {
                if (i != bigger) {
                    swap(arr, i, bigger);
                }

                bigger++;
            }
        }
        //bigger越界减1,和基准值交换元素,bigger-1为分区索引
        swap(arr, base, bigger - 1);

        //左边部分长度大于1,入栈以备循环
        if (jstart < bigger - 1) {
            stack[top++] = {
                start: jstart,
                end: bigger - 1
            };
        }
        //右边部分长度大于1,入栈以备循环
        if (bigger < jend) {
            stack[top++] = {
                start: bigger,
                end: jend
            };
        }
    }

}
function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

 

三、优化

  1. 优化选取基准值;
    • 三数取中法,解决数据基本有序的(就是找到数组中最小下标,最大下标,中间下标的数值,进行比较,把中间值和最左元素交换);
    • 随机选取基准,在待排序列是部分有序时,固定选取枢轴使快排效率底下,就引入了随机选取枢轴,把其和最左元素交换;
  2. 去掉不必要的交换;
  3. 数组长度大于阈值的,使用归并排序策略数组长度小于阈值的,使用直接插入排序
  4. 优化递归;
  5. 三路划分;

四、复杂度

名称 时间复杂度 空间复杂度  稳定性 
平均 最坏 最优
快速排序 O(nlogn) O(n²) O(nlogn)  O(logn) X
  • 快速排序的时间主要耗费在划分操作上,对长度为k的区间进行划分,共需k-1次关键字的比较;
  • 最坏情况是每次划分选取的基准都是当前无序区中关键字最小(或最大)的记录,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中记录数目,仅仅比划分前的无序区中记录个数减少一个。时间复杂度为O(n²);
  • 最优情况,每次划分所取的基准都是当前无序区的"中值"记录,划分的结果是基准的左、右两个无序子区间的长度大致相等。总的关键字比较次数:O(nlogn);
  • 尽管快速排序的最坏时间为O(n²),但就平均性能而言,它是基于关键字比较的内部排序算法中速度最快者,快速排序亦因此而得名。它的平均时间复杂度为O(nlogn)。
  • 本排序每次划分需要为基准值申请一个额外空间,大约需要logn次划分,空间复杂度为O(logn);
  • 跳跃式的交换可能会造成元素的相对位置的改变。,本排序不稳定。

参考资料:

posted @ 2019-11-23 21:46  老余的水壶  阅读(166)  评论(0)    收藏  举报