对计算机存储的数据执行的最常见的操作就是排序和检索。
接下来我们要讲解的算法都是依赖数组来存储数据的,也就是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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-bb0be71005d3551a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
噢,这里我直接用参数的第一个作为数组长度。
![image.png](https://upload-images.jianshu.io/upload_images/2986075-379a5f920623611e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
比较算法,数据量一般都是挺大的,因此我们可以用每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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-1f0717e7efdafa39.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
好的,数据已经出来了,开始排序
##基本排序算法
这里我们首先增加一个**交换数值位置**的函数。
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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-61e9ee6f891f54b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####选择排序
- 第一个元素和其他元素进行比较,比较完之后,最小的元素会被放到数组的第一个位置
- 然后算法从第二个位置继续。
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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-0793af36bd041282.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####插入排序
插入排序有两个循环。
- 外循环将数组元素挨个移动,内循环对外循环中选中的元素及他后面的那个元素怒进行比较。
- 如果外循环中选中的元素比内循环中选中的元素小,那么数组元素会向右移动,为内循环中的这个元素腾出位置。
// 跑到前面,只跟在他前面的数进行对比
// 比如第一个就是只跟第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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-ac7c5fd5d0325252.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
> 数据小的时候,其实这些算法所用的时间都是差不多的,当数据很大的时候才会出现差别。这三种算法中插入排序是最快的,冒泡排序是最慢
##高级排序算法
#####希尔排序
希尔排序的工作原理是通过定义**一个间隔序列**来表示在排序过程中进行比较的元素之间有多远的间隔。
与插入排序不同,他会**首先比较距离较远**的元素。
先在CArray构造函数中添加`this.gaps=[3, 1]`,像这样
![image.png](https://upload-images.jianshu.io/upload_images/2986075-df4c2a782b9d2bb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-0ae02255360faf43.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
然而,其实这个排序更重要的是如何确定初始间隔值,如何动态的确定间隔序列呢?
在《算法》 中有这样一个公式
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;
}
}
![image.png](https://upload-images.jianshu.io/upload_images/2986075-2405807868eaf060.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####归并排序
归并排序的实现原理是把一系列**排好序的子序列**合并成一个大的完整有序序列。
由于自顶向下的归并排序会使用递归的算法实现, 这个算法对与庞大的数据量来说,递归的深度太深了,所以我们经常使用的是**自底向上的归并排序方式**。
- 先将数据集分解成一组只有一个元素的数组
- 然后创建一个左右子数组将他们慢慢合并起来,每次都保存排好序的数据。
![image.png](https://upload-images.jianshu.io/upload_images/2986075-0195fba8e8721468.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这是通过每个小组分别排序进行的。emmmm,内部又是怎么排序的呢?小组内部就是比较大小咯。
![image.png](https://upload-images.jianshu.io/upload_images/2986075-b3ca9e6938889d1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
开始算法
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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-bf5b4b521742b89c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这个归并排序讲起来不难,但实现起来经常会有一些小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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-e99fee8e6a188761.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
总结,快速排序算法是**比较适合于大型数据**。在小数据集的时候性能反而会下降,因为这个递归真是太多了吧,,如果小数据集的话可能跑过程都很久了。
接下来我们要讲解的算法都是依赖数组来存储数据的,也就是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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-bb0be71005d3551a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
噢,这里我直接用参数的第一个作为数组长度。
![image.png](https://upload-images.jianshu.io/upload_images/2986075-379a5f920623611e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
比较算法,数据量一般都是挺大的,因此我们可以用每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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-1f0717e7efdafa39.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
好的,数据已经出来了,开始排序
##基本排序算法
这里我们首先增加一个**交换数值位置**的函数。
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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-61e9ee6f891f54b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####选择排序
- 第一个元素和其他元素进行比较,比较完之后,最小的元素会被放到数组的第一个位置
- 然后算法从第二个位置继续。
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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-0793af36bd041282.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####插入排序
插入排序有两个循环。
- 外循环将数组元素挨个移动,内循环对外循环中选中的元素及他后面的那个元素怒进行比较。
- 如果外循环中选中的元素比内循环中选中的元素小,那么数组元素会向右移动,为内循环中的这个元素腾出位置。
// 跑到前面,只跟在他前面的数进行对比
// 比如第一个就是只跟第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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-ac7c5fd5d0325252.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
> 数据小的时候,其实这些算法所用的时间都是差不多的,当数据很大的时候才会出现差别。这三种算法中插入排序是最快的,冒泡排序是最慢
##高级排序算法
#####希尔排序
希尔排序的工作原理是通过定义**一个间隔序列**来表示在排序过程中进行比较的元素之间有多远的间隔。
与插入排序不同,他会**首先比较距离较远**的元素。
先在CArray构造函数中添加`this.gaps=[3, 1]`,像这样
![image.png](https://upload-images.jianshu.io/upload_images/2986075-df4c2a782b9d2bb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-0ae02255360faf43.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
然而,其实这个排序更重要的是如何确定初始间隔值,如何动态的确定间隔序列呢?
在《算法》 中有这样一个公式
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;
}
}
![image.png](https://upload-images.jianshu.io/upload_images/2986075-2405807868eaf060.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####归并排序
归并排序的实现原理是把一系列**排好序的子序列**合并成一个大的完整有序序列。
由于自顶向下的归并排序会使用递归的算法实现, 这个算法对与庞大的数据量来说,递归的深度太深了,所以我们经常使用的是**自底向上的归并排序方式**。
- 先将数据集分解成一组只有一个元素的数组
- 然后创建一个左右子数组将他们慢慢合并起来,每次都保存排好序的数据。
![image.png](https://upload-images.jianshu.io/upload_images/2986075-0195fba8e8721468.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这是通过每个小组分别排序进行的。emmmm,内部又是怎么排序的呢?小组内部就是比较大小咯。
![image.png](https://upload-images.jianshu.io/upload_images/2986075-b3ca9e6938889d1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
开始算法
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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-bf5b4b521742b89c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这个归并排序讲起来不难,但实现起来经常会有一些小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());
![image.png](https://upload-images.jianshu.io/upload_images/2986075-e99fee8e6a188761.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
总结,快速排序算法是**比较适合于大型数据**。在小数据集的时候性能反而会下降,因为这个递归真是太多了吧,,如果小数据集的话可能跑过程都很久了。