排序算法总结(Javascript版本)

      最近想系统地看看数据结构和算法,虽然实际工作中用到的不多(也许是水平不够不知道要用...),但还是要多培养这方面的意识和思想,再加上大厂面试基本都要考察数据结构和算法,所以还是要把这块捡起来。当然,还是先从排序走起。上一次系统地看排序还是在大学里,刚接触编程不久,当时还是用C和Java写的,如今做了前端,C和Java几年不看也丢得差不多了,只能写写JS了。

1.冒泡排序
实现原理

      依次比较相邻的两个元素,如果第一个元素大于第二个元素就交换它们的位置。这样比较一轮之后,最大的元素就会跑到队尾。然后对未排序的序列重复这个过程,最终转换成有序序列。

代码实现
function bubble(arr){
	//经过一次循环后最大的元素会被放在数组尾端
	for(let i = 0;i < arr.length;i++){
		//最后一个元素位置已固定,只需遍历1~n-1个元素
		for(let j = 0;j < arr.length - i - 1;j++){
			if(arr[j] > arr[j+1]){
				//如果数组本身是顺序的,执行0次;如果数组本身是逆序的,执行n*(n-1)/2次
				let t  = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = t;
			}
		}
	}
	return arr
}
算法分析

      判断语句if(arr[j] > arr[j+1])需要执行1+2+...+n-1次,而且与序列的初始状态(是否有序)无关,但是判断里面的语句和序列的初始状态有关,所以时间复杂度为O(n2),空间复杂度为O(1)

2.插入排序
实现原理

认为第一个元素是排好序的,从第二个开始遍历。

拿出当前元素的值,从排好序的序列中从后往前找。
如果序列中的元素比当前元素大,就把它后移。直到找到一个小的。
把当前元素放在这个小的后面(后面的比当前大,它已经被后移了)。

代码实现
function insert(arr){
	//从第二个元素开始遍历
	for(var i = 1;i < arr.length;i++){
        	var t = arr[i];//保存待插入元素
		//从后往前遍历“有序”部分
		for(var j = i-1;j >= 0 ;j--){
			if(arr[j] > t){//如果待插入元素小,那么将有序序列中的元素后移
                		arr[j+1] = arr[j];
			}else{//如果待插入元素更大,则执行插入操作,插入结束后退出内层循环
                		arr[j+1] = t;
                		break
			}
			//如果有序序列已经遍历完,则执行插入操作
			if(j == 0){
                		arr[j] = t;
			}
		}
	}
	return arr
}

更简洁一点的写法:

function insert(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        //这里用一个while循环,把arr[preIndex] > current这个判断写到条件里面,就不用像上面一样啰嗦
        while(preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current;
    }
    return arr
}
算法分析

      最坏的情况(初始序列逆序):第一次插入比较1次,第二次插入比较2次,第n-1次插入比较n-1次,所以时间复杂度为O(n2),空间复杂度为O(1)。


3.选择排序
实现原理

      首先从未排序序列中找到最小的元素,放置到排序序列的起始位置,然后从剩余的未排序序列中继续寻找最小元素,放置到已排序序列的末尾,所以称之为选择排序。

代码实现

function pick(arr){
	//初始时,已排序序列为空,未排序序列为arr,已排序序列在前
	for(var i = 0;i < arr.length;i++){
		//找出未排序序列中最小的元素
		var min = arr[i],index = i;
		for(var j = i+1;j < arr.length;j++){
			//这个判断执行了1+2+...+n-1次,时间复杂度为O(n2)
			if(arr[j] < min){
				//找到未排序序列中的最小元素
				min = arr[j];
				index = j;//记录下标
			}
		}
		//把未排序序列中最小的元素和已排序序列的末尾元素互换(很重要)
		arr[index] = arr[i]
		arr[i] = min;
	}
	return arr
}
算法分析

      同样,比较要进行1+2+3+...+n-1次,时间复杂度为O(n2),空间复杂度为O(1)。

4.快速排序
实现原理

      在数据集之中,选择一个元素作为”基准”(pivot)。所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。这个操作称为分区 (partition)。操作,分区操作结束后,基准元素所处的位置就是最终排序后它的位置。

      对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

代码实现
//说明:想要通过“夹逼法”确定一个元素的位置,需要两个指针
function partition(arr, start, end){
	var pivot = arr[start]
	while(start < end){
		if(arr[end] > pivot){
			end--;
		}else{
			arr[start] = arr[end];
			start++;
                        arr[end] = arr[start]
		}
	}
	arr[start] = pivot
	return start;
}
function sort(array, lo, hi) {
    if (lo >= hi) {
        return array;
    }
    var index = partition(array, lo, hi);
    sort(array, lo, index - 1);
    sort(array, index + 1, hi);
    return array;
}
算法分析

      快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

      快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

5.希尔排序
实现原理

      先取一个正整数 d1(d1 < n),把全部记录分成 d1 个组,所有距离为 d1 的倍数的记录看成一组,然后在各组内进行插入排序。然后取 d2(d2 < d1)重复上述分组和排序操作;直到取 di = 1(i >= 1) 位置,即所有记录成为一个组,最后对这个组进行插入排序。

代码实现
function shellSort(arr) {
    var len = arr.length,
        temp,
        k = 1;//定义步长
    while(k < len/3) {          //动态定义间隔序列
        k = k*3 + 1;
    }
    for (k; k > 0; k = Math.floor(k/3)) {
        for (var i = k; i < len; i++) {
            temp = arr[i];
            for (var j = i-k; j >= 0 && arr[j] > temp; j-=k) {
                arr[j+k] = arr[j];
            }
            arr[j+k] = temp;
        }
    }
    return arr;
}
算法分析

    希尔排序是插入排序的一种更高效的改进版本,针对插入排序的两个特性做出改进:1.插入排序在对几乎已经排好序的数据操作时效率较高;2.插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。另外,步长的选择对于算法的效率影响也较大,代码中的取法k = k*3 + 1;也是经过考验的效率较高的一种,更多关于步长的研究请看维基。平均时间复杂度是O(n log2 n),空间复杂度是O(1)。

6.归并排序
实现原理

     把 n 个记录看成 n 个长度为 l 的有序子表进行两两归并使记录关键字有序,得到 n/2 个长度为 2 的有序子表。重复第 2 步直到所有记录归并成一个长度为 n 的有序表为止。
总而言之,归并排序就是使用递归,先分解数组为子数组,再合并数组。

代码实现
function mergeSort(arr) {  // 采用自上而下的递归方法
    var len = arr.length;
    if(len < 2) {
        return arr;
    }
    var middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right)
{
    var result = [];

    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }

    while (left.length)
        result.push(left.shift());

    while (right.length)
        result.push(right.shift());

    return result;
}
算法分析

      归并排序的性能不受输入数据的影响,始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间,所以空间复杂度为O(n)。

7.堆排序
实现原理

       堆排序的原理很简单,只要熟悉堆这个数据结构就很容易理解。堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。

代码实现

      这个代码自己没有实现,下面是github上找来的一段代码:

var len;

function buildMaxHeap(arr) {   // 建立大顶堆
    len = arr.length;
    for (var i = Math.floor(len/2); i >= 0; i--) {
        heapify(arr, i);
    }
}

function heapify(arr, i) {     // 堆调整
    var left = 2 * i + 1,
        right = 2 * i + 2,
        largest = i;

    if (left < len && arr[left] > arr[largest]) {
        largest = left;
    }

    if (right < len && arr[right] > arr[largest]) {
        largest = right;
    }

    if (largest != i) {
        swap(arr, i, largest);
        heapify(arr, largest);
    }
}

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

function heapSort(arr) {
    buildMaxHeap(arr);

    for (var i = arr.length-1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        heapify(arr, 0);
    }
    return arr;
}
算法分析

     堆执行一次调整需要O(logn)的时间,在排序过程中需要遍历所有元素执行堆调整,所以最终时间复杂度是O(nlogn)。空间复杂度是O(1)。

小结
      本文写得比较概括,是学习过程中一个小总结,不对的地方欢迎大家指正。另外,关于这些算法的最优应用场景还有区别还不是特别清楚,后面再补充。
      新版编辑器不太会用,导致格式有些混乱,后面我会调整。










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值