前言:
前端时间,上班的时候比较闲,将《数据结构与算法javascript描述》过了一遍。在这期间脑海中刻入了一个排序算法的方法,一直在脑中回旋,久久挥之不去。那个排序算法大概是这样描述的——在数组中去选择一个基准值,将这个值与数组中的其他值做比较,将比它大的值放在后边,将比它小的值放在它的前边。(一个模糊的记忆)为了寻找这句话对应的究竟是哪个排序算法,就好好将排序的算法都敲了一遍,在此做一个简单的记录。
冒泡排序:
- 基本定义:
使用这种排序算法时,数据值会像气泡一样从数组的一端漂浮到另一端。假设正在将一组数字按照升序排列,较大的值会浮动到数组的右侧,而较小的值则会浮动到数组的左侧。之所以会产生这种现象是因为算法会多次在数组中移动,比较相邻的数据,当左侧值大于右侧值时将它们进行交换。
- 动画演示:
3. 实现思路:
1. 比较所有相邻元素,如果第一个比第二个大,则交换它们。
2. 一轮下来,可以保证最后一个数是最大的。
3. 执行len-1轮,就可以完成排序。
4. 里面那一层需要减去外部排好序的那一层的个数。
- 实现方式:
let arr = [1,8,3,6,2,1,8,9];
let len = arr.length;
// 循环所有的元素
for(let j = 0; j < len; j++) {
// 循环内部未排好序的数据
for(let i = 0; i < len - j; i++) {
// 如果第一个数比第二个数大
if(arr[i] > arr[i + 1]) {
// 如果条件成立,则将这两个数的位置互换
let temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
console.log(`当前行的排序:${arr}`);
}
console.log(arr);
- 函数封装:
// 冒泡排序
function bubblingAsc(arr) {
let len = arr.length;
for (let i = 0; i <= len - 1; i++) {
for (let j = 0; j <= len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
let tmep = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = tmep
}
}
// console.log(`第${i + 1}次排序:${arr}`)
}
return arr
}
- 时间复杂度与空间复杂度:
一个循环的时间复杂度为O(n),如果是两个循环叠加则用乘法,所以冒泡排序的时间复杂度为: O(n)*O(n) = O(n^2)
因为所有的排序都是对一维数组的排序,所以空间复杂度恒等于O(n)
选择排序
- 基本定义:
从数组的开头开始,将第一个元素和其他元素进行比较。检查完所有元素后,最小的元素会被放到数组的第一个位置,然后算法会从第二个位置继续。这个过程一直进行,当进行到数组的倒数第二个位置时,所有的数据便完成了排序。
- 动画演示:
- 实现思路:
1. 声明一个最小值
2. 从左到右依次循环,假设当前值为最小值
3. 用最小值与其他值(它后面的其他所有元素)进行比较
4. 如果其他值小于最小值,则其他值才是最小值,将其索引赋值给最小值
5. 如果最小值与当前值的索引不相等,则将最小值与当前值互换位置
- 实现方式:
let arr = [5,4,3,2,1];
let len = arr.length;
let min;
for(let i = 0; i < len; i++) {
min = i;
for(let j = i + 1; j < len; j++) {
if(arr[j] < arr[min]) {
min = j;
}
}
if (min !== i) {
let temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
console.log(arr);
- 函数封装:
function selectSort (arr) {
let len = arr.length;
let min; // 建一个最小值
for (let i = 0; i < len; i++) {
min = i // 从左到右依次循环,假设当前值为最小值
for(let j = i + 1; j < len; j++) { // 用当前值与其他值(它后面的其他所有元素)进行比较
if(arr[j] < arr[min]) { // 如果其他值小于当前值,则后其他值才是最小值,将其索引赋值给最小值
min = j
}
console.log('当前值:' + arr[i], '其他值:' + arr[j], '最小值:' + arr[min]);
}
// 如果最小值与当前值的索引不相等,则将最小值与当前值互换位置
if(min != i) {
let tmep = arr[i];
arr[i] = arr[min];
arr[min] = tmep;
}
}
return arr
}
- 时间复杂度与空间复杂度:
一个循环的时间复杂度为O(n),如果是两个循环叠加则用乘法,所以冒泡排序的时间复杂度为: O(n)*O(n) = O(n^2)
因为所有的排序都是对一维数组的排序,所以空间复杂度恒等于O(n)
插入排序
- 基本定义:
从数组的第二个位置开始,依次往前进行比较,如果比它大则将这个比它大的数往后排,将这个值插入到小于或等于这个值的位置。
- 动画演示:
3. 实现思路:
1. 从第二个值开始循环;
2. 如果第一个值大于第二个值,则将第一个值插入到第二个值的位置,如果小于则不作任何操作
3. 此处逻辑为第一个值如果大于或等于第二个值,则交换位置,如果小于则位置不变
- 实现方式:
let arr = [5,7,1,3,2];
for(let i = 1; i <= arr.length - 1; i++) {
let temp = arr[i];
let j = i;
while(j > 0) {
if(arr[j - 1] >= temp) {
arr[j] = arr[j - 1];
} else {
break;
}
j--
}
arr[j] = temp;
}
console.log(arr);
- 函数封装:
function insertSort (arr) {
let len = arr.length;
for(let i = 1; i <= len - 1; i++) {
let temp = arr[i];
let j = i;
while(j > 0) {
if(arr[j - 1] >= temp) {
arr[j] = arr[j - 1];
} else {
break;
}
j--
}
arr[j] = temp;
}
return arr
}
- 时间复杂度与空间复杂度:
一个循环的时间复杂度为O(n),如果是两个循环叠加则用乘法,所以冒泡排序的时间复杂度为: O(n)*O(n) = O(n^2)
因为所有的排序都是对一维数组的排序,所以空间复杂度恒等于O(n)
基本排序算法的耗时比较:
- 随机数组;
我们这块儿需要做的是对10个元素,100个元素,1000个元素,甚至10000个元素进行算法程序的耗时比较。此时如果还像之前那样自己定一个数组,然后再进行排序的话,就单单写数据都会写疯。在此面对不同的需求与应用场景我们必须采用不同的做法来应对,于是生成随机数组的函数就此诞生。
- 函数具体实现:(暂时只对数字排序进行测试)
// 生成min-max的随机数
function randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// 生成随机数字写入数组
function setNumber(len) {
let arr = []
for (let i = 0; i <= len - 1; i++) {
arr.push(randomNumber(0, len))
}
return arr
}
- 将以上的函数整合成一个类:
class arraySort {
constructor (len) {
this.arr = this.setNumber(len)
this.len = this.arr.length;
}
// 冒泡排序
bubblingAsc () {
const timestart = new Date().getTime();
for(let i = 0; i <= this.len - 1; i++) {
for(let j = 0; j <= this.len - 1 - i; j++) {
// 左右两个值进行比较,如果左边比右边的值大,则交换位置
if(this.arr[j] > this.arr[j + 1]) {
let tmep = this.arr[j]
this.arr[j] = this.arr[j + 1]
this.arr[j + 1] = tmep
}
}
// console.log(`第${i + 1}次排序:${this.arr}`)
}
const timeEnd = new Date().getTime();
// 时长
const duration = timeEnd - timestart
console.log(`冒泡排序:当有${this.len}个元素,程序执行时长: ${duration}`)
return this.arr
}
// 选择排序
selectSort () {
const timestart = new Date().getTime();
let min; // 建一个最小值
for (let i = 0; i <= this.len - 1; i++) {
min = i // 从左到右依次循环,假设当前值为最小值
for(let j = i + 1; j <= this.len - 1; j++) { // 用当前值与其他值(它后面的其他所有元素)进行比较
if(this.arr[j] < this.arr[min]) { // 如果其他值小于当前值,则后其他值才是最小值,将其索引赋值给最小值
min = j
}
// console.log('当前值:' + this.arr[i], '其他值:' + this.arr[j], '最小值:' + this.arr[min]);
}
// 将最小值与当前值互换位置-实际上是将最小值放到数组第一个位置,然后算法会从第二个位置继续。
let tmep = this.arr[i];
this.arr[i] = this.arr[min];
this.arr[min] = tmep;
}
const timeEnd = new Date().getTime();
// 时长
const duration = timeEnd - timestart
console.log(`选择排序:当有${this.len}个元素,程序执行时长: ${duration}`)
return this.arr
}
// 插入排序
insertSort () {
const timestart = new Date().getTime();
for(let i = 1; i <= this.len - 1; i++) {
let temp = this.arr[i]; // 第二个元素的值
let j = i;
while(j > 0) {
if(this.arr[j - 1] >= temp) {
// 如果第二个值大于第一个值,则第二个值等于第一个值
this.arr[j] = this.arr[j - 1];
} else {
break;
}
j--
}
// 此处逻辑为第二个值如果大于或等于第一个值,则交换位置,如果小于则位置不变
this.arr[j] = temp;
}
const timeEnd = new Date().getTime();
// 时长
const duration = timeEnd - timestart
console.log(`插入排序:当有${this.len}个元素,程序执行时长: ${duration}`)
return this.arr
}
// 生成min-max的随机数
randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// 生成随机数字写入数组
setNumber(len) {
let arr = []
for (let i = 0; i <= len - 1; i++) {
arr.push(this.randomNumber(0, len))
}
return arr
}
}
- 测试用例: 代码:10
let arr = new arraySort(10);
console.log(arr.bubblingAsc())
console.log(arr.selectSort())
console.log(arr.insertSort())
结果:
冒泡排序:当有10个元素,程序执行时长: 0
[0, 1, 1, 1, 1, 3, 3, 4, 6, 7]
选择排序:当有10个元素,程序执行时长: 0
[0, 1, 1, 1, 1, 3, 3, 4, 6, 7]
插入排序:当有10个元素,程序执行时长: 0
[0, 1, 1, 1, 1, 3, 3, 4, 6, 7]
代码: 100
let arr = new arraySort(100);
console.log(arr.bubblingAsc())
console.log(arr.selectSort())
console.log(arr.insertSort())
结果:
冒泡排序:当有100个元素,程序执行时长: 1
选择排序:当有100个元素,程序执行时长: 0
插入排序:当有100个元素,程序执行时长: 0
代码:1000
let arr = new arraySort(1000);
console.log(arr.bubblingAsc())
console.log(arr.selectSort())
console.log(arr.insertSort())
结果:
冒泡排序:当有1000个元素,程序执行时长: 5
选择排序:当有1000个元素,程序执行时长: 3
插入排序:当有1000个元素,程序执行时长: 0
代码:10000
let arr = new arraySort(10000);
console.log(arr.bubblingAsc())
console.log(arr.selectSort())
console.log(arr.insertSort())
结果:
冒泡排序:当有10000个元素,程序执行时长: 202
选择排序:当有10000个元素,程序执行时长: 78
插入排序:当有10000个元素,程序执行时长: 2
综上所述:插入排序在元素个数相同的情况下,在基本排序中的执行速度是最快的!
归并排序:
- 基本定义:
把一系列排好序的子序列合并成一个大的完整有序序列。
- 动画演示:
3.实现思路:
1. 将数组进行二分,递归分到最小的单个元素为止;
2. 将单个元素进行比较元素大小,对其进行排序
3. 将排好序的数据进行合并,最终合并成原始数据排好序的数组
4.代码实现:
const arr = [1,3,8,9,10,4,6,94,3];
function mergeSort (arr) {
if (arr?.length <= 1) {
return arr
}
let mid = Math.floor(arr?.length/2);
let left = arr.slice(0, mid);
let rigth = arr.slice(mid);
return merge(mergeSort(left), mergeSort(rigth))
}
function merge(left, rigth) {
let temp = [];
while (left?.length && rigth?.length) {
if (left[0] < rigth[0]) {
temp.push(left.shift());
} else {
temp.push(rigth.shift());
}
}
return [...temp, ...left, ...rigth]
}
console.log(mergeSort(arr));// [1, 3, 3, 4, 6, 8, 9, 10, 94]
- 时间复杂度与空间复杂度
时间复杂度:递归二分的时间复杂度为o(logN),循环的时间复杂度为o(n),嵌套则将时间复杂度相乘o(logN)*o(n)
快速排序:
- 基本定义:
这个算法首先要在列表中选择一个元素作为基准值。数据排序围绕基准值进行。将列表中小于基准值的元素移到数组底部,将大于基准值的元素移到数组顶部。
- 动画演示:
3. 实现思路:
1. 将数组的第一个值设置为基准值,如果大于基准值,则写入右侧,如果小于基准值则写入左侧。
2. 将排好序的数组与基准值进行合并
- 具体实现:
function quickSort (list) {
if (!list?.length) {
return []
}
let left = [];
let rigth = [];
let pivot = list[0];
for (let i = 1; i < list?.length; i++) {
if (list[i] > pivot) {
rigth.push(list[i]);
} else {
left.push(list[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(rigth)];
}
let arr = [8,7,9,5,6,1];
console.log(quickSort(arr)); // [1, 5, 6, 7, 8, 9]
- 时间复杂度与空间复杂度
时间复杂度:递归的时间复杂度为o(logN),循环的时间复杂度为o(n),嵌套则将时间复杂度相乘o(logN)*o(n)
注:希尔排序的嵌套层级太深了,时间复杂度太高,不做讨论
sort方法(工作中肯定是用内置的排序方法啦!)
- sort方法的应用场景:
1. 普通数组排序(如果是数字正常排序,如果是字母则根据首字母排序,那如果是汉字呢???)
2. 数组对象排序(根据数组对象中的某个字段进行排序)
- 测试用例的生成:
测试用例要包括数字,字母,单词(根据单词首字母进行排序),数组对象。(自测应该从多维度,多角度,多种情况进行测试,才能将bug的出现概率降到最低!)
class arraySort {
constructor (len) {
this.arr = this.setLetter(len)
this.len = this.arr.length;
}
// 生成min-max的随机数
randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// 生成随机数字写入数组
setNumber(len) {
let arr = []
for (let i = 0; i <= len - 1; i++) {
arr.push(this.randomNumber(0, len))
}
return arr
}
// 生成随机字母
setLetter(len) {
let letterArr = [];
for (var i = 0; i < len; i++) {
letterArr[i] = String.fromCharCode(this.randomNumber(65, 91));
}
return letterArr;
}
// 自定义英语单词(生成随机的单词太过麻烦,未进行此操作)
setWord(){
return ['my', 'word', 'apple']
}
// 自定义数组对象
setArrObj() {
return [{id: 1, name: 'zhangsan'},{id: 4, name: 'lisi'}, {id: 2, name: 'wanger'}]
}
}
- 测试用例
- 数字
class arraySort {
constructor (len) {
this.arr = this.setNumber(len)
this.len = this.arr.length;
}
// 生成min-max的随机数
randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// 生成随机数字写入数组
setNumber(len) {
let arr = []
for (let i = 0; i <= len - 1; i++) {
arr.push(this.randomNumber(0, len))
}
return arr
}
// 生成随机字母
setLetter(len) {
let letterArr = [];
for (var i = 0; i < len; i++) {
letterArr[i] = String.fromCharCode(this.randomNumber(65, 91));
}
return letterArr;
}
// 自定义英语单词(生成随机的单词太过麻烦,未进行此操作)
setWord(){
return ['my', 'word', 'apple']
}
// 自定义数组对象
setArrObj() {
return [{id: 1, name: 'zhangsan'},{id: 4, name: 'lisi'}, {id: 2, name: 'wanger'}]
}
sort(type = 'asc') {
return this.arr.sort((a,b) => {
if(type == 'asc') return a - b
else if(type == 'desc') return b - a
})
}
}
// 数字:(升序)
let arr = new arraySort(10);
console.log(arr.sort()); // [0, 0, 1, 3, 3, 5, 6, 9, 9, 9]-生成的随机数组,每次数字都不一样
// 数字:(降序)
console.log(arr.sort('desc')); // [9, 9, 9, 6, 5, 3, 3, 1, 0, 0]
- 字母
class arraySort {
constructor (len) {
this.arr = this.setLetter(len)
this.len = this.arr.length;
}
// 生成min-max的随机数
randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// 生成随机数字写入数组
setNumber(len) {
let arr = []
for (let i = 0; i <= len - 1; i++) {
arr.push(this.randomNumber(0, len))
}
return arr
}
// 生成随机字母
setLetter(len) {
let letterArr = [];
for (var i = 0; i < len; i++) {
letterArr[i] = String.fromCharCode(this.randomNumber(65, 91));
}
return letterArr;
}
// 自定义英语单词(生成随机的单词太过麻烦,未进行此操作)
setWord(){
return ['my', 'word', 'apple']
}
// 自定义数组对象
setArrObj() {
return [{id: 1, name: 'zhangsan'},{id: 4, name: 'lisi'}, {id: 2, name: 'wanger'}]
}
// 数字排序
sort(type = 'asc') {
return this.arr.sort((a,b) => {
if(type == 'asc') return a - b
else if(type == 'desc') return b - a
})
}
// 字母排序
sortLetter(type = 'asc') {
return this.arr.sort((a,b) => {
if(type == 'asc') return a.charCodeAt(0) - b.charCodeAt(0)
else if(type == 'desc') return b.charCodeAt(0) - a.charCodeAt(0)
})
}
}
// 字母:(升序)
let arr = new arraySort(10);
console.log(arr.sortLetter()); // ['C', 'D', 'D', 'E', 'G', 'O', 'P', 'R', 'T', 'T']-生成的随机数组,每次字母都不一样
// 字母:(降序)
console.log(arr.sortLetter('desc')); // ['T', 'T', 'R', 'P', 'O', 'G', 'E', 'D', 'D', 'C']
- 单词:
class arraySort {
constructor (len) {
this.arr = this.setWord()
// this.len = this.arr.length;
}
// 生成min-max的随机数
randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// 生成随机数字写入数组
setNumber(len) {
let arr = []
for (let i = 0; i <= len - 1; i++) {
arr.push(this.randomNumber(0, len))
}
return arr
}
// 生成随机字母
setLetter(len) {
let letterArr = [];
for (var i = 0; i < len; i++) {
letterArr[i] = String.fromCharCode(this.randomNumber(65, 91));
}
return letterArr;
}
// 自定义英语单词(生成随机的单词太过麻烦,未进行此操作)
setWord(){
return ['my', 'word', 'apple']
}
// 自定义数组对象
setArrObj() {
return [{id: 1, name: 'zhangsan'},{id: 4, name: 'lisi'}, {id: 2, name: 'wanger'}]
}
// 数字排序
sort(type = 'asc') {
return this.arr.sort((a,b) => {
if(type == 'asc') return a - b
else if(type == 'desc') return b - a
})
}
// 字母和单词(根据首字母)排序
sortLetter(type = 'asc') {
return this.arr.sort((a,b) => {
if(type == 'asc') return a.charCodeAt(0) - b.charCodeAt(0)
else if(type == 'desc') return b.charCodeAt(0) - a.charCodeAt(0)
})
}
}
// 单词:(升序)
let arr = new arraySort();
console.log(arr.sortLetter()); // ['apple', 'my', 'word']
// 单词:(降序)
console.log(arr.sortLetter('desc')); // ['word', 'my', 'apple']
- 数组对象:
class arraySort {
constructor (len) {
this.arr = this.setArrObj()
// this.len = this.arr.length;
}
// 生成min-max的随机数
randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// 生成随机数字写入数组
setNumber(len) {
let arr = []
for (let i = 0; i <= len - 1; i++) {
arr.push(this.randomNumber(0, len))
}
return arr
}
// 生成随机字母
setLetter(len) {
let letterArr = [];
for (var i = 0; i < len; i++) {
letterArr[i] = String.fromCharCode(this.randomNumber(65, 91));
}
return letterArr;
}
// 自定义英语单词(生成随机的单词太过麻烦,未进行此操作)
setWord(){
return ['my', 'word', 'apple']
}
// 自定义数组对象
setArrObj() {
return [{id: 1, name: 'zhangsan'},{id: 4, name: 'lisi'}, {id: 2, name: 'wanger'}]
}
// 数字排序
sort(type = 'asc') {
return this.arr.sort((a,b) => {
if(type == 'asc') return a - b
else if(type == 'desc') return b - a
})
}
// 字母和单词(根据首字母)排序
sortLetter(type = 'asc') {
return this.arr.sort((a,b) => {
if(type == 'asc') return a.charCodeAt(0) - b.charCodeAt(0)
else if(type == 'desc') return b.charCodeAt(0) - a.charCodeAt(0)
})
}
// 数组对象排序
sortArrObj(key,valueType = 'number', type = 'asc') {
return this.arr.sort((a,b) => {
if (valueType == 'number') {
if(type == 'asc') return a[key] - b[key]
else if(type == 'desc') return b[key] - a[key]
} else if (valueType == 'word') {
if(type == 'asc') return a[key].charCodeAt(0) - b[key].charCodeAt(0)
else if(type == 'desc') return b[key].charCodeAt(0) - a[key].charCodeAt(0)
}
})
}
}
let arr = new arraySort();
// 此处专为json便于查看
// // 数组对象:(根据id升序)
console.log(JSON.stringify(arr.sortArrObj('id'))); // [{"id":1,"name":"zhangsan"},{"id":2,"name":"wanger"},{"id":4,"name":"lisi"}]
// // 数组对象:(根据id降序)
console.log(JSON.stringify(arr.sortArrObj('id', 'desc'))); // [{"id":4,"name":"lisi"},{"id":2,"name":"wanger"},{"id":1,"name":"zhangsan"}]
// 数组对象:(根据name升序)
console.log(JSON.stringify(arr.sortArrObj('name', 'word'))); // [{"id":4,"name":"lisi"},{"id":2,"name":"wanger"},{"id":1,"name":"zhangsan"}]
// 数组对象:(根据name降序)
console.log(JSON.stringify(arr.sortArrObj('name', 'word', 'desc'))); // [{"id":1,"name":"zhangsan"},{"id":2,"name":"wanger"},{"id":4,"name":"lisi"}]
- 不传参和传参有什么区别?
// 不传参数,将不会按照数值大小排序,按照字符编码的顺序进行排序
let arr = [30,10,111,35,1899,50,45];
console.log(arr.sort()); // [10, 111, 1899, 30, 35, 45, 50]
console.log(arr.sort((a,b) => a - b)); // [10, 30, 35, 45, 50, 111, 1899]
- sort方法用的什么算法实现的?
sort() 方法用原地算法对数组的元素进行排序,并返回数组。在计算机科学中,就地算法是一种不使用辅助数据结构来转换输入的算法。
- 降序实现?
sort((a,b) => b - a)
- 会改变原始数组吗?
let arr = [5,2,8,7,3];
console.log(arr.sort((a, b) => a - b)) // [2, 3, 5, 7, 8]
console.log(arr) // [2, 3, 5, 7, 8]
参考文献:
- 图片来源:visualgo网站
- 《数据结构与算法javascript描述》
预留疑问:
- 请问诸位,如果用sort方法给汉字排序,它是按照什么来排序的?
后记:
以上就是面试过程中与实际项目中会遇到的一些排序类问题,在此做一个记录与总结,希望能帮助到有需要的小伙伴。我是一个前端小菜鸡,一直都在学习的路上,若是我有什么错误的地方还望各位能批评指正,在此非常感谢!