最近在复习数据结构的同时也顺便看了一下一些基本的排序算法,当初刚踏入计算机专业大门的时候学得死去活来的东西现在看起来容易理解了许多,于是用JS将各个排序的算法写了一下:
1.冒泡排序
原理:对数组进行遍历,相邻元素根据大小进行交换,每次遍历将最小值推至最前方,然后对剩下的值再次进行比较
最坏时间复杂度:O(n^2)
有的人有可能会问,冒泡每次遍历所需要执行的次数不应该是(n-1)+........+1=n*(n-1)/2么,为什么是n^2,因为如果当n为一个极大数如10000时,n*(n-1)/2=(100000000-10000)/2,相对于10^8,10000已经可以忽略不计了,所以为n^2/2所以最坏时间复杂度为O(n^2)
稳定性:稳定
空间复杂度:O(1)
function pop(array) {
var len = array.length,
i, j, tmp, result;
result = array.slice(0);
for (i = 0; i < len; i++) {
for (j = len - 1; j > i; j--) {
if (result[j] < result[j - 1]) {
tmp = result[j - 1];
result[j - 1] = result[j];
result[j] = tmp;
}
}
}
return result;
}
alert(pop([5,6,4,7,8]));
2.快速排序:
原理:从数组中取一个值为基准值,并将剩下的值与之比较,小于基准值的放到左边,大于基准值的放到右边,并再次对左右两边进行快速排序,直至左右两边只剩一个元素。
最坏时间复杂度:O(n^2) 当选择的基准值为最大值或最小值时
稳定性:不稳定
平均时间复杂度:O(n*log2n)
function quick(arr){
var len=arr.length;
if(len<=1){
return arr;
}
var index=Math.floor(len/2),//向下取整 根据中间的值作为比较对象
pindex=arr.splice(index,1)[0],//需要删除中间值,以缩小排序的数组大小
left=[],//定义左右两个数组 左大右小
right=[];
for(var i=0;i<len-1;i++){ //遍历整个数组 大放右 小放左
if(arr[i]>=pindex){
right.push(arr[i]);
}else{
left.push(arr[i]);
}
}
return quick(left).concat([pindex],quick(right)); //继续递归并将数组合并
}
alert(quick(arr));
注意:在取基准值的时候有的人可能会问:为什么要用splice把他取出来呢?直接用索引值引用对应的值就好了,原因在这里,如果我们没有把基准值提取出来的话,基准值也会做为一个值与基准值(它本身)进行比较,数组的整体大小没有改变,而我们把它提取出来之后可以减少数组的长度。当然我们也可以把数组的第一位作为基准值,在遍历时跳过,也是可以的。
3.插入排序
原理:从数组第二个值开始,依次将后续的数值经过比较与前面排序后的序列比较后插入,如图:
最坏时间复杂度:O(n2):当数组是从大到小排列时,插入第一个元素需要移动一次,插入第二个需要移动两次,以此类推,所以一共为1+2+3+4+......+(n-1),与冒泡排序相同
最优时间复杂度:最好的情况是数组已经由小到大排好,则为O(n)
稳定性:稳定
空间复杂度:O(1)
function insert(array) {
var len = array.length,
i, j, tmp, result;
// 设置数组副本
result = array.slice(0);
for(i=1; i < len; i++){ //数组第一个值为默认的衡量值
tmp = result[i]; //从第二个值开始与之前的值进行比较
j = i - 1; //之前已经排好序的数组的最后一个
while(j>=0 && tmp < result[j]){ //如果j大于等于零(否则越界) 与最后一个值进行比较,如果小于
result[j+1] = result[j]; //则将最后一个值后移一位
j --; //j往前移动一位
}
result[j+1] = tmp; //比较完成 这时result[j]<temp或者j已经为-1,则将tmp的值赋给j+1
}
return result;
}
4.希尔排序(对插入排序的优化)
原理:由于直接插入排序每一次插入新值都要与之前已经序列化的部分进行比较,越往后所需要比较的次数越多,所以希尔排序通过设置步长,将整个数组依照步长分为一个个分块儿,将分块序列化之后再将整个数组进行插入排序。
注意此处根据步长划分块儿不是每X个分一个小块儿,而是将索引值相隔X的元素进行比较,比如如图步长为5,则索引值为0,5;1,6;2,7;3,8;4,9分别进行插入排序,排序完毕后降低步长,再次进行排序
时间复杂度:希尔排序的时间复杂度和其增量序列有关系,这涉及到数学上尚未解决的难题;不过在某些序列中复杂度可以为O(n1.3);
空间复杂度:O(1)
稳定性:不稳定
function shell(arr) {
var len =arr.length,
i,j,temp,
gap = Math.floor(len/2);//设置步长
while (gap>0) { //当步长大于0时 每次步长减半
for (i = gap; i < len; i++) {
temp = arr[i];
j = i-gap;
while (j>=0&&temp<arr[j]){ //J>=0且arr[i]<arr[j]时
arr[j+gap]=arr[j]; //a[j]的值向前移动一个步长
j -=gap; //j往前移动一个步长
}
arr[j+gap] =temp;
}
gap = Math.floor(gap/2);//每次步长缩短一半直至为1
}
return arr;
}
alert(shell([2,5,7,9,45,12,6,74]));
5.选择排序
原理:与冒泡排序类似,只不过选择排序不是通过相邻元素交换而将最小值“冒泡”到顶端,而是从数组第一个元素开始,与后面的的元素进行比较,如果后面的元素都比他大,则不需要交换,如果有比其小的,则两个值相互交换。
最坏时间复杂度:O(n2)
平均时间复杂度:O(n2)
稳定性:稳定
空间复杂度:O(1)
function select(arr) {
var len = arr.length,
i,j,temp,k;
for (i=0;i<len;i++){ //遍历数组的每一个值,并于其后的值比较找出最小值之后互换
k=i;
for (j=i+1;j<len;j++){
if (arr[j]<arr[k])k=j;
}
if (k!=i){ //如果arr[i]已经是最小的则不需要互换
temp=arr[k];
arr[k]=arr[i];
arr[i]=temp;
}
}
return arr;
}
alert(select([14,5,45,2,6,7,5,9]));