算法设计与分析--分治法 --》线性时间选择问题、众数问题、求逆序对数、棋盘覆盖问题

目录

一 、线性时间选择问题

二、众数问题

三、求逆序对数

四、棋盘覆盖问题


算法设计与分析--分治法 --》线性时间选择问题、众数问题、求逆序对数、棋盘覆盖问题_第1张图片

 

一 、线性时间选择问题

任务描述

给定线性无序数组n个元素和一个正整数k,1≤k≤n,要求在线性时间找到这n个元素的第k小。

相关知识

  1. 排序求第K个元素。由于排序算法的时间复杂度都在O(nlogn),因此不满足线性时间要求。
  2. 借用快速排序中的划分Partition思想,选一个基准元素,将比基准元素小的放到左侧,比基准元素大的放到右侧,如果基准元素的位置是j,则比较k与j的大小:
  • k==j 则基准元素刚好是第k小元素,返回
  • k
  • k>j 则第k小在右侧,对右侧递归找第j-k小
  1. 该方法效率取决于每次选择的基准元素,如果基准元素能将数组每次分成ϵn1−ϵn0<ϵ<1)两部分,则能在线性时间完成找第k小任务;如果每次选择基准元素是最大值或最小值,则退化成最坏情况,时间复杂度为O(n2)
  2. 问题的关键变成如何在线性时间找一个基准元素,能将数组划分成ϵn1−ϵn0<ϵ<1)两部分.思想如下:
  • 将所有元素每5个一组,分成n/5组,将每组的中位数找到
  • 以所有中位数的中位数做为基准元素

编程要求

完成一个冒泡排序函数:

  1. void BubbleSort(Type a[],int p,int r)

完成根据基准元素x进行划分的函数:

  1. int Partition(int a[],int p,int r,Type x)

完成线性时间选择数组a[p]~a[r]的第k小函数:

  1. int Select(int a[],int p,int r,int k)
  • 数组元素个数小于等于75时,直接用冒泡排序,返回第k小
  • 数组元素个数大于等于75时,调用Select函数,返回第k小

测试说明

  • 函数中数组的元素是整型,返回值也是整型。

  • 输入的数组元素是整型,且不重复。

代码如下:

#include
using namespace std;
//p 为起始元素,r为最后一个元素地址,
//线性排序,找数组中第k小的数
int  Select(int a[],int p,int r,int k);
//冒泡排序
void Bubblesort(int a[], int p, int r);
//交换两个元素位置
void Swap(int& a, int& b);
//将x作为基准数,将数组分割,返回x的位置
int Partition(int a[], int p, int r, int x);

int main()
{
    int a[1000] = { 0 }, n, k;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    cin >> k;
    cout << Select(a, 0, n - 1, k) << endl;
    return 0;
}

void Swap(int& a, int& b)
{
    int temp = a;
    a = b;
    b = temp;

}

int Partition(int a[], int p, int r, int x)
{
    int i = p - 1, j = r + 1;
    //将比x大的数换到x右边,小的数换到左边
    while (true)
    {
        //i从x右边找到第一个大于等于x的数
        while (a[++i] < x && i < r);
        //j从最后一个元素开始找到第一个小于等于x得数
        while (a[--j] > x && j > p);
        if (i >= j)
            break;//找到基准值的位置,j
        Swap(a[i], a[j]);
    }
    return j;
}

void Bubblesort(int a[], int p, int r)
{
    int n = r - p + 1;
    for (int i = 0; i < n - 1; i++)
    {
        for (int j = 0; j < n - 1 - i; j++)
        {
            if (a[j] > a[j + 1])
            {
                Swap(a[j], a[j + 1]);
            }
        }
    }
    return;
}

int Select(int a[], int p, int r, int k)
{
    if (r - p < 75)//数组元素较少时,直接利用排序来找
    {//随便用一种排序,这里用的是冒泡排序
        Bubblesort(a, p, r);
        return a[p + k - 1];//返回第k小的元素
    }
    //分成n/5组,每组5个元素,找到每组的中位数并放到数组前边
    for (int i = 0; i <= (r - p - 4) / 5; i++)
    {
        int pp = p + i * 5, rr = p + 5 * i + 4;
        Bubblesort(a, pp, rr);
        int mid = (rr - pp + 1) / 2 + pp;
        Swap(a[mid], a[p + i]);
    }
    //找各组中位数的中位数--在if语句中返回
    int x = Select(a, p, p + (r - p - 4) / 5, (r - p - 4) / 10 + 1);//(r-p-4)/5元素的个数,(r-p-4)/10+1指要找第几个数。
    //按照中位数划分,i为x在数组中的位置
    int i = Partition(a, p, r, x);
    //求比中位数x小的数组的长度
    int len = i - p + 1;
    if (k <= len)//则k在较小数中
    {
        return  Select(a, p, i, k);
    }
    else
    {
        return Select(a, i + 1, r, k - len);
    }
}

二、众数问题

任务描述

给定含有n个元素的多重集合S,每个元素在S中出现的次数称为该元素的重数。多重集S中最大的元素称为众数。给定一个n个自然数组成的多重集合S,设计算法求其众数和重数。 例如:给出 S = [1,2,3,4,5,2,2] S其众数是2,重数是3

相关知识

  1. 排序统计法
    • 将集合S中的元素进行排序。
    • 排序后,数组相同的元素都会相邻出现。
    • 遍历数组,在遍历过程中记录出现过的重数最大的元素及其重数
  2. 分治法
    • 选取一个基准数,将比基准数小的放于左侧,比基准数大的放与右侧
    • 计算基准数的重数
    • 如果左半部分的元素个数大于基准数重数,递归求左半部分众数及其重数
    • 如果右半部分的元素个数大于基准数重数,递归求右部分众数及其重数
    • 三者中重数最大的是S的众数

编程要求

  • 本关的编程任务是用分治法编写求众数及其重数的函数
  • int GetMode(int a[],int p,int r,int &Count)
  • 返回值为数组a[p]~a[r]之间的众数,参数Count存放众数对应的重数。
#include
using namespace std;

//排序,比基准值大的放右边,小于等于放左边,并计算基准值的重数,重数初始值为1
//p为数组第一个元素,r为数组最后一个元素,x为基准值的下标
void Partition(int a[], int p, int r,int x,int jizhun[])
{
	int left = p , right = r;
	while (left < right)
	{
        //从右边遍历找到第一个<=基准值的元素
		while (a[right] > a[x] && right != left)//要注意,左右指针前进的顺序,因为以第一个元素为基准值,故右指针先走
		{
			if (a[right] == a[x])//判断等于为基准值的
				jizhun[1]++;//基准值的重数
			right--;
		}
//从左边遍历找到第一个>基准值的元素
		while (a[left] <= a[x] && left != right)
		{
			if (a[left] == a[x])
				jizhun[1]++;
			left++;
		}
		
		//交换位置,使左边的元素<=基准值,右边的元素>基准值
		if (left != right)
		{
			int temp = a[left];
			a[left] = a[right];
			a[right] = temp;
		}
	}
    //基准值归位
	int temp = a[right];
	a[right] = a[x];
	a[x] = temp;
	jizhun[0] = right;//存基准值下标
}
//函数返回值:众数
int getMode(int a[], int p, int r, int& Cnt)
{
	int Cnt_m=1,Cnt_l = 1, Cnt_r = 1;//存左右边、基准值元素的重数
	int mod_m=0,mod_l = 0, mod_r = 0;//存左右、基准值众数
	int* jizhun = new int[2];//数组0存基准值下标,1存基准值重数
	jizhun[0] = 0; jizhun[1] = 1;//设置初始值
    //设置基准值为数组第一个元素
	Partition(a,p,r,p,jizhun);//数组第一个元素为基准值,对数组进行划分
	mod_m = a[p]; Cnt_m = jizhun[1];//基准值的重数和众数
	int leftsize = jizhun[0] - jizhun[1] + 1;//基准值左边元素个数
	int rightsize = r - jizhun[0];//基准值右边的元素个数
	if (leftsize > jizhun[1])//左边元素个数多,递归求左边元素
	{
		mod_l=getMode(a, p, (jizhun[0] - jizhun[1]), Cnt_l);
	}
	if (rightsize > jizhun[1])//右边元素个数多,递归求右边元素
	{
		mod_r=getMode(a, (jizhun[0] + 1), r, Cnt_r);
	}
	//比较三者重数--特殊情况没有考虑---》即重数均为1或者有两个数的重数相等,再加一部分比较重数相等情况下的众数即可,这里可以单独写一个比较函数.
	if (Cnt_l > Cnt_r)
	{
		if (Cnt_l > Cnt_m)
		{
			Cnt = Cnt_l;
			return mod_l;
		}
		else
		{
			Cnt = Cnt_m;
			return mod_m;
		}
	}
	else
	{
		if (Cnt_r > Cnt_m)
		{
			Cnt = Cnt_r;
			return mod_r;
		}
		else
		{
			Cnt = Cnt_m;
			return mod_m;
		}
	}
}

int main()
{
	int n, Cnt, mode;
	cin >> n;
	int* a = new int[n];
	for (int i = 0; i < n; i++)
		cin >> a[i];
	mode = getMode(a, 0, n - 1, Cnt);
    cout << mode<<"\n"<

三、求逆序对数

任务描述

对于不同的排序可以用逆序来评价它们之间的差异。 一个排列含有逆序的个数称为这个排列的逆序数。例如排列 263451 含有8个逆序,(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),因此,该排列的逆序数就是8。 显然,由1,2,...,nn个数组成的n!个排列中,最小逆序数是0,对应排列1,2,...,n。 最大逆序数是2n(n−1)​,对应排列n,n−1,...,2,1。 逆序数越大与原始排列差异越大。

输入格式

第一行是一个整数n,表示该排列有n个数(n<=100000)。

第二行是n个不同的正整数,之间以空格隔开,表示该排列。

输出格式

输出该排列的逆序数。

输入输出样例

测试输入:

6 2 6 3 4 5 1 预期输出: 8

提示

结合归并排序做


//归并排序及求逆序对
#include
using namespace std;
#define N 1000005
int a[N], b[N];//b为辅助数组
long long cnt;
void merge_sort(int l, int r)
{
	// 如果整个区间中元素个数大于1,则继续分割
	if (r - l > 0)
	{
		// 1、尽量划分为数量相等的2个子序列,并排序
		int mid = (l + r) / 2;
		merge_sort(l, mid);
		merge_sort(mid + 1, r);

		// 2、将2个有序的序列合并成一个有序序列,在左右有序的子数组合并的同时,统计逆序对数
		/*********Begin***********/
		
		int left = l, right = mid + 1;
		int bj = l;//b数组的下标从l开始
		while (left <= mid &&right <= r)//对数组进行排序,存到b数组中
		{
			if (a[right] < a[left])
			{
				b[bj] = a[right];
				cnt=cnt+right-bj;//right-bj计算元素向前挪的位数,也就是逆序数
				right++;
			}
			else
			{
				b[bj] = a[left];
				left++;
			}	
			bj++;
		}
		if (right <= r)//将剩余元素存入b中
			b[bj++] = a[right++];
			
		
		if (left <= mid)
		    b[bj++] = a[left++];
		
	   /*********End***********/
		// 3、将辅助数组b中排好序的元素复制到a中
		for (int i = l; i <= r; i++)
			a[i] = b[i];
	}
}
int main()
{
	int n;
	while (cin >> n)
	{
		for (int i = 1; i <= n; i++)
			cin >> a[i];
		cnt = 0;
		merge_sort(1, n);
		cout << cnt << endl;
	}
	return 0;
}

四、棋盘覆盖问题

任务描述

在一个2k×2k个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。

算法设计与分析--分治法 --》线性时间选择问题、众数问题、求逆序对数、棋盘覆盖问题_第2张图片

例如:4×4的棋盘的一种覆盖方法为:

算法设计与分析--分治法 --》线性时间选择问题、众数问题、求逆序对数、棋盘覆盖问题_第3张图片

编写函数,输出给定k的一种棋盘覆盖。

编程要求

根据提示,在右侧编辑器补充代码。

输入格式

输入为三个正整数: size dr dc 其中(size=2k,0≤k≤5),(dr,dc)是特殊方格的位置,且0≤dr,dc≤size−1

输出格式

输出棋盘覆盖方案,特殊方格处输出-1,其他位置处同一编号的L形骨牌用同一个数字表示,数字占宽4格,右对齐。

输入输出示例

测试输入:8 3 2

预期输出:

2 2 3 3 7 7 8 8

2 1 1 3 7 6 6 8

4 1 5 5 9 9 6 10

4 4 -1 5 0 9 10 10

12 12 13 0 0 17 18 18

12 11 13 13 17 17 16 18

14 11 11 15 19 16 16 20

14 14 15 15 19 19 20 20


#include
#include
# define SIZE 32
using namespace std;

int tile = 0;
int Board[SIZE][SIZE];
//棋盘以(tr,tc)为左上角,以size为边长,特殊方格位置在(dr,dc),ChessBoard函数用分治法
//完成它四个子棋盘覆盖
void ChessBoard(int tr, int tc, int dr, int dc, int size)
{
    /************Begin**************/
    //递归出口
    if (size == 1)
        return;
    int s = size / 2;//分割棋盘
    int t = tile++;//记录本层骨牌序号
    //判断特殊方格在不在左上棋盘上
    if (dr < tr + s && dc < tc + s)
    {
        ChessBoard(tr, tc, dr, dc, s);//特殊方格在左上棋盘上
    }
    else
    {
        Board[tr + s - 1][tc + s - 1] = t;//用t号L型覆盖右下角
        ChessBoard(tr, tc, tr + s - 1, tc + s - 1, s);//递归覆盖其余方格
    }
    //判断特殊方格在不在右上棋盘上
    if (dr < tr + s && dc >= tc + s)
    {
        ChessBoard(tr, tc + s, dr, dc, s);
    }
    else
    {
        Board[tr + s-1][tc + s ] = t;
        ChessBoard(tr, tc + s, tr + s - 1, tc + s, s);
    }

    //判断特殊方格在不在左下棋盘
    if (dr >= tr + s && dc < tc + s)
    {
        ChessBoard(tr + s, tc, dr, dc, s);
    }
    else
    {
        Board[tr + s][tc + s - 1] = t;
        ChessBoard(tr + s, tc, tr + s, tc + s - 1, s);
    }
    //判断特殊方格在不在右下棋盘
    if (dr >= tr + s && dc >= tc + s)
    {
        ChessBoard(tr + s, tc + s, dr, dc, s);
    }
    else
    {
        Board[tr + s][tc + s] = t;
        ChessBoard(tr + s, tc + s, tr + s, tc + s, s);
    }

    /************End**************/

}
int main()
{
    /************Begin**************/
    int dr = 0, dc = 0;//存特殊方格位置
    int size = 0;//存棋盘大小
    cin >> size >> dr >> dc;
    ChessBoard(0, 0, dr, dc, size);
    Board[dr][dc] = -1;//设置特殊方格为-1
    /************End**************/
    for (int i = 0; i < size; i++)
    {
        for (int j = 0; j < size; j++)
            cout << setw(4) << Board[i][j];
        cout << endl;
    }
    return 0;
}

你可能感兴趣的:(算法,数据结构)