精华内容
下载资源
问答
  •  归并排序是采用分治法非常典型应用。归并排序的思想就是先递归分解组,再合并数组。归并(Merge)排序法是将两(或两以上)有序表合并成一有序表,即把待排序序列分为若干个子序列,每子...

    归并排序(Merge Sort)

            归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递分解数组,再并数组。归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

    原理:

            先考虑合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。

             再考虑递归分解,基本思路是将数组分解成leftright,如果这两个数组内部数据是有序的,那么就可以用上面合并数组的方法将这两个数组合并排序。如何让这两个数组内部是有序的?可以再二分,直至分解出的小组只含有一个元素时为止,此时认为该小组内部已有序。然后合并排序相邻二个小组即可。

    算法详细描述:

    • 把长度为n的输入序列分成两个长度为n/2的子序列;
    • 对这两个子序列分别采用归并排序;
    • 将两个排序好的子序列合并成一个最终的排序序列。

    动图演示:

    代码实现:

    def merge(left, right):
        '''合并操作
           将两个有序数组left[]和right[]合并成一个大的有序数组
        '''
        l, r = 0, 0
        result = []
        while l < len(left) and r < len(right):
            if left[l] < right[r]:
                result.append(left[l])
                l += 1
            else:
                result.append(right[r])
                r += 1
        result += left[l:]
        result += right[r:]
        return result
    
    
    def mergeSort(arr):
        if len(arr) <= 1: return arr
        num = int(len(arr) / 2)     #二分分解
        left = mergeSort(arr[:num])
        right = mergeSort(arr[num:])
        return merge(left, right)   #合并数组

    时间复杂度、空间复杂度及稳定性:

            归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。

    展开全文
  • 题目链接:http://hihocoder.com/contest/hiho39/problem/1 ,归并排序求逆序。  其实这道题也是可以用树状数组来做,不过数据都比较大,所以要离散化... 我们来看一个归并排序的过程: 给定数组为[2, 4, 5...

      题目链接:http://hihocoder.com/contest/hiho39/problem/1 ,归并排序求逆序数。

      其实这道题也是可以用树状数组来做的,不过数据都比较大,所以要离散化预处理一下,文中也会给出离散化+树状数组的解法,不过要比归并排序慢一点。

     


     

    算法:  

      还是按照题中给的解法。

      我们来看一个归并排序的过程:
      给定的数组为[2, 4, 5, 3, 1],二分后的数组分别为[2, 4, 5], [1, 3],假设我们已经完成了子过程,现在进行到该数组的“并”操作:

    a: [2, 4, 5]   b: [1, 3]   result:[1]   选取b数组的1
    a: [2, 4, 5]   b: [3]   result:[1, 2]   选取a数组的2
    a: [4, 5]   b: [3]   result:[1, 2, 3]   选取b数组的3
    a: [4, 5]   b: []   result:[1, 2, 3, 4]   选取a数组的4
    a: [5]   b: []   result:[1, 2, 3, 4, 5]   选取a数组的5

      在执行[2, 4, 5]和[1, 3]合并的时候我们可以发现,当我们将a数组的元素k放入result数组时,result中存在的b数组的元素一定比k小。在原数组中,b数组中的元素位置一定在k之后,也就是说k和这些元素均构成了逆序对。那么在放入a数组中的元素时,我们通过计算result中b数组的元素个数,就可以计算出对于k来说,b数组中满足逆序对的个数。

      又因为递归的过程中,a数组中和k满足逆序对的数也计算过。则在该次递归结束时,[2, 4, 5, 3, 1]中所有k的逆序对个数也就都统计了。同理对于a中其他的元素也同样有这样的性质。由于每一次的归并过程都有着同样的情况,则我们可以很容易推断出:

      若将每一次合并过程中得到的逆序对个数都加起来,即可得到原数组中所有逆序对的总数。

      即在一次归并排序中计算出了所有逆序对的个数,时间复杂度为O(NlogN)

    #include <iostream>
    #include <cstdio>
    #include <vector>
    #include <queue>
    #include <cmath>
    #include <string>
    #include <string.h>
    #include <algorithm>
    using namespace std;
    #define LL long long
    #define eps 1e-8
    #define INF 1000005
    const int maxn = 100000 + 5;
    int a[maxn] , b[maxn];
    LL sum;
    void merge(int a[] , int b[] , int l , int m , int r)
    {
        int i = l , j = m + 1 , k = 0;
        int cnt = 0;
        while(i <= m && j <= r) {
            if(a[i] <= a[j]) {
                b[k++] = a[i++];
                sum += cnt;
            } else {
                b[k++] = a[j++];
                cnt++;
            }
        }
        while(i <= m) {
            b[k++] = a[i++];
            sum += cnt;
        }
        while(j <= r)
            b[k++] = a[j++];
        for(int i = 0 ; i < k ; i++)
            a[i + l] = b[i];
    }
    void merge_sort(int a[] , int l , int r)
    {
        if(l < r) {
            int m = (l + r) >> 1;
            merge_sort(a , l , m);
            merge_sort(a , m + 1 , r);
            merge(a , b , l , m , r);
        }
    }
    int main()
    {
        int n;
        cin >> n;
        for(int i = 0 ; i < n ; i++)
            scanf("%d" , &a[i]);
        sum = 0;
        merge_sort(a , 0 , n - 1);
        cout << sum << endl;
        return 0;
    }
    归并

     

    也可以离散化+树状数组:

      先把数据存起来,然后进行排序,这样原来的每个数在排序后数组中的下标可作为新的值,这样来离散化数据。

      树状数组求逆序数的方法:假设求数组a[]的逆序对,倒序将数组中的每一个元素插入到树状数组中a[i]对应的位置,在插入每一个元素时,统计比它小的元素的个数。一次遍历之后,就能求得所有的逆序数。

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    #include <queue>
    #include <vector>
    #include <string>
    #include <string.h>
    #include <algorithm>
    using namespace std;
    const int maxn = 100000 + 5;
    int a[maxn] , c[maxn] , n;
    int lowbit(int x)
    {
        return x & (-x);
    }
    void update(int x , int num)
    {
        while(x <= n) {
            c[x] += num;
            x += lowbit(x);
        }
    }
    int getsum(int i)
    {
        int res = 0;
        while(i > 0) {
            res += c[i];
            i -= lowbit(i);
        }
        return res;
    }
    int binary_search(int a[] , int l , int r , int x)
    {
        int m = (l + r) >> 1;
        while(l <= r) {
            if(a[m] == x)
                return m;
            if(a[m] < x)
                l = m + 1;
            if(a[m] > x)
                r = m - 1;
            m = (l + r) >> 1;
        }
        return -1;
    }
    int main() 
    {
        cin >> n;
        for(int i = 1 ; i <= n ; i++) {
            scanf("%d" , &a[i]);
            c[i] = a[i]; 
        }
        sort(c + 1 , c + n + 1);
        for(int i = 1 ; i <= n ; i++) {
            int j = binary_search(c , 1 , n , a[i]);
            a[i] = j;
        }
        memset(c , 0 , sizeof(c));
        long long sum = 0;
        for(int i = n ; i >= 1 ; i--) {
            sum += getsum(a[i] - 1);
            update(a[i] , 1);
        }
        cout << sum << endl;
        return 0;
    }
    树状数组

     

      

     

    转载于:https://www.cnblogs.com/H-Vking/p/4389671.html

    展开全文
  • 如{2,4,3,1}中,2和1,4和3,4和1,3和1是逆序数对,因此整个数组逆序数对个数为4,现在给定一数组,要求统计出该数组逆序数对个数。 计算数列逆序数对个数最简单方便就最从前向后依次统计每个数字与它...

    首先来看看原题

     

    微软2010年笔试题

    在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序数对。一个排列中逆序的总数就称为这个排列的逆序数。如{2,4,3,1}中,2和1,4和3,4和1,3和1是逆序数对,因此整个数组的逆序数对个数为4,现在给定一数组,要求统计出该数组的逆序数对个数。

     

    计算数列的逆序数对个数最简单的方便就最从前向后依次统计每个数字与它后面的数字是否能组成逆序数对。代码如下:

    #include <stdio.h>
    int main()
    {
    	printf("     数列的逆序数对 \n");    
    	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");    
    
    	const int MAXN = 8;
    	int a[MAXN] = {1, 7, 2, 9, 6, 4, 5, 3};
    	
    	int nCount = 0;
    	int i, j;
    	for (i = 0; i < MAXN; i++)
    		for (j = i + 1; j < MAXN; j++)
    			if (a[i] > a[j])
    				nCount++;
    
    	printf("逆序数对为: %d\n", nCount);
    }

    运行结果如下:

    这种方法用到了双循环,时间复杂度为O(N^2),是一个不太优雅的方法。因此我们尝试用其它方法来解决。

     

    在《白话经典算法系列之五归并排序的实现》中观察归并排序——合并数列(135)(24)的时候:

    1.先取出前面数列中的1

    2.然后取出后面数列中的2明显!这个2和前面的35都可以组成逆序数对即3252都是逆序数对。

    3.然后取出前面数列中的3

    4.然后取出后面数列中的4同理,可知这个4和前面数列中的5可以组成一个逆序数对。

    这样就完成了逆序数对的统计,归并排序的时间复杂度是O(N * LogN),因此这种从归并排序到数列的逆序数对的解法的时间复杂度同样是O(N * LogN),下面给出代码:

    //从归并排序到数列的逆序数对
    #include <stdio.h>
    int g_nCount;
    void mergearray(int a[], int first, int mid, int last, int temp[])
    {
    	int i = first, j = mid + 1;
    	int m = mid,   n = last;
    	int k = 0;
    
    	while (i <= m && j <= n) //a[i] 前面的数  a[j] 后面的数
    	{
    		if (a[i] < a[j])
    			temp[k++] = a[i++];
    		else
    		{
    			temp[k++] = a[j++];
    			//a[j]和前面每一个数都能组成逆序数对
    			g_nCount += m - i + 1;
    		}
    	}
    
    	while (i <= m)
    		temp[k++] = a[i++];
    
    	while (j <= n)
    		temp[k++] = a[j++];
    
    	for (i = 0; i < k; i++)
    		a[first + i] = temp[i];
    }
    void mergesort(int a[], int first, int last, int temp[])
    {
    	if (first < last)
    	{
    		int mid = (first + last) / 2;
    		mergesort(a, first, mid, temp);    //左边有序
    		mergesort(a, mid + 1, last, temp); //右边有序
    		mergearray(a, first, mid, last, temp); //再将二个有序数列合并
    	}
    }
    
    bool MergeSort(int a[], int n)
    {
    	int *p = new int[n];
    	if (p == NULL)
    		return false;
    	mergesort(a, 0, n - 1, p);
    	return true;
    }
    
    int main()
    {
    	printf("     从归并排序到数列的逆序数对 \n");    
    	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");    
    
    	const int MAXN = 8;
    	int a[MAXN] = {1, 7, 2, 9, 6, 4, 5, 3};
    
    	g_nCount = 0;
    	MergeSort(a, MAXN);
    	printf("逆序数对为: %d\n", g_nCount);
    	return 0;
    }

    运行结果:

     

     

    好了,介绍到这里后,相信大家对如何求数列的逆序数对已经有了很好的认识,文章中所用到的“知识迁移”这种方法还是不错的,值得大家掌握。

     

     

    转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/8029996

    展开全文
  • 这个非常简单,只要从比较二个数列第一个数开始,谁小就先取谁。然后再进行比较,如果有数列比较完了,那直接将另一个数列数据依次取出即可。可以看出合并有序数列效率是比较高,可以达到O(n)。 1.2 算法...

    1. 数据结构和算法(十九)归并排序算法

    1.1 什么是归并排序

      归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数开始,谁小就先取谁。然后再进行比较,如果有数列比较完了,那直接将另一个数列的数据依次取出即可。可以看出合并有序数列的效率是比较高的,可以达到O(n)。
    在这里插入图片描述

    1.2 算法基本思想

      归并排序的核心思想其实很简单,如果要排序一个数组,我们先把数组从中间分成前后两部分,然后分别对前后两部分进行排序,再将排好序的两部分数据合并在一起就可以了。归并排序使用的是分治思想,分治也即是分而治之,将一个大问题分解为小的子问题来解决。分治算法一般都是用递归来实现的。分治是一种解决问题的处理思想,递归是一种编程技巧。

    在这里插入图片描述

    1.3 归并排序复杂度分析

    • 归并排序是一个稳定的排序算法,在进行子数组合并的时候,我们可以设置当元素大小相等时,先将前半部分的数据放入临时数组,这样就可以保证相等元素在排序后依然保持原来的顺序。

    • 不仅递归求解的问题可以写成递推公式,递归代码的时间复杂度也可以写成递归公式。

    • 如果我们对n个元素进行归并排序所需要的时间是T(n),那分解成两个子数组排序的时间都是在这里插入图片描述,而合并两个子数组的时间复杂度为 。所以,归并排序的时间复杂度计算公式为:O(n)

    1.4 归并排序代码实现

    package com.yuanxw.datastructure.chapter19;
    
    import java.util.Arrays;
    
    /**
     * 归并排序
     */
    public class MergeSort {
        public static void main(String[] args) {
            // int[] array = new int[]{10, -7, 8, -2, 5, -4};
            // 定义需要排序的数组
            int[] array = new int[]{8, 4, 5, 7, 1, 3, 6, 2};
            // 临时数组
            int[] temp = new int[array.length];
            System.out.println("【归并排序】前的结果:" + Arrays.toString(array));
            // 拆分
            mergeSort(array, 0, array.length - 1, temp);
            System.out.println("【归并排序】后的结果:" + Arrays.toString(array));
        }
    
        /**
         * 拆分数组
         *
         * @param array 原始数组
         * @param left  数组左侧下标
         * @param right 数组右侧下标
         * @param temp  临时数组(中间数组)
         */
        public static void mergeSort(int[] array, int left, int right, int[] temp) {
            // 如果【left < right】成立,说明还没有拆分到最小单元
            if (left < right) {
                // 得到中间索引
                int middle = (left + right) / 2;
                // 递归-拆份左边的部分
                mergeSort(array, left, middle, temp);
                // 递归-拆份右边的部分
                mergeSort(array, middle + 1, right, temp);
                // 合并 数组
                merge(array, left, middle, right, temp);
            }
        }
    
        /**
         * 合并数组
         *
         * @param array
         * @param left
         * @param middle
         * @param right
         * @param temp
         */
        public static void merge(int[] array, int left, int middle, int right, int[] temp) {
            // 左边最左侧下标
            int leftIndex = left;
            // 右边最左侧下标
            int middleIndex = middle + 1;
            // 临时数组
            int tempIndex = 0;
            while (leftIndex <= middle && middleIndex <= right) {
                // 比较左右两边大小,分别放到temp临时数组中
                if (array[leftIndex] <= array[middleIndex]) {
                    temp[tempIndex] = array[leftIndex];
                    tempIndex++;
                    leftIndex++;
                } else {
                    temp[tempIndex] = array[middleIndex];
                    tempIndex++;
                    middleIndex++;
                }
            }
    
            // 如果比较完毕, 第左边还有数剩下, 则全部填入temp
            while (leftIndex <= middle) {
                temp[tempIndex] = array[leftIndex];
                tempIndex++;
                leftIndex++;
            }
    
            // 如果比较完毕, 第右边还有数剩下, 则全部填入temp
            while (middleIndex <= right) {
                temp[tempIndex] = array[middleIndex];
                tempIndex++;
                middleIndex++;
            }
    
            // 将temp数组中的数据全部拷贝到array数组中
            for (int i = 0; i < tempIndex; i++) {
                array[left + i] = temp[i];
            }
        }
    }
    
    

    执行结果:

    【归并排序】前的结果:[8, 4, 5, 7, 1, 3, 6, 2]
    【归并排序】后的结果:[1, 2, 3, 4, 5, 6, 7, 8]
    

        – 以上为《数据结构和算法(十九)归并排序算法》,如有不当之处请指出,我后续逐步完善更正,大家共同提高。谢谢大家对我的关注。

    ——厚积薄发(yuanxw)

    展开全文
  • 重复并略过最末尾元素直到没有任何一对数字需要比较 #include <cstdio> #include <algorithm> using namespace std; int a[10005], n; void Bubble(){ for(int i = 0; i < n; i++) { for...
  • 首先来看看原题 ...如{2,4,3,1}中,2和1,4和3,4和1,3和1是逆序数对,因此整个数组逆序数对个数为4,现在给定一数组,要求统计出该数组逆序数对个数。   计算数列逆序数对个数最简单
  • 假设数组为a:[2 4 5],和b:[1 3],那么在这一次归并的时候逆序对这样求,belement表示当前result数组中b数组对应元素个数,total表示逆序对个数: a:[2 4 5] b:[1 3] result{} a:[2 4 5] b[3] result{1} belement...
  • 冒泡排序: 设数组长度为N, 1、比较相邻前后两个数据,数组为[0,N-1],则要...第一个for循环表示:要排序的数值个数-1 第二个for循环表示:将无序数组进行排序,即比较相邻两个数值 以下为代码:
  • * 快速排序:选取flag(这里选第一个数),先从右边开始找到小于它数,交换,再从左边开始找到大于它数,交换, * 这样它就在合适位置,并把数组分为两个部分,分别重复上述步骤即可。 * 时间复杂度:O...
  • 大常见排序算法

    热门讨论 2021-06-03 10:18:23
    大常见排序算法排序算法一:冒泡排序算法排序算法二:选择排序排序算法三:插入排序排序算法四:希尔排序排序算法五:归并排序 排序算法一:冒泡排序算法 基本思想:n个数比较n-1轮,第一轮有n个数,但是由于是...
  • 举例:现在要排序1,2,2这三个数,我们用A算法排序,如果排序后两个2位置不会互换,则A算法是稳定,如果互换了,则A算法就是不稳定。 稳定排序有哪些: 冒泡、插入、归并、二叉树排序都是稳定排序。 不稳定...
  • 排序算法

    2016-10-20 23:33:14
    1. 以下遍历中用到了swap()函数,进行两个数的交换 2. 其中的伪代码参考自《算法导论第三版》冒泡排序冒泡排序是一种简单的排序算法。它重复地“走访”要排序的数列,一次比较两个元素,如果他们的顺序错误就进行...
  • 排序算法

    2016-04-14 11:37:59
    输出:n个数的排列:a1’,a2’,a3’,…,an’,使得a1’’’…’。In-place sort(不占用额外内存或占用常数的内存):插入排序、选择排序、冒泡排序、堆排序、快速排序。 Out-place sort:归并排序、计数排序、基数...
  • 接着我们在讲解递归时候,介绍了归并排序归并排序需要O(NlogN),这比简单排序要快了很多,但是归并排序缺点,它需要空间是原始组空间两倍,当我们需要排序数据占据了整个内存一半以上空间,那么...
  • 目录几种常见排序算法算法思想简介1、基数排序2、归并排序3、冒泡排序4、快速排序5、直接插入6、折半插入7、希尔排序8、堆排序程序实现1、基数排序2、归并排序3、冒泡排序4、快速排序5、直接插入6、折半插入7、...
  • 数据结构(排序

    2016-10-08 12:03:00
    内排序吧整个数据ui放在内存中进行排序,外排序由于记录个数太多,不能全部放在内存中 7种排序算法分为两大类: (1)简单算法:冒泡排序,简单选择排序,直接插入排序 (2)改进算法:希尔排序,堆排序,归并排序,...
  • 排序算法总结

    2014-10-07 15:13:41
    输出:n个数的排列:a1',a2',a3',...,an',使得a1' In-place sort(不占用额外内存或占用常数的内存):插入排序、选择排序、冒泡排序、堆排序、快速排序。 Out-place sort:归并排序、计数排序、基数排序、桶排序...
  • 算法第记-线性排序

    2018-11-26 16:57:42
    我们已经知道很多高效率排序,归并排序、快速排序、堆排序。它们都是O(nlogn)级别。事实上还存在更高效率排序算法,但是它采取了“拿空间换取时间”,这三种排序算法分别是:①计数排序 ②基数排序 ③ 桶排序 ...

空空如也

空空如也

1 2 3
收藏数 58
精华内容 23
关键字:

九个数的归并排序