你必须要了解的几种排序方法
作为一个程序员,你怎么能不了解冒泡算法呢?
下面向大家介绍六中排序算法,并提供javascript实现,以及简单分析算法复杂度。
1. 简单排序方法
1.1 冒泡排序
总体描述:
相邻元素进行比较,每次选取最大的元素,进行下一次比较,因此可以将最大的元素像冒泡一样,从某一位置,到达最顶端
算法简单描述:
假设:共有n个元素
进行(n-1)次循环,第i(从1开始计数)次循环获得第i大的元素,放在数组第(n-i)(数组从0开始计数)位
每次循环都从第一个元素开始,比较当前元素与其后一个元素的大小关系,如果后一个元素小于当前元素,则说明,当前元素较大,互换位置,即将(0~n-i区间最大的数放在n-i位),并将当前元素指向下一个位置,直到当前位置指向0,循环结束
完整代码:
function bubbleSort(originArr) {
/* 数组副本 */
var cloneArr = originArr.concat();
/* 用于交换数据 */
var temp;
/* 数组长度 */
var len = cloneArr.length;
/* 每次选择出最大的元素 */
/* 通过比较相邻元素,将较大的元素放在后面,将较大的数继续进行比较 */
for (var i = 0; i < len - 1; ++i) { // 需要执行len-1次
for (var j = 0; j < len - i; ++j) { // 需要执行len-i-1次
// 如果当前元素大于下一个元素,互换两个元素
if (cloneArr[j] > cloneArr[j + 1]) { // 执行(1 + 2 + ... + n-1)算法复杂度为O(n^2)
temp = cloneArr[j];
cloneArr[j] = cloneArr[j + 1];
cloneArr[j + 1] = temp;
}
}
}
return cloneArr;
}
1.2 选择排序
总体描述:
每次选择最小的元素,放在相应的位置上
算法简单描述:
假设:共有n个元素
进行(n-1)次循环,第i(从1开始计数)次循环获得第i小的元素,放在数组第(i-1)(数组从0开始计数)位,第i次循环,从数组第(i-1)位开始,将该位置元素与其后所有元素进行比较,获取较小元素索引,循环结束之后,将当前元素与最小索引位置元素位置互换,当前位置向前移动,进行下一轮循环,直到当前位置指向(n-1)
完整代码:
function selectionSort(originArr) {
/* 数组副本 */
var cloneArr = originArr.concat();
/* 用于交换数据 */
var temp;
/* 存放最小元素索引 */
var minIndex = 0;
/* 数组长度 */
var len = cloneArr.length;
/* 从第一个位置开始,比较当前位置和后面所有元素,获取最小元素后面的位置 */
for (var i = 0; i < len - 1; ++i) { // 需要执行len-1次
minIndex = i;
// 获取最小元素位置
for (var j = i + 1; j < len; ++j) { // 需要执行len-i-1次
if (cloneArr[minIndex] > cloneArr[j]) { // 执行(1 + 2 + ... + n-1)算法复杂度为O(n^2)
minIndex = j;
}
}
// 如果最小元素所在索引,不是当前位置,交换元素
if (minIndex !== i) {
temp = cloneArr[i];
cloneArr[i] = cloneArr[minIndex];
cloneArr[minIndex] = temp;
}
}
return cloneArr;
}
1.3 插入排序
总体描述:
将数组分为前后两部分,前一部分是已排序的元素集合,后一部分是未排序的元素集合。每次选中未排序的第一个数组,插入到已排序集合中的合适的位置
算法简单描述:
假设:共有n个元素
从第2个元素开始,进行(n-1)次循环,第i次循环,将第i个元素插入到之前位置(1~i-1)中,将当前元素依次后面元素进行比较。比较元素起始值为当前元素前一位置元素,如果当前元素小于比较元素,比较元素向数组后面移动,当前元素继续与下一个元素进行比较,直到比较元素位置为0或者当前元素大于比较元素,将元素插入当前比较位置
完整代码:
function insertSort(originArr) {
/* 数组副本 */
var cloneArr = originArr.concat();
/* 用于交换数据 */
var temp;
/* 数组长度 */
var len = cloneArr.length;
/* 循环数组中的每一个元素 */
for (var i = 1; i < len; ++i) { // 需要执行len-1次
// 记录要插入的值
temp = cloneArr[i];
// 找到合适的位置插入
for (var j = i - 1; j >= 0; --j) { // 需要执行i次
if (temp < cloneArr[j]) { // 执行(1 + 2 + ... + n-1)算法复杂度为O(n^2)
// 右移已排序数组
cloneArr[j + 1] = cloneArr[j];
} else {
break;
}
}
cloneArr[j + 1] = temp;
}
return cloneArr;
}
2. 高级排序算法
2.1 希尔排序
总体描述:
希尔排序就是插入排序的优化,插入排序,每次将当前元素与之前的每一个元素进行比较,然后插入,希尔排序,相当于先按照一定步长,将数组进行分组,对每一组进行插入排序,这样就可以大幅度的调整数据的分布情况,最后执行一次快速排序进行微调
算法简单描述:
对于间隔数组中的每个元素gap,将数组元素根据gap分为gap组,对于每组进行插入排序
完整代码:
function shellSort(originArr) {
/* 数组副本 */
var cloneArr = originArr.concat();
/* 用于交换数据 */
var temp;
/* 数组长度 */
var len = cloneArr.length;
/* 间隔数组 */
var gap = [];
/* 动态创建间隔数组 */
for (var i = Math.floor(len / 2); i > 0;) {
gap.push(i);
i = Math.floor(i / 2);
}
/* 使用间隔数组中的每一个元素,选择数组中的元素,进行快速排序 */
/* 方法1: 对每一个间隔的每一个分组进行快速排序 */
/*for (var i = 0, gapLen = gap.length; i < gapLen; ++i) {
// 分为gap[i]组分别进行排序
for (var j = 0; j < gap[i]; ++j) {
// 第j组进行排序
for (var k = j + gap[i]; k < len; k = k + gap[i]) {
temp = cloneArr[k];
while (k - gap[i] >= 0 && cloneArr[k - gap[i]] > temp) {
cloneArr[k] = cloneArr[k - gap[i]];
k = k - gap[i];
}
cloneArr[k] = temp;
}
}
}*/
/* 方法2: 对于每一个间隔,从间隔位置开始,对其后每一个元素进行快速排序,保证前面的已经排好序 */
for (var i = 0, gapLen = gap.length; i < gapLen; ++i) {
for (var j = gap[i]; j < len; ++j) {
temp = cloneArr[j];
var k = j;
while (k - gap[i] >= 0 && cloneArr[k - gap[i]] > temp) {
cloneArr[k] = cloneArr[k - gap[i]];
k -= gap[i];
}
cloneArr[k] = temp;
}
// 最坏情况O(n(logn)^2) 平均情况O(n(logn)^2)
}
return cloneArr;
}
2.2 快速排序
总体描述:
每次选取一个基准值,将数组中其他的元素和它进行比较,大于则移到数组右边,小于则移到左边。然后分类出来的数组继续进行上述操作。
算法简单描述:
选择数组第一位元素位基准值,创建两个新数组,分别存放小于基准值和大于基准值的元素。然后这两个新数组递归进行上述操作,直到数组为空。然后将左右数组和基准值进行拼接
完整代码:
function quickSort(originArr) {
/* 如果数组为空,直接返回 */
if (originArr.length === 0) {
return [];
}
/* 基准值 */
var pivot = originArr[0];
/* 分别存放大于小于数组 */
var lesser = [];
var greater = [];
/* 小于基准值,存放到lesser数组中,否则存放到greater数组中 */
for (var i = 1; i < originArr.length; ++i) {
if (originArr[i] < pivot) {
lesser.push(originArr[i]);
} else {
greater.push(originArr[i]);
}
// 最坏情况O(n^2) 平均情况O(nlogn)
}
/* 将数组拼接后返回 */
return quickSort(lesser).concat(pivot, quickSort(greater));
}
2.3 归并排序
总体描述:
自顶向下:先通过递归分解数组,再合并数组
算法简单描述:
分解数组:如果数组长度不为1,从中间将数组分为两部分,继续分解
合并数组:将分解的数组融合,创建一个新数组,用于存放融合的数组元素。创建指针分别指向两个数组的首位,比较当前指针指向位置元素的大小,将较小的元素插入新数组中,指针向后移动,直到有一个数组元素全部移出。最后检查两个数组,将未移出的元素追加到新数组中,最后存放已排序的数组根据对应位置存入待排序数组中
完整代码:
function mergeSort(originArr) {
/* 数组副本 */
var cloneArr = originArr.concat();
/* 调用归并排序 */
doMergeSort(cloneArr, 0, cloneArr.length - 1);
return cloneArr;
}
/* 向下分解数组,递归调用 */
function doMergeSort(arr, low, high) {
if (low < high) {
var mid = low + Math.floor((high - low) / 2);
doMergeSort(arr, low, mid);
doMergeSort(arr, mid + 1, high);
merge(arr, low, mid, high);
// 最坏情况O(nlogn) 平均情况O(nlogn)
}
}
/* 数组融合 */
function merge(arr, low, mid, high) {
var p_low = low;
var p_high = mid + 1;
var sortArr = [];
/* 比较左右部分元素,将较小的元素存放在sortArr前面 */
while (p_low <= mid && p_high <= high) {
if (arr[p_low] > arr[p_high]) {
sortArr.push(arr[p_high++]);
} else {
sortArr.push(arr[p_low++]);
}
}
/* 将两部分可能剩余的元素复制到数组中 */
while (p_high <= high) {
sortArr.push(arr[p_high++]);
}
while (p_low <= mid) {
sortArr.push(arr[p_low++]);
}
/* 将已排序的数组复制到原数组对应位置 */
for (var i = low; i < high + 1; i++) {
arr[i] = sortArr[i - low];
}
}
3. 最终测试结果
100000数组测试:



