冒泡
思路
- 比较所有的相邻元素,如果第一个比第二个大,则交换它们
- 一轮下来,可以保证最后一个数是最大的
- 执行n-1轮,就可以完成排序(例如第二轮,可以保证倒数第二个数是数据中第二大的)
代码实现
数组原型上的方法和this
<script>
Array.prototype.bubbleSort = function () {
console.log(this); //[5, 4, 3, 2, 1];
}
const arr = [5, 4, 3, 2, 1];
arr.bubbleSort()
</script>
获取数组中的所有相邻元素
<script>
Array.prototype.bubbleSort = function () {
for(let j =0;j<this.length-1;j++){
console.log(this[j],this[j+1]);
//5 4
//4 3
//3 2
//2 1
}
}
const arr = [5, 4, 3, 2, 1];
arr.bubbleSort()
</script>
比较前后数大小,交换位置
- 如果前一个数比后一个数要大,则交换他们的位置
- 第一轮循环下来,数组中的最后一个数就是数组中第一大的数,(后面类推)
<script>
Array.prototype.bubbleSort = function () {
for(let j =0;j<this.length-1;j++){
if(this[j] > this[j+1]){
let temp = this[j]
this[j] = this[j+1]
this[j+1] = temp
}
}
}
const arr = [5, 4, 3, 2, 1];
arr.bubbleSort()
console.log('arr',arr); //[4, 3, 2, 1, 5]
</script>
所有元素排完序
执行n-1轮循环,就可以对数组中所有的元素排完序
<script>
Array.prototype.bubbleSort = function () {
for (let i = 0; i < this.length - 1; i++) {
for (let j = 0; j < this.length - 1; j++) {
if (this[j] > this[j + 1]) {
let temp = this[j]
this[j] = this[j + 1]
this[j + 1] = temp
}
}
}
}
const arr = [5, 4, 3, 2, 1];
arr.bubbleSort()
console.log('arr', arr); //[1, 2, 3, 4, 5]
</script>
优化(完整代码)
例如:第二轮循环下来,倒数的第二个数就是第二大的数,所以就不用去循环数组中第二大后面的数
<script>
Array.prototype.bubbleSort = function () {
for (let i = 0; i < this.length - 1; i++) {
for (let j = 0; j < this.length - 1-i; j++) {
if (this[j] > this[j + 1]) {
let temp = this[j]
this[j] = this[j + 1]
this[j + 1] = temp
}
}
}
}
const arr = [5, 4, 3, 2, 1];
arr.bubbleSort()
console.log('arr', arr); //[1, 2, 3, 4, 5]
</script>
时空复杂度
时间复杂度
O(n^2),因为2个嵌套循环
空间复杂度
空间复杂度就是在交换元素时那个临时变量所占的内存空间;
最优的空间复杂度就是开始元素顺序已经排好了,则空间复杂度为:0;
最差的空间复杂度就是开始元素逆序排序了,则空间复杂度为:O(n);
平均的空间复杂度为:O(1);
选择
思路
- 找到数组中的最小值,选中它并将其放置在第一位
- 找到数组中的第二小的值,选中它并将其放置在第二位
- 以此类推,执行n-1轮
代码实现
一轮循环下来找到数组中最小值的下标
<script>
Array.prototype.selectSort = function(){
let indexMin = 0; //默认数组最小值的下标为0
for(let j =0;j<this.length;j++){
let thisJ = this[j]
let thisIndexMin = this[indexMin]
if(thisJ < thisIndexMin){
indexMin = j
}
}
console.log('indexMin',indexMin); //4
}
const arr = [5, 4, 3, 2, 1];
arr.selectSort()
</script>
一轮循环下来,把找到的数组中最小值,与数组第一个值进行交换
<script>
Array.prototype.selectSort = function(){
let indexMin = 0; //默认数组最小值的下标为0
for(let j =0;j<this.length;j++){
let thisJ = this[j]
let thisIndexMin = this[indexMin]
if(thisJ < thisIndexMin){
indexMin = j
}
}
const temp = this[0]
this[0] = this[indexMin]
this[indexMin] = temp;
// console.log('indexMin',indexMin);
}
const arr = [5, 4, 3, 2, 1];
arr.selectSort()
console.log('arr', arr) //[1, 4, 3, 2, 5]
</script>
执行n-1轮循环
把0都换成i,因为i代表当前第几轮,也就是从第几个元素开始循环
<script>
Array.prototype.selectSort = function () {
for (let i = 0; i < this.length - 1; i++) {
let indexMin = i; //默认数组最小值的下标为0
for (let j = i; j < this.length; j++) {
let thisJ = this[j]
let thisIndexMin = this[indexMin]
if (thisJ < thisIndexMin) {
indexMin = j
}
}
const temp = this[i]
this[i] = this[indexMin]
this[indexMin] = temp;
// console.log('indexMin',indexMin);
}
}
const arr = [5, 4, 3, 2, 1];
arr.selectSort()
console.log('arr', arr) //[1, 2, 3, 4, 5]
</script>
完整代码
一轮循环下来,找到了数组中最小值的下标,如果最小值的下标与当前值不相等,才进行交换,(以此类推)
<script>
Array.prototype.selectSort = function () {
for (let i = 0; i < this.length - 1; i++) {
let indexMin = i; //默认数组最小值的下标为0
for (let j = i; j < this.length; j++) {
let thisJ = this[j]
let thisIndexMin = this[indexMin]
if (thisJ < thisIndexMin) {
indexMin = j
}
}
// 一轮循环下来,找到了数组中最小值的下标,
// 如果最小值的下标与当前值不相等,才进行交换
if (indexMin !== i) {
const temp = this[i]
this[i] = this[indexMin]
this[indexMin] = temp;
}
}
}
const arr = [5, 4, 3, 2, 1];
arr.selectSort()
console.log('arr', arr) //[1, 2, 3, 4, 5]
</script>
时空复杂度
时间复杂度
O(n^2),因为2个嵌套循环
空间复杂度
插入排序
思路
- 从第二个数开始往前比
- 比它大就往后排
代码实现
Array.prototype.quickSort = function () {
const temp = this[1]
let j =1;
while(j >0){
j--
}
}
const arr = [5,4,3,2,1]
arr.quickSort()
第一轮循环
Array.prototype.quickSort = function () {
const temp = this[1]
let j =1;
while(j >0){
if(this[j-1] > temp){
this[j] = this[j-1]
}else{
break;
}
j--
}
this[j] = temp;
}
const arr = [5,4,3,2,1] //[4,5,3,2,1]
arr.quickSort()
循环数组的长度n轮
Array.prototype.quickSort = function () {
for (let i = 1; i < this.length; i++) {
const temp = this[1]
let j = 1;
while (j > 0) {
if (this[j - 1] > temp) {
this[j] = this[j - 1]
} else {
break;
}
j--
}
this[j] = temp;
}
}
const arr = [5, 4, 3, 2, 1]
arr.quickSort()
时间复杂度
嵌套2层循环 O(n^2)
快速排序
思路
- 分区: 从数组中任意选择一个“基准”,所有比基准小的元素放在基准前面,比基准大的元素放在基准的后面
- 递归: 递归地对基准前后的子数组进行分区
代码实现
基准
选择数组第一个元素做基准
Array.prototype.quickSort = function(){
const rec = (arr) => {
const left = []
const right = []
const mid = arr[0]
}
const res = rec(this)
console.log('res',res);
res.forEach((n, i) => { this[i] = n });
}
const arr = [2,4,5,3,1]
arr.quickSort()
分区
从第2个元素开始,小于基准的放左边,大于基准放右边
Array.prototype.quickSort = function(){
const rec = (arr) => {
const left = []
const right = []
const mid = arr[0]
for(let i =1;i<arr.length;i++){
if(arr[i] < mid){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
}
const res = rec(this)
console.log('res',res);
res.forEach((n, i) => { this[i] = n });
}
递归
例如在左分区中,又定义基准,再分区
Array.prototype.quickSort = function(){
const rec = (arr) => {
const left = []
const right = []
const mid = arr[0]
for(let i =1;i<arr.length;i++){
if(arr[i] < mid){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return [...rec(left),mid,...rec(right)]
}
const res = rec(this)
console.log('res',res);
res.forEach((n, i) => { this[i] = n });
}
终结条件,边界
数组只有一个时,就不用排序了
Array.prototype.quickSort = function(){
const rec = (arr) => {
if(arr.length <=1){
return arr;
}
const left = []
const right = []
const mid = arr[0]
for(let i =1;i<arr.length;i++){
if(arr[i] < mid){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return [...rec(left),mid,...rec(right)]
}
const res = rec(this)
console.log('res',res);
res.forEach((n, i) => { this[i] = n });
}
时间复杂度
递归:O(logN)
分区: O(n)
总体: O(n*logN)
另一种写法
const quickSort1 = arr => {
if (arr.length <= 1) {
return arr;
}
//取基准点
const midIndex = Math.floor(arr.length / 2);
//取基准点的值,splice(index,1) 则返回的是含有被删除的元素的数组。
const valArr = arr.splice(midIndex, 1);
const midIndexVal = valArr[0];
const left = []; //存放比基准点小的数组
const right = []; //存放比基准点大的数组
//遍历数组,进行判断分配
for (let i = 0; i < arr.length; i++) {
if (arr[i] < midIndexVal) {
left.push(arr[i]); //比基准点小的放在左边数组
} else {
right.push(arr[i]); //比基准点大的放在右边数组
}
}
//递归执行以上操作,对左右两个数组进行操作,直到数组长度为 <= 1
return quickSort1(left).concat(midIndexVal, quickSort1(right));
};
const array2 = [5, 4, 3, 2, 1];
console.log('quickSort1 ', quickSort1(array2));
// quickSort1: [1, 2, 3, 4, 5]
归并
思路
- 分:把数组劈成两半,再递归地对子数组进行“分”操作,直到分成一个个单独的数
- 合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并成为一个完整的数组
合并两个有序数组
- 新建一个空数组,用于存放最终排序后的数组
- 比较两个有序数组的头部,较小者出队并推入res中
- 如果两个数组还有值,就重复第二步
代码实现
数组分成两半
Array.prototype.mergeSort = function (n) {
const rec = (arr) => {
const mid = Math.floor(arr.length /2)
const left = arr.slice(0,mid)
const right = arr.slice(mid.arr.length)
}
rec(this)
}
const arr = [5, 4, 3, 2, 1];
var result = arr.mergeSort(0)
console.log('result', result)
递归
Array.prototype.mergeSort = function (n) {
const rec = (arr) => {
if(arr.length === 1){
return arr
}
const mid = Math.floor(arr.length /2)
const left = arr.slice(0,mid)
const right = arr.slice(mid.arr.length)
const orderLeft = rec(left)
const orderRight = rec(right)
}
rec(this)
}
const arr = [5, 4, 3, 2, 1];
arr.mergeSort(0)
console.log('arr', arr)
合并
Array.prototype.mergeSort = function (n) {
const rec = (arr) => {
if(arr.length === 1){
return arr
}
const mid = Math.floor(arr.length /2)
const left = arr.slice(0,mid)
const right = arr.slice(mid,arr.length)
const orderLeft = rec(left)
const orderRight = rec(right)
const res = []
while(orderLeft.length || orderRight.length){
if(orderLeft.length && orderRight.length){
res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift())
}else if(orderLeft.length){
res.push(orderLeft.shift())
}else if(orderRight.length){
res.push(orderRight.shift())
}
}
return res;
}
const res = rec(this)
}
const arr = [5, 4, 3, 2, 1];
arr.mergeSort(arr)
console.log('arr', arr)
把rec方法拷贝到数组this上
Array.prototype.mergeSort = function (n) {
const rec = (arr) => {
if (arr.length === 1) {
return arr
}
const mid = Math.floor(arr.length / 2)
const left = arr.slice(0, mid)
const right = arr.slice(mid, arr.length)
const orderLeft = rec(left)
const orderRight = rec(right)
const res = []
while (orderLeft.length || orderRight.length) {
if (orderLeft.length && orderRight.length) {
res.push(orderLeft[0] < orderRight[0] ? orderLeft.shift() : orderRight.shift())
} else if (orderLeft.length) {
res.push(orderLeft.shift())
} else if (orderRight.length) {
res.push(orderRight.shift())
}
}
return res;
}
const res = rec(this)
//只是把一个数组排序好放到rec里面,要把它拷贝到this上
res.forEach((n, i) => { this[i] = n })
}
const arr = [5, 4, 3, 2, 1];
arr.mergeSort(arr)
console.log('arr', arr)
时间复杂度
分: O(logN)
合: O(n)
总: O(n*logN)
另一种写法
// const mergeSort2 = arr => {
// //采用自上而下的递归方法
// const len = arr.length;
// if (len < 2) {
// return arr;
// }
// // length >> 1 和 Math.floor(len / 2) 等价
// let middle = Math.floor(len / 2),
// left = arr.slice(0, middle),
// right = arr.slice(middle); // 拆分为两个子数组
// return merge2(mergeSort2(left), mergeSort2(right));
// }
function merge2(left, right) {
const result = []
// 写法一
while (left.length || right.length) {
if (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift())
} else {
result.push(right.shift())
}
} else if (left.length) {
result.push(left.shift())
} else if (right.length) {
result.push(right.shift())
}
}
// 写法二 while与if相比,if只做一次判断,while是递归循环判断
// while (left.length && right.length) {
// // 注意: 判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
// if (left[0] <= right[0]) {
// result.push(left.shift());
// } else {
// result.push(right.shift());
// }
// }
// while (left.length) result.push(left.shift());
// while (right.length) result.push(right.shift());
return result
}
const arr2 = [5, 4, 3, 2, 1];
var result2 = mergeSort2(arr2)
console.log('result2', result2)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。