归并排序的优化--自然归并排序

本文介绍了一种改进的合并排序算法——自然合并排序。该算法利用数组中已存在的有序子数组进行合并,提高排序效率。首先通过扫描数组确定有序子数组段,然后逐步合并这些子数组直至整个数组有序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不知道怎么将这个算法思想表达的更好,也不知道自己的理解的是否对

黑体的注释是普通的自然归并,从相邻长度为1的子数组段进行合并也就是一开始将每两个相邻元素进行归并,然后再相邻四个元素左右两组都有序的合并成4个有序的........自下向上不断往上归并直到有序  

 

/*
 * 自然合并排序是合并排序算法的一种改进. 自然合并排序:
 * 对于初始给定的数组,通常存在多个长度大于1的已自然排好序的子数组段.
 * 例如,若数组a中元素为{4,8,3,7,1,5,6,2},则自然排好序的子数组段有{4,8},{3,7},{1,5,6},{2}.
 * 构成更大的排好序的子数组段({3,4,7,8},{1,2,5,6}).继续合并相邻排好序的子数组段,直至整个数组已排好序.
 *
 *
 * 用一个数组t[n]来存储数组排好序的子数组段   下标表示第i段  值表示在原数组值下降的位置(在升序的情况下)
 * 例如 arr{4,8,3,7,1,5,6,2}  t[0] = 0  t[1] = 2 t[2]=4 t[3]=7 那么一共有4段子数组
 *
 * 用t[n]代替之前的数组长度,从步长为1开始合并 表示相邻为步长的t[n]子数组段   t[n]来表示要合并的子数组下标
 */


public class 自然合并排序 {
    static int part; //表示有多少段排好序的子数组
    static int[] t;
    public static void main(String[] args) {
        int[] arr = {4,8,3,7,1,5,6,2};
        t = new int[arr.length];
        t[0] = 0;//第一个赋值为0 第一个数字肯定是有序的
        getPart(arr);
        mergeSore(arr,arr.length);
        
        for(int i=0;i<arr.length;i++) {
            System.out.print(arr[i]+" ");
        }
    }

    private static void getPart(int[] arr) {
        int k = 1;
        for(int i=0;i<arr.length-1;i++) {
            if(arr[i]>arr[i+1])
                t[k++] = i+1;  //记录第k+1段有序子数组开始的下标  
            //那么t[k]-1就为第k段的有序子数组的末尾下标
        }
        //相当于 一根绳子 切了三刀  分成了四条  的意思   第一个t[0]不算 只是表示起点
        part = k; // 因为前面是k++ 所以k表示数组分成了几段有序子数组段
        
        
    }

    private static void mergeSore(int[] arr,int n) {
        
        int s = 1;//表示步长
        
        //while(s<n) 
        while(s<part) {
            
            mergePass(arr,s,n);
            s+=s;
            mergePass(arr,s,n);
            s+=s;
        }
        
    }

    private static void mergePass(int[] arr, int s, int n) {
        int i=0;//控制t数组的下标   从0开始
        
        //t数组已经赋值的长度以内进行循环合并数组  
        
        //while(i<=n-s*2)
        while(i<=part-s*2) {
             //合并t数组下标相差s的两个子数组段
            int r = ((i+2*s)<part)?t[i+2*s]:n;//判断第二段子数组段的边界取什么值 因为i+2*s 可能超过part了    重点注意
            
            
            //注意 t[i+s]-1  才表示左子数组段的末尾下标
            //注意r-1才表示右子数组段的末尾
            merge(arr,t[i],t[i+s]-1,r-1);
            //merge(arr,i,i+s-1,i+2*s-1);
            
            i=i+2*s;
        }
    
        
        //if(i+s<n)
        if(i+s<part)
            merge(arr,t[i],t[i+s]-1,n-1);

        //merge(arr,i,i+s-1,n-1);


        
        //这里我只用原数组 所以不需要变动
        /*else
            if(i<part)
            {
                for(int j=t[i];j<=n-1;j++)
                    arr[j]
            }*/
        
    }

    private static void merge(int[] arr, int i, int j, int r) {
        int[] b = new int[arr.length];
        int left = i;
        int right = j+1;
        int k = 0;
        while(left<=j&&right<=r) {
            if(arr[left]<=arr[right])  //谁小就放进辅助数组 且指针++
                b[k++] = arr[left++];
            else
                b[k++] = arr[right++];
        }
        
        if(left>j) {
            for(int l = right;l<=r;l++) {
                b[k++] = arr[l];
            }
        }
        else {
            for(int l = left;l<=j;l++) {
                b[k++] = arr[l];
            }
        }
        
        //赋值回原数组
        for(int l=0;l<k;l++) {
            arr[i++] = b[l];
        }
        
    }
}

### 归并排序升序的分治法数学建模过程 归并排序是一种经典的基于分治策略的排序算法,其数学建模过程可以分为以下几个核心部分进行描述: #### 1. 分治策略的分解阶归并排序中,分解阶的目标是将原始序列不断分割为更小的部分,直到每个部分仅包含一个元素。这一过程可以通过递归实现,每次将当前序列的中间位置作为分割点[^1]。 假设待排序的序列为 $ a[n] = \{a_1, a_2, a_3, ..., a_n\} $,初始时将其视为一个整体。通过递归的方式,将该序列不断划分为左右两个子序列,直到每个子序列只包含一个元素。此时,单个元素的子序列被认为是有序的,因为它们无法进一步比较[^2]。 #### 2. 合并的数学模型 合并归并排序的核心部分,它涉及将两个已排序的子序列合并一个更大的有序序列。假设两个子序列分别为 $ A[m] = \{a_1, a_2, ..., a_m\} $ 和 $ B[k] = \{b_1, b_2, ..., b_k\} $,且两者均已按升序排列。目标是将这两个子序列合并一个新的有序序列 $ C[m+k] $。 具体步骤如下: - 初始化两个指针 $ i $ 和 $ j $,分别指向 $ A[m] $ 和 $ B[k] $ 的起始位置。 - 比较 $ A[i] $ 和 $ B[j] $ 的值,将较小的元素放入结果序列 $ C[] $ 中,并将对应的指针向前移动一位。 - 如果其中一个子序列的所有元素已经处理完毕,则将另一个子序列的剩余部分直接追加到结果序列中[^3]。 #### 3. 时间复杂度分析 归并排序的时间复杂度可以通过递推关系式进行建模。假设排序长度为 $ n $ 的序列所需的时间为 $ T(n) $,则有以下递推关系: $$ T(n) = 2T\left(\frac{n}{2}\right) + O(n) $$ 其中,$ 2T\left(\frac{n}{2}\right) $ 表示对左右两个子序列分别进行排序所需的时间,而 $ O(n) $ 表示合并两个子序列所需的时间。根据主定理,可以得出归并排序的时间复杂度为 $ O(n \log n) $[^4]。 #### 4. 空间复杂度分析 归并排序的空间复杂度主要由辅助数组决定。在合并,需要额外的空间来存储临时的有序序列。因此,归并排序的空间复杂度为 $ O(n) $,其中 $ n $ 是输入序列的长度[^5]。 #### 示例代码实现 以下是归并排序的完整代码实现,展示了分解和合并的具体操作: ```python def merge_sort(arr): if len(arr) <= 1: return arr # 分解阶 mid = len(arr) // 2 left = merge_sort(arr[:mid]) right = merge_sort(arr[mid:]) # 合并 return merge(left, right) def merge(left, right): result = [] i = j = 0 # 双指针合并两个有序子序列 while i < len(left) and j < len(right): if left[i] <= right[j]: # 按照升序规则选择较小的元素 result.append(left[i]) i += 1 else: result.append(right[j]) j += 1 # 将剩余的元素加入结果列表 result.extend(left[i:]) result.extend(right[j:]) return result ``` #### 数学建模总结 归并排序通过分治策略实现了高效的排序过程。分解阶利用递归将问题规模逐步缩小,而合并则通过双指针技术将两个有序子序列高效地合并一个更大的有序序列。整个过程的时间复杂度为 $ O(n \log n) $,空间复杂度为 $ O(n) $[^6]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值