排序算法 归并 & 快排

87 阅读1分钟

对于递归的理解

我的理解是递归有点像DOM事件机制和洋葱模型,但是多少有一点区别
DOM事件的触发过程是捕获 -> 当前节点 -> 冒泡
洋葱模型: 请求 -> next -> 相应 image.png 递归操作: 递 -> 节点 -> 归
React组件挂载: componentWillMount -> render -> componentDidMount; 父组件的WillMount早于子组件的WillMount;父组件的DidMount晚于子组件的DidMount;

总结

递归的操作跟上面的这几种都特别相像,不同的是别的可能是不同的函数主体,但是递归是一直调用的自身。

归并

归并原理

将数组中中间分开,一直分,一直分,当分到nums.length <= 1时,开始出栈做合并操作。 所以它的代码步骤是两部分

  1. 递阶段从中间拆开当前数组
  2. 归阶段合并左右两个数组 由于合并两个数组的操作,而不是原地位置交换操作,所以需要做一个新的空间来做存储,因此空间复杂度是O(n)

例子理解

// 第一次入栈
var nums = [5,1,1,2,0,0];
a = [5, 1, 1];
b = [2, 0, 0];
// a 分支入栈
// a第一次入栈
a = [5];
b = [1, 1];
// a第二次入栈
a = [1];
b = [1];
// a第一次出栈
merge([1, 1]);
// a第二次出栈
merge([5], [1, 1]);
// b分支第一次入栈
a = [2];
b = [0, 0];
// b分支第二次入栈
a = [0];
b = [0];
// b分支第一次出栈
merge([0], [0])
// b分支第二次出栈
merge([2], [0, 0])
// 最后一次出栈
merge([1, 1, 5], [0, 0, 2]);

代码

var sortArray = function(nums) {
    // 1. 递操作
    if (nums.length <= 1) return nums;
    let i = ~~(nums.length / 2);
    let a = nums.slice(0, i);
    let b = nums.slice(i);
    // 2. 当前节点
    a = sortArray(a);
    b = sortArray(b);
    // 3. 归操作
    return merge(a, b);
};

function merge(a, b) {
    let result = [];
    let m = a.length;
    let n = b.length;
    let i = 0;
    let j = 0;
    while(i < m && j < n) {
        if (a[i] < b[j]) {
            result.push(a[i]);
            i++;
        } else {
            result.push(b[j]);
            j++;
        }
    }

    while(i < m) {
        result.push(a[i]);
        i++;
    }

    while(j < n) {
        result.push(b[j]);
        j++;
    }

    return result;
}

快排

快排的原理

找到一个povit,确定他在数组中的位置,当他的位置确定后,在以它为轴,去左右中在分别确定一个轴,去确定这个轴在数组的位置 因此他的代码操作是

  1. 在递的阶段找到一个点,确定他的位置
  2. 归不做操作

因为是原地交换操作,排除递归造成的空间损耗,可以认为它的空间复杂度是O(1);

代码实现

var sortArray = function(nums) {
    quickSort(nums, 0, nums.length - 1);
    return nums;
};

function quickSort(nums, l, r) {
    if (l < r) {
        let pos = getPos(nums, l, r);
        quickSort(nums, l, pos - 1);
        quickSort(nums, pos + 1, r)
    }
}
// 如果设置左边为povit,则指针改变应该从右边开始
// 因为从右边的话,最后i == j的那个nums[i] 一定是 nums[i] < povit 的。因为如果nums[j] >= povit的话,一定会向左移动。直到第一个小于
// 对于数据本身比较有序的情况下可以做随机化处理
// 随机化处理,就是先随机获取一个下标index,然后在开始循环前,将他和l交换位置
function getPos(nums, l, r) {
    // 随机化处理
    randomPovit(nums, l, r);
    let povit = nums[l];
    let i = l;
    let j = r;
    while(i < j) {
        // 从数组的最后找到第一个小于自己的点
        while(i < j && nums[j] >= povit) j--;
        // 从数组前面找到第一个大于自己的点
        while(i < j && nums[i] <= povit) i++;
        // 交换他们两个的位置
        if (i !== j) {
            swap(nums, i, j);
        }
    }
    // 此时i === j,数组中的情况
    // l是povit, l + 1到i是小于povit的,j + 1 到r是大于povit的
    // 将l和i交换位置后,则l到i-1是小于povit的,i是当前的povit
    swap(nums, l, i);
    return i;
}

function randomPovit(nums, l, r) {
    let i = ~~(Math.random() * (r - l + 1) + l);
    swap(nums, l, i);
}

function swap(nums, i, j) {
    let temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}