算法——分治


前言

这次我们要学习的是一种基本的算法思想,分治。之前学习的二分查找其实是属于查找算法里的一类,但它也包含着分治思想。


一、主要思想

分治算法,根据字面意思解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。就像我们打牌前需要先洗牌,若牌的数目较多,一个人洗不过来,则会将牌进行分堆,单独洗一小堆牌是相对容易的,每一堆牌都洗完之后再放到一起,则完成洗牌过程。

二、使用场景

①该问题的规模只要小到一定程度就可以容易地解决。
②该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
③该问题分解出的子问题的解可以合并为总问题的解。
④该问题所分出的各个子问题之间相互独立,互不影响。

三、基本步骤

①分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题。
②求解:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题。
③合并:将各个子问题的解合并为原问题的解。
在这里插入图片描的述

四、典型例题

1.归并排序

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表。
在这里插入图片描述图片来源于公众号:五分钟学算法

步骤

  1. 分解:将原问题(整个待排序列)分解为若干个规模较小,相互独立,与原问题形式相同的子问题(子序列)。本题我们采用折半的分解方法。
  2. 求解:若子问题规模较小而容易被解决则直接解(若子序列小到只有一个元素,就可以结束递归),否则递归地解各个子问题(否则分为左右子序列递归排序)。
  3. ③合并:将各个子问题的解(已经排好序的子序列)合并为原问题的解。在本题中使用Merge函数进行合并,也正是在Merge中才涉及到真正的比较大小从而进行排序。

代码

void Merge(int a[], int left, int mid, int right)
{//将两个排好序的子序列合并为一个,此过程也需要排序
	int* temp = new int[right - left + 1];	//声明一个辅助数组并开辟空间
	int pl = left;	//一个用来遍历左侧子序列的指针
	int pr = mid + 1;	//一个用来遍历右侧子序列的指针
	int pnow = 0;	//一个存放指针
	while (pl <= mid && pr <= right)
	{//两个子序列都没有遍历完时,执行下面语句
		if (a[pl] <= a[pr])
			temp[pnow++] = a[pl++];
		else
			temp[pnow++] = a[pr++];
	}
	while (pl <= mid)
	{//如果左边序列未检测完,直接将后面所有元素加到合并的序列中
		temp[pnow++] = a[pl++];
	}
	while (pr <= right)
	{//如果右边序列未检测完,直接将后面所有元素加到合并的序列中
		temp[pnow++] = a[pr++];
	}
	for (int i = 0; i <= right - left; i++)
	{//将排好序的数组返回给原数组
		a[i+left] = temp[i];
	}
}

void MergeSort(int a[], int start, int end)
{//传入一个待排数组,以及待排序片段的始末位置
	if (start < end)
	{//当子序列中只有一个元素时结束递归
		int mid = (start + end) / 2;		//从中间划分子序列
		MergeSort(a, start, mid);			//对左侧子序列进行递归排序
		MergeSort(a, mid + 1, end);	//对右侧子序列进行递归排序
		Merge(a, start, mid, end);		//合并排序好的左右子序列
	}
}

分析
时间复杂度为O(nlogn)。

2.快速排序

步骤

  1. 在待排序列中任选一个数据作为基准(pivot),为这个基准在序列中找到一个位置,使得序列中左边元素都小于等于基准,右边元素都大于等于基准
  2. 完成第一步后,基准左右两边还是无序的,所以需要分别继续按照第一步的方法排序,以此类推

代码

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

void QuickSort(int arr[], int low, int high) 
{
    if (high <= low) 
        return;	//传入数组只有一个元素时直接返回,也是递归的结束条件
    int i = low;    //指向序列头部
    int j = high;   //指向序列尾部
    int key = arr[low];     //基准
    while (i != j)
    {//下列循环直到i = j时候结束
        while (j > i && arr[j] >= key)      //从右边开始遍历
            j--;    //只要j指向的元素大于key,就让j减一,指向下一个
        swap(arr[i], arr[j]);   //一旦j指向的元素不大于key,就让这个元素与key交换位置
        while (j > i && arr[i] <= key)  //从左边开始遍历
            i++;    //只要i指向的元素小于key,就让i加一,指向下一个
        swap(arr[i], arr[j]);   //一旦i指向的元素不小于key,就让这个元素与key交换位置
    }
    /*完成上述代码以后,key的左右两边都分别小于和大于key,接下来要继续如此排序左右两边的序列*/
    /*完成上述代码后i=j,arr[i]=arr[j]=key*/
    QuickSort(arr, low, i - 1);     //对左边序列进行递归排序
    QuickSort(arr, i + 1, high);    //对右边序列进行递归排序
}

分析

时间复杂度为O(nlogn),最坏情况下为O(n^2)。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页