目录:[top]
前言
使用语言javascript;尽量使用改进的算法;默认从小到大排序;代码均经过实践;
排序基本概念:
1,内排序,在内存中排序。外排序,数量太大,需要借助外存。
2,排序稳定性,相同的数字应该在排序之后仍然不变(如已经按照学号
排好之后再需要按成绩排名,要求排名之后学号仍然是有序的。这样
情况下需要排序稳定)
3,性能指标,时间复杂度和空间复杂度。
冒泡排序
时间复杂度
O(n*(n-1)/2) => O(n^2) 稳定
思想
每轮相邻交换最后,最大的被换到了最后
优缺点
时间复杂度大,只适合小的数组
关键代码
function BubbleSort(a){
//从前往后冒,取最大的
//外层循环0-a.length-1次
for(var i=0;i< a.length-1;i++){
//内层循环0-a.length-i+1次
for(var j=0;j<=a.length-i-1;j++){
if(a[j]>a[j+1]){
change(a[j],a[j+1]);
}
}
}
return a;
}
选择排序
时间复杂度
O(n*(n+1)/2) => O(n^2) 不稳定
思想
依次选择无序区的最小值加入有序区
优缺点
易理解,时间代价大。适合小数组
关键代码
function selectSort(a){
var i;
for(i=0;i<a.length-1;i++){
var min=a[i];
for(var j=i;j<a.length;j++){
if(a[j]<min) min=a[j];
}
var res=a.indexOf(min);
change(a[res],a[i]);
}
return a;
}
插入排序
时间复杂度
O((n+4)*(n-1)/2) => O(n^2) 稳定
思想
依次将无序区值插入有序区中.优化:折半插入排序.
优缺点
相对于前面两种,比较次数得到明显减少。但是对于数组的操作移动
比较麻烦。
关键代码
function insertSort(arr){
for(var i=1;i<arr.length;i++){
//[0,i-1]是有序区,[i,arr.length]是无序区
var now=arr[i];//当前需要被插入的数字
for(var j=0;j<i;j++){
if(arr[j]>now){
if(j==0) arr.unshift(now);
else arr.splice(j-1,0,now);
break;
}
}
}
return arr;
}
希尔排序
时间复杂度
O(n^2)~O(nlog2N) 不稳定
思想
预先根据数组的大小设置一组步数数组,最后一步必须是1.直到原分组数组
只有一个数的时候停止,此时原数组为有序的了。
关键是步数的设置。一般的说法是设置这个数组长度的1/2依次这样。例如
数组大小为10,则设置步数序列为[5,3,2,1].
优缺点
只适合小规模数组。这个步数序列难以设置。
关键代码
function shellSort(a){
var step=[5,3,1];
for(var i=0;i<step.length;i++){
for(var j=0;j+step[i]< a.length;j++){
var max=j+step[i];
if(a[j]>a[max]){
//change(a[j],a[max]);
var temp;
temp=a[j];
a[j]=a[max];
a[max]=temp;
}
}
}
return a.toString();
}
归并排序
时间复杂度
O(nlog2N) 稳定
思想
归并排序--分治法--将无序序列分为n个单个的序列.再依次两两合并成
组内有序,组外无序。
到了需要递归的函数的时候,先进行递归,直至递归结束.然后开始回溯,从
最"新"的递归结果开始回溯,一次执行下面的程序.因此,递归的处理类似于
栈---先进后处理。
首先寻找递归结束的条件,递归之后的函数执行是从内向外执行的!---栈
优缺点
思想较复杂。时间复杂度仍然太大。
关键代码
function mergeSort(a){
var len= a.length;
if(len<2) return a;//递归结束的条件时的处理
var center=Math.floor(len/2);//平分
return merge(mergeSort(a.slice(0,center)),
mergeSort(a.slice(center+1)));
//左数组和有数组进行合并。递归的思想,从内部向外部
}
function merge(leftArr,rightArr){
var final=[];
while(leftArr.length || rightArr.length){
final.push(leftArr[0]<=rightArr[0] ?
leftArr.shift() : rightArr.shift());
}
return final.concat(leftArr.concat(rightArr));
}
快速排序
时间复杂度
O(nlog2N) 不稳定
空间复杂度:需要一个栈,栈深:O(log2N)
思想
从无序区选取一个记录设置为k,左边都存放比K小的,右边都存放比k大的。
依次循环,直到数组里只有一个值。让将所有的值进行拼接。此时借助递归
的思想。
优缺点
不稳定排序,时间非常短。
关键代码
function quick_sort(arr) {
var len = arr.length;
if (len <= 1)
return arr.slice(0);//数组只有一个值。取出字串0开始
var left = [];//左字串
var right = [];//右字串
var key = [arr[0]];//被比较的数组,为每个数组的第一个数字
for (var i = 1; i < len; i++) {
if (arr[i] < key[0]) left.push(arr[i]);
//小于key值放在左边
else right.push(arr[i]);
}
//最后把左右半部执行递归的的排序结果拼接
return quick_sort(left).concat(key.concat(quick_sort(right)));
}
堆排序
时间复杂度
建堆:O(nlog2N) 维护堆:O(log2N) =>O(nlog2N) 不稳定
思想
首先将待排序列构造小根堆,选取堆顶元素为最小值,并将其移走(或将堆
顶记录和最后一个元素进行交换)。将剩余的记录维护成为一个堆,这样再
取顶部最小值。直到堆中只有一个元素。
关键:1,从(0-n/2-1)为非叶子节点(即,一定有左子树。因下标需要从0开始)
2,若右子树不存在(2*i+1>n),结束维护堆
3,若右子树存在(2*i+1<=n)选取左右子树最大的和跟比较(即,a[2i+1<2i+2] MaxSon++)
4,与跟比较a[MaxSon]>a[i]则将这个最大的和跟交换,并且再次维护堆。
优缺点
对原始排序的记录并不敏感。适合与大量的数据。
关键代码
Array.prototype.heap_sort = function() {
var arr = this.slice(0);
function swap(i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function max_heapify(start, end) {
var dad = start;//父节点
var son = dad * 2 + 1;//子节点
if (son >= end)//无右子树
return;
if (son + 1 < end && arr[son] < arr[son + 1])
//选取左右子树最大的
son++;
if (arr[dad] <= arr[son]) {
//父节点小于最大子节点
//交换父子,并且再次维护堆
swap(dad, son);
max_heapify(son, end);
}
}
var len = arr.length;
//只对非叶子节点进行 父子维护
for (var i = Math.floor(len / 2) - 1; i >= 0; i--)
max_heapify(i, len);
//依次取出每个大根堆的根节点
for (var i = len - 1; i > 0; i--) {
swap(0, i);
max_heapify(0, i);
}
return arr;
};
总结
主要是掌握算法的思想!及其优缺点和优化方式。
具体代码可以前往我的git下载 https://github.com/zyd317/algorithm-sort