js数据结构与算法 第七天(排序)

在这里插入图片描述
相关概念
稳定:如果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]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值