对计算机存储的数据执行的最常见的操作就是排序和检索。
接下来我们要讲解的算法都是依赖数组来存储数据的,也就是js形式的排序算法方式。
讲到排序,我们首先就要有一个可测试的数据, 也就是创建一个数据测试平台里的第一步,生成乱序的一个数组。
function CArray() {
this.dataStore = [];
this.pos = 0;
this.setData = setData;
this.swap = swap;
this.length = parseInt(arguments[0]);
}
function setData() {
for(var i = 0; i < this.length; i++) {
var item = Math.floor(Math.random()*(this.length + 1));
this.dataStore.push(item);
}
}
这样,我们首先就有了乱序的数据了
比如,
var a = new CArray(10);
a.setData();
console.log(a.dataStore.toString());
噢,这里我直接用参数的第一个作为数组长度。
比较算法,数据量一般都是挺大的,因此我们可以用每10个数据一行的方式排列下去。
CArray.prototype.toString = function() {
var str = '';
for(var i = 0; i < this.length; i++) {
if(i % 10 == 9) {
str += this.dataStore[i] + ' ' + '\n';
} else {
str += this.dataStore[i]+ ' ';
}
}
return str;
}
var a = new CArray(20);
a.setData();
console.log(a.toString());
好的,数据已经出来了,开始排序
基本排序算法
这里我们首先增加一个交换数值位置的函数。
CArray.prototype.swap= function(arr, index1, index2) {
var item = arr[index1];
arr[index1] = arr[index2];
arr[index2] = item;
}
冒泡排序
最慢的排序方式之一,复杂度为O(n²)。
此算法中,算法会在数组中多次移动。比较左右相邻的数据,当左侧大于右侧时进行交换。
算法实现
CArray.prototype.bubbleSort = function() {
for(var j = 1; j < this.length; j++)
for(var i = 0; i < this.length -j; i++) {
if(this.dataStore[i] > this.dataStore[i+1]) this.swap(this.dataStore,i, i+1);
}
}
var a = new CArray(20);
a.setData();
console.log(a.toString());
a.bubbleSort();
console.log(a.toString());
选择排序
第一个元素和其他元素进行比较,比较完之后,最小的元素会被放到数组的第一个位置
-
然后算法从第二个位置继续。
CArray.prototype.selectionSort = function () { for (var j = 0; j < this.length - 1; j++) { var min = j; for (var i = j + 1; i < this.length; i++) { if (this.dataStore[min] > this.dataStore[i]) { min = i; } } this.swap(this.dataStore, j, min); } } var a = new CArray(20); a.setData(); console.log(a.toString()); a.selectionSort(); console.log(a.toString());
插入排序
插入排序有两个循环。
外循环将数组元素挨个移动,内循环对外循环中选中的元素及他后面的那个元素怒进行比较。
-
如果外循环中选中的元素比内循环中选中的元素小,那么数组元素会向右移动,为内循环中的这个元素腾出位置。
// 跑到前面,只跟在他前面的数进行对比 // 比如第一个就是只跟第0个比较,第n就是跟前面n-1个数比较,不跟在他后面的数比较 CArray.prototype.insertionSort = function() { var temp, inner; for (var outer = 1; outer < this.length; outer++) { temp = this.dataStore[outer]; inner = outer; while(inner > 0 && (this.dataStore[inner - 1] >= temp)) { this.dataStore[inner] = this.dataStore[inner - 1]; --inner; } this.dataStore[inner] = temp; } } var a = new CArray(30); a.setData(); console.log(a.toString()); a.insertionSort(); console.log(a.toString());
数据小的时候,其实这些算法所用的时间都是差不多的,当数据很大的时候才会出现差别。这三种算法中插入排序是最快的,冒泡排序是最慢
高级排序算法
希尔排序
希尔排序的工作原理是通过定义一个间隔序列来表示在排序过程中进行比较的元素之间有多远的间隔。
与插入排序不同,他会首先比较距离较远的元素。
先在CArray构造函数中添加this.gaps=[3, 1]
,像这样
CArray.prototype.shellSort = function () {
for (var g = 0; g < this.gaps.length; g++) {
for (var i = this.gaps[g]; i < this.dataStore.length; ++i) {
// 假设间距为3, 则第三个与第0个做比较,
// 比较第6个与第3个,若第6个小于第3个则交换位置,再与第0个做比较
// 若第9个小于第6个,则9, 6交换位置,之后重复进行第二步操作
while(i >= this.gaps[g] && this.dataStore[i-this.gaps[g]] > this.dataStore[i]) {
this.swap(this.dataStore, i, i - this.gaps[g]);
i -= this.gaps[g];
}
}
}
}
var a = new CArray(20);
a.setData();
console.log(a.toString());
a.shellSort();
console.log(a.toString());
然而,其实这个排序更重要的是如何确定初始间隔值,如何动态的确定间隔序列呢?
在《算法》 中有这样一个公式
var N = this.dataStore.length;
var h = 1;
while(h < N/3) {
h = 3 * h + 1;
}
虽然不明白为什么是用3作为每次gaps的间隔,但还是将就一点吧
因此,我们可以重新定义shellSort函数
CArray.prototype.shellSort1 = function() {
var N = this.dataStore.length;
var h = 1;
while(h < N/3) {
h = 3 * h + 1;
}
while(h >= 1) {
for(var i = h; i < this.dataStore.length; i++) {
while(i >= h && this.dataStore[i-h] > this.dataStore[i]) {
this.swap(this.dataStore, i, i - h);
i -= h;
}
}
h = (h - 1) / 3;
}
}
归并排序
归并排序的实现原理是把一系列排好序的子序列合并成一个大的完整有序序列。
由于自顶向下的归并排序会使用递归的算法实现, 这个算法对与庞大的数据量来说,递归的深度太深了,所以我们经常使用的是自底向上的归并排序方式。
- 先将数据集分解成一组只有一个元素的数组
-
然后创建一个左右子数组将他们慢慢合并起来,每次都保存排好序的数据。
这是通过每个小组分别排序进行的。emmmm,内部又是怎么排序的呢?小组内部就是比较大小咯。
开始算法
CArray.prototype.mergeSort = function () {
var step = 1;
// 每次两组两组的合并
while (step < this.length) {
var left = 0;
var right = step;
while (right + step < this.length) {
this.mergeArrays(this.dataStore, left, left + step, right, right + step);
left = right + step;
right = left + step;
}
if (right < this.length) {
this.mergeArrays(this.dataStore, left, left + step, right, this.length);
}
step *= 2;
}
}
CArray.prototype.mergeArrays = function (arr, leftStart, leftStop, rightStart, rightStop) {
var leftArr = [];
var rightArr = [];
for(var k = leftStart; k < leftStop; k++) {
leftArr.push(arr[k]);
}
for(var j = rightStart; j < rightStop; j++) {
rightArr.push(arr[j]);
}
// 进入两个无穷大,即在永远不会超过数组 leftArr 和 rightArr的长度
leftArr.push(Infinity);
rightArr.push(Infinity);
var m = 0;
var n = 0;
for (var k = leftStart; k < rightStop; k++) {
if (leftArr[m] > rightArr[n]) {
arr[k] = rightArr[n];
n++;
} else {
arr[k] = leftArr[m];
m++;
}
}
}
var a = new CArray(10);
a.setData();
console.log(a.toString());
a.mergeSort();
console.log(a.toString());
这个归并排序讲起来不难,但实现起来经常会有一些小bug,也是很需要小心的了。
快速排序
快速排序是处理大数据集最快的排序算法之一。通过递归分解为较小元素和较大元素的不同子序列,不断重复该步骤直到所有数据有序。
这里运用到的是递归,所以我们就不写在构造器里面了,我们直接写成一个函数的形式。
function quickSort() {
var arr = arguments[0];
if(arr.length == 0) {
return [];
}
var middle = arr[0];
var greater = [];
var lesser = [];
for(var i = 1; i < arr.length; i++) {
if(arr[i] < middle) {
lesser.push(arr[i]);
} else {
greater.push(arr[i]);
}
}
return quickSort(lesser).concat(middle, quickSort(greater));
}
之后再使用call方法调用这个函数
var a = new CArray(10);
a.setData();
console.log(a.toString());
a.dataStore = quickSort.call(a, a.dataStore);
console.log(a.toString());
总结,快速排序算法是比较适合于大型数据。在小数据集的时候性能反而会下降,因为这个递归真是太多了吧,,如果小数据集的话可能跑过程都很久了。