快速排序和两路合并排序速度_快速排序 堆排序 归并排序

快速排序

不稳定排序,时间复杂度平均为

equation?tex=O%28nlog_2+n%29 ,最差为
equation?tex=O%28n%5E2%29
// quickSort  自顶向下。
int partition(vector<int> &arr, int lo, int hi) {
    int k = arr[lo];   // 基准
    while (lo < hi) {
        while (lo < hi && k < arr[hi]) {  //从右找到第一个比key小的
            hi--;
        }
        arr[lo] = arr[hi];
        while (lo < hi && k >= arr[lo]) {
            lo++;
        }
        arr[hi] = arr[lo];   
    }
    arr[lo] = k;  // 基准归位
    return lo;
}
​
// [lo, hi]
void quickSort(vector<int> &arr, int lo, int hi) { 
    if (lo < hi) {
        int partitionIdx = partition(arr, lo, hi);
        quickSort(arr, lo, partitionIdx);
        quickSort(arr, partitionIdx + 1, hi);
    }
}

快排什么情况下不好,优化手段?

数据规模越大快速排序的性能越优。快排在极端情况下会退化成

equation?tex=O%28n%5E2%29 的算法,因此
假如在提前得知处理数据可能会出现极端情况的前提下,可以选择使用较为稳定的归并排序。

最差情况一般出现在:待排序的数据本身已经是正序或反序排好了。

最好的情况是,每次所取的基准就是该数组的中点,因此一共需要进行n次划分。

// pivot选用中位数   [lo, hi]
void quickSort_mid(vector<int> &arr, int lo, int hi) {
    int pivot = lo + (hi - lo) / 2, i = lo, j = hi;
    while (i <= j) {
        while (arr[pivot] > arr[i]) i++;
        while (arr[pivot] < arr[j]) j--;
        if (i <= j) swap(arr[i++], arr[j--]);
    }
​
    if (lo < j) quickSort_mid(arr, lo, j);
    if (i < hi) quickSort_mid(arr, i, hi);
}

如果一个带排序列重复元素过多,我们先随机选取一个pivot,设为T,那么数列可以分为三部分:小于T,等于T,大于T:

e575c41101f4cde64a82dec7eefe1e3f.png

等于T的部分就无需再参与后续的递归调用了。可以设置四个游标,左端a、b,右端c、d。

b、c的作用跟之前两路划分时候的左右游标相同,就是从两端向中间遍历序列,并将遍历到的元素与pivot比较,如果等于pivot,则移到两端(b对应的元素移到左端,c对应的元素移到右端。移动的方式就是拿此元素和a或d对应的元素进行交换,所以a和d的作用就是记录等于pivot的元素移动过后的边界)。如果大于或小于pivot,还按照之前两路划分的方式进行移动。这样一来,中间部分就和两路划分相同,两头是等于pivot的部分,我们只需要将这两部分移动到中间即可。

堆排序

满二叉树,字面意思节点是满的,从形象上来看是一个完整的三角形,一棵深度为k的满二叉树有且共有

equation?tex=2%5Ek-1 个节点
equation?tex=%282%5E0%2B2%5E1%2B2%5E2%2B...2%5E%7Bk-1%7D+%29

完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。第 k 层所有的结点都连续集中在最左边。

大根堆是一棵完全二叉树,且对于任意一个子节点,其值均不大于父节点的值,如此递推,就是根节点的值是最大的,即大根堆。小根堆也是一棵完全二叉树,且对于任意一个子节点,其数值均不小于父节点的值,这样层层递推,就是根节点的值最小,即小根堆。

完全二叉树通过一维数组(数组起始索引为0)来存储时有如下特性,当然堆就也有了:

  • 父节点i的左子节点在位置
    equation?tex=%282%2Ai%2B1%29 ;
  • 父节点i的右子节点在位置
    equation?tex=%282%2Ai%2B2%29 ;
  • 子节点i的父节点在位置
    equation?tex=floor%28%28i-1%29%2F2%29 ;

建堆,把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出。这个过程持续到剩余数只有一个时结束。

e4cba36c3a66be7c9a535d6eeb09fca4.png
步骤1和步骤2
1. 将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆
2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn)
3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
// 把数组arr中从start到end的部分调整成大顶堆。 从左至右,从下至上进行调整。
void max_heapify(vector<int>& arr, int start, int end) {
    int dad = start, max_son = 2 * start + 1; // 从左到右找值最大的子节点
    while (max_son <= end) {
        if (max_son + 1 <= end && arr[max_son] < arr[max_son + 1]) 
            max_son ++; // max_son 向右 找最大
        if (arr[max_son] > arr[dad]) {
            swap(arr[max_son], arr[dad]);
            dad = max_son;    // 继续迭代 dad指针下移,看是否还是比dad值大的子节点
            max_son = 2 * dad + 1;
        } else return ;
    }
}

// 递归写法​
void max_heapify(vector<int>& arr, int start, int end) {
    int largest = start, l = 2 * start + 1, r = 2 * start + 2; // 左孩子 右孩子
    if (l <= end && arr[l] > arr[largest]) largest = l;
    if (r <= end && arr[r] > arr[largest]) largest = r;
    if (largest != start) {
        swap(arr[start], arr[largest]);
        max_heapify(arr, largest, end);  // 递归定义子堆
    }
}

// 堆排序
void heap_sort(vector<int>& arr) {
    int len = arr.size();
    // 建堆
    // 从最后一个非叶子结点开始,有多少个非叶子节点就要调整几次(叶子节点不调整)
    // 最后一个叶子节点的索引值是n-1,它的父节点索引值是[(n-1)-1]/2 = n/2 -1
    for (int i = len / 2 - 1; i >= 0; i--)  
        max_heapify(arr, i, len - 1);
    
    for (int i = len - 1; i> 0; i--) {   // 交换len - 1次
        swap(arr[0], arr[i]); // 堆顶和末尾元素的交换,后续要处理的元素就是前面的i-1个 
        max_heapify(arr, 0, i - 1);  // 只有堆顶改变了,依据堆顶调整堆元素
    }
}

归并排序

//  mergeSort   自底向上。
void merge(vector<int> &arr, int lo, int mid, int hi) {
    vector<int> tmp(hi - lo + 1);
    int i = lo, j = mid + 1, k = 0;
    while (i <= mid && j <= hi) {
        tmp[k++] = (arr[i] < arr[j]) ? arr[i++] : arr[j++];
    }
    // 如果左边的数组还没比较完,右边的数都已经完了,那么将左边的数抄到大数组中
    while (i <= mid) tmp[k++] = arr[i++];
    while (j <= hi) tmp[k++] = arr[j++];
    // 把结果拷回arr数组
    for (int i = 0; i < tmp.size(); i++) {
        arr[i + lo] = tmp[i];
    }
}
​
void mergeSort(vector<int> &arr, int lo, int hi) {
    if (lo == hi) return;
    int mid = lo + (hi - lo) / 2;
    mergeSort(arr, lo, mid);
    mergeSort(arr, mid + 1, hi);
    merge(arr, lo, mid, hi);
}

剑指 Offer 51. 数组中的逆序对

 class Solution {
public:
    int reversePairs(vector<int>& nums) {
        if (nums.size() < 2) return 0;
        return mergeSort(nums, 0, nums.size() - 1);
    }
​
    // [start, end]
    int mergeSort(vector<int>& nums, int start, int end) {
        if (start == end) return 0;
        int mid = start + (end - start) / 2;
  // [ left] [ right ] 
  // left right内部先计算逆序对个数,然后left和right放在一起计算逆序对个数。
        int left_num = mergeSort(nums, start, mid);    
        int right_num = mergeSort(nums, mid + 1, end);
        int cross_num = merge(nums, start, mid, end);
        return left_num + right_num + cross_num;
    }
​
    int merge(vector<int>& nums, int start, int mid, int end) {
        vector<int> tmp(end - start + 1, -1);
        int i = start, j = mid + 1, tmp_idx = 0, res = 0;
        while (i <= mid && j <= end) {
            if (nums[i] > nums[j]) 
                res += (mid + 1 - i);  
            //  [10, 11, 14, 15]  [6, 7]  6的时候,left中4个都构成逆序对
            tmp[tmp_idx++] = nums[i] <= nums[j] ? nums[i++] : nums[j++]; //保证排序稳定
        }
​
        while (i <= mid) tmp[tmp_idx++] = nums[i++];  // right没有了,直接拷贝left部分
        while (j <= end) tmp[tmp_idx++] = nums[j++];
​
        for (int i = 0; i < tmp.size(); i++) {
            nums[i + start] = tmp[i];
        }
​
        return res;
    }
};

912. 排序数组

// [start, end] 
// 找最大的孩子节点值,如果大于父节点的值就交换,
// 之后父节点下标就调整为刚刚的孩子节点下标,继续找是否可以交换
void max_heapify(vector<int>& arr, int start, int end) {
    int dad = start, max_son = 2 * dad + 1;
    while (max_son <= end) {
        if (max_son + 1 <= end && arr[max_son] < arr[max_son + 1]) max_son++;
        if (arr[max_son] > arr[dad]) {
            swap(arr[max_son], arr[dad]);
            dad = max_son;
            max_son = dad * 2 + 1;
        } else return;
    }
}

void heap_sort(vector<int>& arr) {
    int len = arr.size();

    // 建堆 最后一个非叶节点
    for (int i = len / 2 - 1; i >= 0; i--) {
        max_heapify(arr, i, len - 1);
    }

    for (int i = len - 1; i > 0; i--) {
        swap(arr[0], arr[i]);
        max_heapify(arr, 0, i - 1);
    }

}

vector<int> sortArray(vector<int>& nums) {
    heap_sort(nums);
    return nums;
}

参考:

【面试】堆排序举例图解最后给你个常见面试题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值