IV.-分治

本文介绍了分治法的基本概念,重点分析了归并排序和快速排序的步骤以及如何使用分治策略解决输出前m大数的问题。同时讨论了求排列逆序数的方法,包括归并排序的应用和优化。
摘要由CSDN通过智能技术生成

目录

A.分治的基本概念

B.归并排序 

C.快速排序

D.输出前m大的数

E.求排列的逆序数 


A.分治的基本概念


把一个任务,分成形式和原任务相同,但规模更小的几个部分任务(通常是两个部分),分别完成,或只需要选一部完成。然后再处理完成后的这一个或几个部分的结果,实现整个任务的完成。

B.归并排序 


 数组排序任务可以如下完成:

  •  把前一半排序
  •  把后一半排序
  •  把两半归并到一个新的有序数组,然后再拷贝回原数组,排序完成。

步骤如下:

第一步
第二步
第三步

第四步
#include <stdio.h>

int a[10]={ 13,27,19,2,8,12,2,8,30,89};
int b[10];
 
void Merge(int a[],int s,int m,int e,int tmp[])
{//将数组a的局部a[s,m]和a[m+1,e]合并到tmp,并保证tmp有序,然后再拷贝回a[s,m]
    int pb = 0;
    int p1 = s, p2 = m+1;
    while( p1<=m && p2<=e)
    {
        if( a[p1] < a[p2])
            tmp[pb++] = a[p1++];
        else
            tmp[pb++] = a[p2++];
    }
    while( p1 <= m)
    {
        tmp[pb++] = a[p1++];
    }
    while( p2 <= e)
    {
        tmp[pb++] = a[p2++];
    }
    for(int i=0;i < e-s+1;i++)
    {
        a[s+i] = tmp[i];
    }
}

void MergeSort(int a[],int s,int e,int tmp[])
{
    if( s<e)
    {
        int m = s + (e-s)/2;
        MergeSort(a,s,m,tmp);
        MergeSort(a,m+1,e,tmp);
        Merge(a,s,m,e,tmp);
    }
}

int main()
{
    int size = sizeof(a)/sizeof(int); 
    MergeSort(a,0,size-1,b);
    for(int i = 0;i < size; ++i)
    {
        printf("%d",a[i]);
    }
    printf("\n");
    return 0;
}

C.快速排序


数组排序任务可以如下完成:

  • 设k=a[0], 将k挪到适当位置,使得比k小的元素都在k左边,比k大的元素都在k右边,和k相等的,不关心在k左右出现均可 (O(n)时间完成)
  • 把k左边的部分快速排序
  • 把k右边的部分快速排序 

步骤如下:

第一步
第二步
第三步
第四步
第五步
第六步
第七步
第八步
第九步
#include <stdio.h>

int a[10]={ 93,27,30,2,8,12,2,8,30,89};

void swap(int *a,int *b)//交换变量a,b值
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}

void QuickSort(int a[],int s,int e)
{
    if( s>=e)
        return;
    int k = a[s];
    int i = s, j = e;
    while( i != j )
    {
        while( j>i && a[j]>=k )
        {
            j--;
        }
        swap(a[i],a[j]);
        while( i<j && a[i]<=k )
        {
            i++;
        }
        swap(a[i],a[j]);
    }//处理完后,a[i] = k;
    QuickSort(a,s,i-1);
    QuickSort(a,i+1,e);
}
int main()
{
    int size = sizeof(a)/sizeof(int); 
    QuickSort(a,0,size-1);
    for(int i = 0;i < size; ++i)
    {
        printf("%d",a[i]);
    }
    printf("\n"); 
    return 0;
}

D.输出前m大的数


题目描述

给定一个数组包含n个元素,统计前m大的数并且把这m个数从大到小输出。

输入

第一行包含一个整数n,表示数组的大小。n < 100000。

第二行包含n个整数,表示数组的元素,整数之间以一个空格分开 。每个整数的绝对值不超过100000000。
第三行包含一个整数m。m < n。

输出

从大到小输出前m大的数,每个数一行。

题目解答

  • 解法一:

                排序后再输出。

  • 解法二:

                用分治处理:

                思路:把前m大的都弄到数组最右边,然后对这最右边m个元素排序,再输出
                关键 :O(n)时间内实现把前m大的都弄到数组最右边

                        1)    设key=a[0], 将key挪到适当位置,使得比key小的元素都在key左边,比key大的元素都在key右边(线性时间完成)
                        2)       选择数组的前部或后部再进行 arrangeRight操作

                引入操作 arrangeRight(k): 把数组(或数组的一部分)前k大的都弄到最右边

                

#include <stdio.h>

void swap(int *a,int *b)
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}

void ArrangeRight(int *a,int left,int right,int key)
{
    if(left >= right)
        return;
    int mid = a[left];
    int i = left, j = right;
    while(i != j)
    {
        while( j > i && a[j] >= mid)
            j--;
        swap(a[i],a[j]);
        while( j > i && a[i] <= mid)
            i++;
        swap(a[i],a[j]);
    }
    if( right-i+1 > key)
        return ArrangeRight(a,i+1,right,key);
    else if( right-i+1 < key)
        return ArrangeRight(a,0,i-1,key-(right-i+1) );
}
int main()
{
    int n, num[100050];
    scanf("%d",&n);
    for(int i = 0;i < n; i++)
    {
        scanf("%d",&num[i]);
    }
    int m;
    scanf("%d",&m);
    ArrangeRight(num,0,n-1,m);
    sort(num+(n-m),num+n);
    for(int i = 0,j = n-1;i < m; i++, j--)
    {
        printf("%d",num[j]);
    }
}

E.求排列的逆序数 


题目描述

考虑1,2,…,n (n <= 100000)的排列i1,i2,…,in,如果其中存在j,k,满足 j < k 且 ij > ik, 那么就称(ij,ik)是这个排列的一个逆序。
一个排列含有逆序的个数称为这个排列的逆序数。例如排列 263451 含有8个逆序(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),因此该排列的逆序数就是8。
现给定1,2,…,n的一个排列,求它的逆序数。

题目解答

1)    将数组分成两半,分别求出左半边的逆序数和右半边的逆序数
2)    再算有多少逆序是由左半边取一个数和右半边取一个数构成(要求O(n)实现)

                ——2) 的关键:左半边和右半边都是排好序的

               比如,都是从大到小排序的。这样,左右半边只需要从头到尾各扫一遍,就可以找出由两边各取一个数构成的逆序个数

第一步

第二步

第三步
第四步
第五步
第六步
第七步
#include <stdio.h>

int mergeSortAndCount(int a[], int left, int mid, int right)
{
    int len1 = mid - left + 1;//左半部分lenth
    int len2 = right - mid;//右半部分lenth
    int al[len1], ar[len2];
    int count = 0; //逆序数
    int i, j;
    for(i = 0; i < len1; i++)//左半部分数组
        al[i] = a[left+i];
    for(i = 0; i < len2; i++)//右半部分数组
        ar[i] = a[mid+1+i];
    i = 0;
    j = 0;
    int k;
    for(k = left; k <= right; k++)
    {
        if(i >= len1)
        {
           a[k] = ar[j];
           j++;
        }
        else if(j >= len2)
        {
           a[k] = al[i];
           i++;
        }
        else if(al[i] > ar[j])
        {
           a[k] = al[i];
           i++;
           count += len2-j;
        }
        else
        {
           a[k] = ar[j];
           j++;
        }
    }
  return count;
}

int merge(int a[], int left, int right)
{
    int mid = left + (right-left)/2;
    int count = 0;
    if(left < right)
    {
         count += merge(a, left, mid);
         count += merge(a, mid+1, right);
         count += mergeSortAndCount(a, left, mid, right);
    }
    return count;
}

int main()
{
    int a[100];
    int n;
    scanf("%d", &n);
    for(int i = 0; i< n; i++)
        scanf("%d", &a[i]);
    printf("逆序数为:%d\n", merge(a, 0, n-1) );
    return 0;
}

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值