老白的算法学习笔记01

269 阅读4分钟

老白的算法学习笔记01

入行程序员5年有余,从去年开始慢慢接触算法,但是学习的效果一直不是很好。学习算法的道路是枯燥且难以坚持的,所以为了自己能够很好的坚持下去,同时也为了能让和我一样开始学习算法的小伙伴能有个参考,于是有了这个算法学习笔记系列。希望自己能坚持下去。

第一期先来几个经典的排序算法。 本系列所有代码都可以在github仓库中查询到. 本篇文章中的代码在这里.

本篇会总结出以下算法

  • 选择排序
  • 插入排序
  • 冒泡排序
  • 希尔排序
  • 归并排序
  • 快速排序

选择排序

选择排序是一基本上没有任何优化的排序算法。没什么好说的,直接看代码。

时间复杂度:

O(n^2)

代码实现

function selectionSort(arr) {
  const len = arr.length
  if (len < 1) return arr
  for (let i = 0; i < len - 1; i++) {
    let minIndex = i
    for (let j = i + 1; j < len; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j
      }
    }
    swapArr(arr, i, minIndex)
  }
  return arr
}
const selectionArr = getRandomArr();
console.log('before selection ', selectionArr)
selectionSort(selectionArr)
console.log('after selection sort ===>', selectionArr)

插入排序

插入排序的工作原理是把数组分成两部分,一部分是有序序列,一部分是无序序列。把无序序列的值一个一个按照我们想要的规律插入到前面的有序序列 中。

时间复杂度

O(n^2)

代码实现

// 2. 插入排序
function insertionSort(arr) {
  const len = arr.length
  let sortInd; // 当前要插入到有序数列中的index
  // 有序数列 0 ~ i-1;
  // 无序数列 i ~ len

  for (let i = 1; i < len; i++) {
    sortInd = i
    for (let j = i - 1; j >= 0; j--) {
      if (arr[sortInd] < arr[j]) {
        swapArr(arr, sortInd, j)
        sortInd = j
      } else {
        break
      }
    }
  }
  return arr
}
const insertionArr = getRandomArr();
console.log('before insertion ',insertionArr)
insertionSort(insertionArr)
console.log('after insertion sort ===>', insertionArr)

冒泡排序

冒泡排序也是一种直观简单的排序算法。它重复地走访过要排序的数列, 一次比较两个元素,如果顺序和目标结果不一致就进行交换。这个算法的名字由来是最大或者最小的元素会经由交换慢慢“浮”到数列的顶端

时间复杂度

O(n^2)

代码实现

function bubbleSort(arr) {
	const len = arr.length;
	for (let i = 0; i < len; i++) {
		for (let j = 0; j < len-i-1; j++) { // 注意j的终止条件是len - i - 1, 因为我们采用的策略是和下一个元素比较
			if(arr[j] > arr[j+1]) {
				swapArr(arr, j, j+1);
			}
		}
	}
	return arr;
}

const bubbleArr = getRandomArr();
console.log('before bubble sort',bubbleArr);
insertionSort(bubbleArr)
console.log('after bubble sort', bubbleArr)

.

希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。

希尔排序的基本思想是:先追求表中的元素部分有序,再逐渐逼近全局有序

时间复杂度

O(nlog2(n) ~ n^2)非稳定排序

演示动图

Sorting_shellsort_anim.gif

1024555-20161128110416068-1421707828.png

算法步骤

  1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;

  2. 按增量序列个数 k,对序列进行 k 趟排序;

  3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度

代码实现

function shellSort(arr) {
	const len = arr.length;
	let gap = len;
	while(gap > 1) {
		gap  = Math.floor(gap/2);
		for (let i = gap; i < len; i++) {
			for (let j = i -gap; j >= 0; j -= gap) { // 插入排序
				if (arr[j] > arr[j+gap]) {
					swapArr(arr, j, j+gap);
				}
			}
		}
	}
}

const shellArr = getRandomArr();
console.log('before shell sort ===>', shellArr);
shellSort(shellArr)
console.log('after shell sort ===>',shellArr);

归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

时间复杂度

nlog2(n)

T(n) = 2* T(n/2) + a*n;
     = 2*(2*T(n/4) + a*n/2) + a * n;
		 = 4 * T(n/n/4+2a * n;
     = 8 * T(n/8) + 3a * n;
		 ...
     = 2^k * T(n/2^k) + k*a*n;
直到 n/2^k = 1, 此时 k = log2(n)
		 = n + a * (log2(n)) * n

图解

1024555-20161218163120151-452283750.png

1024555-20161218194508761-468169540.png

代码实现

function mergeSort(arr) {
	const len = arr.length;
	if(len < 2) return arr;
	const middle = Math.floor(len/2);
	const left = arr.slice(0, middle);
	const right = arr.slice(middle);
	return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
	const result = [];
	while(left.length && right.length) {
		if(left[0] <= right[0]) {
			result.push(left.shift());
		}else {
			result.push(right.shift());
		}
	}
	if(left.length) {
		result.push(...left);
	}
	if(right.length) {
		result.push(...right);
	}
	return result;
}
const mergeArr = getRandomArr();
console.log('before merge sort ===>', mergeArr);
const mergedArr = mergeSort(mergeArr);
console.log('after merge sort ===>', mergedArr)

快速排序

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

时间复杂度

nlog2(n) - n^2 非稳定排序

图解

quickSort.jpeg

代码实现

function quickSort(arr, left, right) {
	let partitionIndex;
	if(left < right) {
		partitionIndex = partition(arr, left, right);
		quickSort(arr, left, partitionIndex -1);
		quickSort(arr, partitionIndex + 1, right);
	}
	return arr;
}
function partition(arr, left, right) {
	let pivot = left;
	let index = pivot +1;
	for (let i = index; i <= right; i++) { // 比pivot小的值都依次和前面的值进行交换,确保最后pivot左边的都小于 arr[pivot]
		if(arr[i] < arr[pivot]) {
			swapArr(arr, i, index);
			index++;
		}
	}
	swapArr(arr, pivot, index-1);// 把pivot和最后一个比它小的值进行交换,完成 partition 过程;
	return index -1;
}

const quickArr = getRandomArr();
console.log('before quick sort ==>', quickArr)
quickSort(quickArr, 0, quickArr.length-1)
console.log('after quick sort ===>', quickArr)

参考学习地址

十大经典排序算法

归并排序

快速排序

希尔排序