常见排序算法(二)

归并排序:算法

  一、是创建在归并操做上的一种排序算法,该算法为分治法的一个应用数组

  二、步骤:数据结构

    假设待排序序列为 R[0...n-1],并采用二路归并ide

    a、将 R 划分成长度相同或相差为 1 的两个子序列 R1,R2函数

    b、递归地将子序列 R1 和 R2 划分,直至子序列长度为 1spa

    c、两两合并子序列,同时进行排序,直至序列长度为 n,如合并 [1, 4] 和 [2, 3] ==> [1, 2, 3, 4]指针

注:n 路归并即将序列划分红 n 个子序列,通常 n = 2code

  分治法是把问题实例划分红若干个子实例,并分别递归地解决每一个子实例,而后把这些子实例的解组合起来,获得原问题实例的解blog

void merge(std::vector<int>& nums, int left, int mid, int right)
{
    
    int len = right - left + 1;
    int* temp = new int[len];       // 辅助空间O(n)
    int index = 0;
    int i = left;                   // 前一数组的起始下标 
    int j = mid + 1;                // 后一数组的起始下标 
    
    while (i <= mid && j <= right) {
//        选取较小的那一个 
        temp[index++] = nums[i] <= nums[j] ? nums[i++] : nums[j++];
    }
//    将未放回辅助空间的子序列所有放回去(因为分治,子序列里面已是排序好了的) 
    while (i <= mid) {
        temp[index++] = nums[i++];
    }
    while (j <= right) {
        temp[index++] = nums[j++];
    }
//    深拷贝 
    for (int k = 0; k < len; k++) {
        nums[left++] = temp[k];
    }
//    使用 new 关键字建立的空间在堆,须要主动删除,而平时的变量声明通常在栈里,由计算机自动删除并释放空间 
    delete temp;
}

void mergeSort(std::vector<int>& nums, int left, int right)
{
    if (left >= right)
        return ;
    int mid = (left + right) / 2;
    mergeSort(nums, left, mid);
    mergeSort(nums, mid+1, right);
    merge(nums, left, mid, right);
}
View Code

 注:深拷贝可认为拷贝后指针所指地址不一样,但内容相同;而浅拷贝不止值相同,连指针所指地址也相同,会受到原来内容改变的影响排序

 

堆排序:

  一、堆的性质:

    a、在数据结构里,堆是一棵近似的彻底二叉树,除最底层,其余都是全满的(注意和满二叉树的区别)

    b、树的每一个节点对应数组中的一个元素,其根节点对应下标为 0 的元素

    c、下标为 i 的元素,其父节点下标为 (i+1) / 2 - 1,左孩子下标为 2*i + 1,右孩子下标为 2*i + 2

    d、最大堆中,父节点的值必须大于它的子节点的值;而最小堆则必须小于

  二、步骤:

    a、创建最大堆,方法详细看代码

    b、将最大堆的第一个元素(最大值)取出,而后拿最后的元素放到下标为 0 的位置,从新调整最大堆(以下图)

    c、重复操做 b 直至到第一个元素,此时该堆已变成最小堆

//向下调整 
void siftDown(std::vector<int>& nums, int star, int last_index)
{
    int i = star;
    int j = i * 2 + 1;
//    记录要调整的节点 
    int temp = nums[i];
    while (j <= last_index) {
//        记录较大的那个子节点 
        if (j < last_index && nums[j] < nums[j+1])
            j++;
//        若父节点大于等于最大的那个子节点,则退出循环,不然交换这两个节点 
        if (temp >= nums[j])
            break;
        else {
            nums[i] = nums[j];
//            向下寻找子节点 
            i = j;
            j = 2 * j +1;
        }
    }
//    循环结束后 i 的位置即为要调整节点的新位置 
    nums[i] = temp;
}

void heapSort(std::vector<int>& nums)
{
    int len = nums.size();
//    构建最大堆,注意是减 2,由于最后一个节点必定是子节点 
    for (int i = (len - 2) / 2; i >= 0; --i) {
        siftDown(nums, i, len-1);
    }
    //经过交换使最大堆变成最小堆 
    for (int i = len-1; i >= 0; --i){
        //交换首元素和下标为i的元素 
        int t = nums[0];
        nums[0] = nums[i];
        nums[i] = t;
        //把最大元素排除在外重构最大堆 
        siftDown(nums, 0, i-1);
    }
}
View Code

 

 注:若使用最小堆时,咱们只能保证父节点大于子节点,而没法保证兄弟节点的大小关系,所以咱们先构建最大堆,再进行堆排序

  上面两种排序算法的 main 函数和上一篇冒泡排序的 main 函数同样

相关文章
相关标签/搜索