排序算法动画演示网站
一、排序算法
默认比较函数
const compareFunc = ( a, b) => a-b;
1)冒泡排序
//1.冒泡排序 两两比较 大的往后挪
const bubbleSort = (array, compareFunc) => {
//外层循环控制进行多少轮排序 内层循环把当前项和下一项做比较
for (let i= 0; i<array.length; i++) {
// 减 i 是为了不去比较已经排序正确的项,不减算法也能正常运行,只是会多做许多次没有必要的比较
for (let j=0; j<array.length-1-i; j++) {
if (compareFunc(array[j], array[j+1]) > 0) {
// 如果当前值比下一个值大 则与下一个值交换
[array[j], array[j+1]] = [array[j+1], array[j]]
}
}
}
return array
}
2)选择排序
//2.选择排序 最小的放最前面 紧接着第二小的 以此类推
const selectionSort = (array, compareFunc) => {
let indexMin;
//外循环负责控制迭代轮次 内循环负责找出每一轮中的最小值
for (let i=0; i<array.length-1; i++) {
//默认第一个为最小值
indexMin = i;
for (let j=i; j<array.length; j++) {
if (compareFunc(array[indexMin], array[j]) > 0) {
//如果当前值比最小值小更新最小值索引
indexMin = j;
}
}
if ( i !== indexMin) {
[array[i], array[indexMin]] = [array[indexMin], array[i]]
}
}
return array
}
3)插入排序
function insertSort(array) {
for (let i = 1; i < array.length; i++) {
let target = i;
for (let j = i - 1; j >= 0; j--) {
if (array[target] < array[j]) {
[array[target], array[j]] = [array[j], array[target]]
target = j;
} else {
break;
}
}
}
return array;
}
//3.插入排序 先前两个排序 再前三个排序 ,以此类推。每次排序把最小值排在最前面
const insertSort = (array, compareFunc) => {
for (let i=1; i<array.length; i++) {
let j = i;
let temp = array[i]; //临时变量存储待插入值
while( j > 0 && compareFunc(array[j-1], temp) > 0) {
//如果前一个位置值更大就把前面位置值移到当前位置,直到j=0位置
array[j] = array[j-1];
j--
}
//把带插入值插入它应该插入的位置, 如果没有进while循环 那值不变
array[j] = temp
}
return array
}
4)归并排序
归并排序是第一个可以实际使用的排序算法。前三个排序算法性能不好(复杂度为O(n^2)),但归并排序性能不错,其复杂度为 O(nlog(n))。
/*4.归并排序 先选一个中间点 把数组分成两个小数组,
*再把小数组选中间点分别分成更小的两个,最后都变成只有一个元素的数组,然后排序归并成一个数组
*/
const mergeSort = ( array, compareFunc ) => {
const { length } = array;
if (length > 1) {
let middle = Math.floor(length / 2);
let left = mergeSort(array.slice(0, middle), compareFunc);
let right = mergeSort(array.slice(middle, length), compareFunc);
array = merge(left, right, compareFunc)
}
return array
}
const merge = ( left, right, compareFunc) => {
let i = 0,
j = 0,
result = [];
while( i<left.length && j < right.length) {
result.push(
compareFunc(left[i], right[j]) < 0 ? left[i++] : right[j++]
)
}
return result.concat(i<left.length ? left.slice(i) : right.slice(j))
}
法二:和法一思想是类似的
function mergeSort(array) {
if (array.length < 2) {
return array;
}
const mid = Math.floor(array.length / 2);
const front = array.slice(0, mid);
const end = array.slice(mid);
return merge(mergeSort(front), mergeSort(end));
}
function merge(front, end) {
const temp = [];
while (front.length && end.length) {
if (front[0] < end[0]) {
temp.push(front.shift());
} else {
temp.push(end.shift());
}
}
while (front.length) {
temp.push(front.shift());
}
while (end.length) {
temp.push(end.shift());
}
return temp;
}
5)快速排序
快速排序也许是最常用的排序算法了。它的复杂度为 O(nlog(n)),且性能通常比其他复杂度为 O(nlog(n))的排序算法要好。它的算法思想如下:
(1) 首先,从数组中选择一个值作为主元(pivot),一般选数组中间的那个值。
(2) 创建两个指针(引用),左边一个指向数组第一个值,右边一个指向数组最后一个值。移动左指针直到我们找到一个比主元大的值,接着,移动右指针直到找到一个比主元小的值,然后交换它们,重复这个过程,直到左指针超过了右指针。这个过程将使得比主元小的值都排在主元之前,而比主元大的值都排在主元之后。这一步叫作划分(partition)操作。
(3) 接着,算法对划分后的小数组(较主元小的值组成的子数组,以及较主元大的值组成的子数组)重复之前的两个步骤,直至数组已完全排序。
const quickSort = (array, compareFn = defaultCompare) => {
return quick(array, 0, array.length - 1, compareFn);
};
const quick = (array, left, right, compareFn)=> {
let index;
if (array.length > 1) {
index = partition(array, left, right, compareFn);
if (left < index - 1) {
quick(array, left, index - 1, compareFn);
}
if (index < right) {
quick(array, index, right, compareFn);
}
}
return array;
};
const partition = (array, left, right, compareFn) => {
const pivot = array[Math.floor((right + left) / 2)];
let i = left;
let j = right;
while (i <= j) {
//每次外层循环如果在主元左边找到一个比主元大的或等于则不进入循环
while (compareFn(array[i], pivot) < 0) {
i++;
}
//每次外层循环如果在主元右边找到一个比主元小的或等于则不进入循环
while (compareFn(array[j], pivot) > 0) {
j--;
}
if (i <= j) {
//左右指针值交换
[array[i], array[j]] = [array[j], array[i]]
i++;
j--;
}
}
return i;
}
法二:不需要额外存储空间,写法思路稍复杂
记录一个索引l
从数组最左侧开始,记录一个索引r
从数组右侧开始
在l<r
的条件下,找到右侧小于target
的值array[r]
,并将其赋值到array[l]
在l<r
的条件下,找到左侧大于target
的值array[l]
,并将其赋值到array[r]
这样让l=r
时,左侧的值全部小于target
,右侧的值全部小于target
,将target
放到该位置
function quickSort(arr, start = 0, end = arr.length - 1) {
if (end - start < 1) {
return
}
let l = start;
let r = end;
const target = arr[start]
while(l < r) {
while(l < r && arr[r] >= target) {
r--
}
arr[l] = arr[r]
while(l < r && arr[l] < target) {
l++
}
arr[r] = arr[l]
}
arr[l] = target
quickSort(arr, start, l - 1)
quickSort(arr, l+ 1, end)
return arr
}
法三:浪费大量存储空间,写法简单
function quickSort(array) {
if (array.length < 2) {
return array;
}
const target = array[0];
const left = [];
const right = [];
for (let i = 1; i < array.length; i++) {
if (array[i] < target) {
left.push(array[i]);
} else {
right.push(array[i]);
}
}
return quickSort(left).concat([target], quickSort(right));
}
二、搜索算法
1)顺序或线性搜索是最基本的搜索算法。它的机制是,将每一个数据结构中的元素和我们要找的元素做比较。顺序搜索是最低效的一种搜索算法。
const DOES_NOT_EXIST = -1;
function sequentialSearch(array, value) {
for (let i = 0; i < array.length; i++) {
if (array[i] === value ) {
return i;
}
}
return DOES_NOT_EXIST;
}
2)二分搜索
其算法思想是先对要搜索的数据排序,把需要查找的值与数据中间值做比较,如果小于,就把中间值前面的数据重复前面的操作;如果大于就把中间值后面的数据重复前面的操作。类似与猜价格游戏,每说一个价格会说高了还是低了,我们会根据高了还是低了进行折半或加倍。
const DOES_NOT_EXIST = -1;
const binarySearch = ( array, value, compareFunc ) => {
if (!array.length) {
return DOES_NOT_EXIST
}
const sortedArray = quickSort(array, compareFunc);
let low = 0, high = array.length -1;
while(sortedArray[low] <= sortedArray[high]) {
let middle = Math.floor((low + high) /2);
if (compareFunc(value, sortedArray[middle]) < 0) {
high = middle -1;
} else if (compareFunc(value, sortedArray[middle]) > 0) {
low = middle + 1;
} else if (compareFunc(value, sortedArray[middle]) === 0) {
return middle;
}
}
return DOES_NOT_EXIST
}
三、随机算法(洗牌算法)
它的思想是迭代数组,从最后一位开始并将当前位置和一个随机位置进行交换。这个随机位置比当前位置小。这样,这个算法可以保证随机过的位置不会再被随机一次。
const shuffle = array => {
for (let i=array.length -1; i>0; i--) {
let randomIndex = Math.floor(Math.random()*(i + 1));
[array[i], array[randomIndex]] = [array[randomIndex], array[i]];
}
return array
}
本内容整理自《学习JavaScript数据结构与算法》