相关概念
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
二分查找的时间复杂度是O(n)
简单排序
- 冒泡排序
交换次数O(n的平方)
这里封装了一个数组用于插入元素,还有一个公用的代码
this.array = [];
//插入
ArrayList.prototype.insert = function(item) {
this.array.push(item);
}
//toString()
ArrayList.prototype.toString = function(item) {
return this.array.join('-');
}
//交换数据
ArrayList.prototype.swap = function(m, n) {
var temp = this.array[m];
this.array[m] = this.array[n];
this.array[n] = temp;
}
//冒泡排序
ArrayList.prototype.bubblesort = function(item) {
var length = this.array.length;
//注意是j>0
for (var j = length - 1; j > 0; j--) {
for (var i = 0; i < j; i++) {
if (this.array[i] > this.array[i + 1]) {
this.swap(i, i + 1);
}
}
}
}
- 选择排序
交换次数O(n)
//选择排序
ArrayList.prototype.selectsort = function() {
var length = this.array.length;
//注意length-1
for (var j = 0; j < length - 1; j++) {
var min = j;
for (var i = min + 1; i < length; i++) {
if (this.array[min] > this.array[i]) {
min = i;
}
}
this.swap(min, j);
}
}
- 插入排序
是简单排序中效率最高的
从第二个数字开始与前面的数字一一相比较,比它大的就让其往后排,比它小的就插在它后面
//插入排序
ArrayList.prototype.insertSort = function() {
var length = this.array.length;
for (var i = 1; i < length; i++) {
var j = i - 1;
//获取i位置的元素 和前面的元素一一进行比较
var temp = this.array[i];
//核心
while (this.array[j] > temp && j >= 0) {
this.array[j + 1] = this.array[j];
j--;
}
//注意这里是j+1!!!
this.array[j + 1] = temp;
}
}
高级排序
- 希尔排序
希尔排序与插入排序类似,就是将数据分成多个不同的组,在每个组内进行插入排序,在代码上比插入排序多了一层循环
//希尔排序
ArrayList.prototype.shellSort = function() {
var length = this.array.length;
//初始化增量
//Math.floor()向下取整
var gap = Math.floor(length / 2);
//让gap不断减小
while (gap >= 1) {
for (var i = gap; i < length; i++) {
//以gap作为间隔进行分组
//对分组中的数据进行插入排序
var temp = this.array[i];
var j = i - gap;
while (this.array[j] > temp && j >= 0) {
this.array[j + gap] = this.array[j];
j -= gap;
}
//注意这里是j+gap!!!
this.array[j + gap] = temp;
}
gap = Math.floor(gap / 2);
}
}
- 快速排序
快速排序有两种方法
1.内存消耗较大。找到一个pivot基准,并去除,然后将比其小的数放在left数组里,将比其大的数放在right数组里,最后将两个数组和pivot基准合并
//快速排序-1
function quickSort(arr) {
//如果数组长度为1直接返回
if (arr.length < 1) {
return arr;
}
//找到那个基准数的索引
var pivotIndex = Math.floor(arr.length / 2);
//因为splice返回的是一个数组,我们只删除了一个元素,所以这里取索引为0的值
var pivot = arr.splice(pivotIndex, 1)[0];
//console.log(pivot);
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right)); //加入基准数
}
arr = [33, 12, 23, 2, 44, 67, 3, 11, 15];
console.log(quickSort(arr)); //[2, 3, 11, 12, 15, 23, 33, 44, 67]
2.(正宗)性能较好。将数组的第一个或者中间一个或者最后一个作为基准,然后j从右向左寻找比基准小的数,将i和j交换,然后i从左到右寻找比基准大的数,将i和j交换,直到i和j都指向了基准;此时pivot基准已经排到了它的位置并且不会再移动;再对基准两边的数组进行快速排序
//快速排序-2 选第一个数为基准
function quickSort2(arr, left, right) {
//选第一个数为基准
var pivot = arr[left];
var i = left;
var j = right;
while (j > i) {
//一直都是i与j交换
while (j > i && arr[j] >= pivot) j--;
if (arr[j] <= pivot) {
var temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
while (j > i && arr[i] <= pivot) i++;
if (arr[i] >= pivot) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//当循环完后,i和j都指向pivot基准,此时分别对基准左右两边的数组进行快速排序
if (i > left) quickSort2(arr, left, i - 1);
if (j < right) quickSort2(arr, j + 1, right);
}
var arr = [33, 12, 23, 2, 44, 67, 3, 11, 15];
var start = 0;
var end = arr.length - 1;
quickSort2(arr, start, end);
console.log('After arr:' + arr); //After arr:2,3,11,12,15,23,33,44,67
//以下是b站上面的方法,但是运行有问题,思路大致没错,可做参考
找出一个枢纽(pivot),在pivot的左边从左到右查找比pivot大的数,标记为left,在pivot的右边从右到左查找比pivot小的数,标记为right,交换left和right,继续查找,直到left和right重合或者right小于left,再将left和pivot交换;第二轮将left左边和右边分别进行快速排序
可以说是目前所有的排序算法中,最快的一种排序算法
快速排序可以在一次循环中(其实是递归调用),找出某个元素的正确位置,并且该元素之后不需要任何移动
//快速排序
//选择枢纽
ArrayList.prototype.media = function(left, right) {
//取出中间位置
var center = Math.floor((left + right) / 2);
//判断并进行交换
if (this.array[left] > this.array[center]) {
this.swap(left, center);
}
if (this.array[left] > this.array[right]) {
this.swap(left, right);
}
if (this.array[center] > this.array[right]) {
this.swap(center, right);
}
//将center交换到right-1的位置
this.swap(center, right - 1);
//返回center
return this.array[right - 1];
}
ArrayList.prototype.quickSort = function() {
this.quick(0, this.array.length - 1);
}
//递归函数
ArrayList.prototype.quick = function(left, right) {
//结束条件
if (left >= right) return;
//else
//获取枢纽
var pivot = this.media(left, right);
//定义左右的变量,标记当前找到的位置
//左指针
var i = left;
//右指针
var j = right - 1;
//进行交换
while (true) {
//从左往右找比pivot大的数
while (this.array[++i] < pivot) {
//如果小于pivot,则继续查找
}
//从右往左找比pivot小的数
while (this.array[--j] > pivot) {
//如果大于pivot,则继续查找
}
//如果左指针没有超过右指针
if (i < j) {
this.swap(i, j);
} else {
break;
}
}
//将枢纽放置在正确的位置
this.swap(i, right - 1);
//分别对pivot的左右两边进行递归
this.quick(left, i - 1);
this.quick(i + 1, right);
}
归并排序
采用分治法
运行出来有问题,但是还没找出来问题
大致的思路是对的
//归并排序
//这个方法是针对左右两边都是有序数组的情况来说的
function merge(arr, l, m, r) {
//存储左边一半的有序数组
let left = [];
//存储右边一半的有序数组
let right = [];
//左边数组的大小
let leftsize = m - l + 1;
//右边数组的大小
let rightsize = r - m;
//将左边一半的数组存入left中
for (let i = l; i <= m; i++) {
left.push(arr[i]);
}
console.log(left);
//将右边一半的数组存入right中
for (let j = m + 1; j <= r; j++) {
right.push(arr[j]);
}
console.log(right);
let i = 0; //遍历left数组
let j = 0; //遍历right数组
let k = 0; //遍历arr数组
while (i < leftsize && j < rightsize) {
//left数组和right数组一个一个值进行比较,小的那个先存入arr中,再将指针++
if (left[i] < right[j]) {
arr[k] = left[i];
i++;
k++;
} else if (left[j] < right[i]) {
arr[k] = right[j];
j++;
k++;
}
}
console.log(arr);
//当left或者right数组的指针遍历到最后时,
//将另一个没有遍历完的数组中的值直接加入到arr中
while (i < leftsize) {
arr[k] = left[i];
i++;
k++;
}
while (j < leftsize) {
arr[k] = right[j];
j++;
k++;
}
console.log(arr);
return arr;
}
//分治
//将数组不断的进行分割
//由两个到三个到四个这样不断的合并成有序数组
//最后返回一个总的有序数组
function mergeSort(arr, L, R) {
if (L == R) {
return;
} else {
let M = Math.floor((L + R) / 2);
mergeSort(arr, L, M);
mergeSort(arr, M + 1, R);
merge(arr, L, M, R);
return arr;
}
}
let arr = [2, 8, 9, 10, 13, 5, 6, 7, 11]
console.log(merge(arr, 0, 4, 8));
console.log(mergeSort(arr, 0, 8));
堆排序
//堆:要满足两个条件
//1.是一颗完全二叉树
//2.每个节点都要满足父节点的值大于子节点的值
//这个方法的前提条件是除去根节点,其他节点都是一个堆
//这是自顶向下的
function heapify(tree, n, i) {
if (i >= n) {
return;
}
//两个子节点
let c1 = 2 * i + 1;
let c2 = 2 * i + 2;
let max = i;
if (c1 < n && tree[c1] > tree[max]) {
max = c1;
}
if (c2 < n && tree[c2] > tree[max]) {
max = c2;
}
if (max != i) {
//将max和i所在位置的内容进行交换
swap(tree, max, i);
//对max所在的位置进行堆排序
heapify(tree, n, max);
}
return tree;
}
function swap(tree, a, b) {
let temp = tree[a];
tree[a] = tree[b];
tree[b] = temp;
}
//构建堆
//从最后一个节点的父节点开始向上构建堆
//自底向上
function buildHeap(tree, n) {
let lastNode = n - 1; //最后一个节点
let parent = Math.floor((lastNode - 1) / 2); //最后一个节点的父节点
for (let i = parent; i >= 0; i--) {
heapify(tree, n, i);
}
return tree;
}
//堆排序
//在一个堆中,最大的数肯定是在最上面的
//堆排序的过程是,将最大的那个数(即根节点上的数)与最后一个子节点交换,这样最大值就到了最后
//然后除去这个子节点,再对其他节点进行heapify(因为节点发生了交换所以堆结构可能被破坏了)
//重复上面的步骤,要进行heapify的堆的长度慢慢减少,最大的数一个一个排到最后
//最后便形成了一个从小到大的二叉树
function heapSort(tree, n) {
buildHeap(tree, n);
for (let i = n - 1; i >= 0; i--) {
swap(tree, i, 0); //将子节点与根节点进行交换
//对其他节点进行heapify,重新排列成一个堆
heapify(tree, i, 0); //要拿掉最后一个子节点,这里是将堆的长度减一
}
return tree;
}
let tree = [4, 10, 3, 5, 1, 2];
console.log(heapify(tree, 6, 0)); //[10, 5, 3, 4, 1, 2]
let tree2 = [2, 5, 3, 1, 10, 4];
console.log(heapSort(tree2, 6)); //[1, 2, 3, 4, 5, 10]