zoukankan      html  css  js  c++  java
  • 分治法

    分治法不仅仅是应用于计算机学科,还涉及到了各行各业。分治意为分而治之。即就是将原问题分解为规模更小的,但是形式上与原问题相同的子问题来解决。对于较小的问题求解起来也是比较容易的,在有必要的时候,可以将子问题的解进行合并以得到原问题的解。

    归并排序就是分治思想的一种体现,归并排序的操作如下,假设给定一个数组num,需要对它排序,首先将数组分成两半部分,对这两半部分分别进行归并排序。最后将两个排好序的子序列进行合并,就可以得到排序以后的结果。使用伪代码描述如下:

    //伪代码描述
    type temp[n];                       //这可能是该算法唯一不足的地方,就是它的空间复杂度是O(N)
    MergeSort(type num[n],int start,int end)//排序算法
    {
        int mid = (start + end) / 2;    //中间位置
        MergeSort(num[n],start,mid);    //对前半部分子序列进行归并排序
        MergeSort(num[n],mid+1,end);    //对后半部分子序列进行归并排序
        Merge(num,start,mid,end,temp);  //这一步进行合并操作
    }
    Merge(num,start,mid,end,temp)//合并操作
    {
        i = start,j = mid + 1, k = start;
        //这里使用的办法可以称之为“双指针法”。在这里分别是i和j。它们分别指向了待合并的两个子序列的
        //第一个元素,然后比较这两个元素的大小(我在这里排序的结果是从小到大的),将小的那个元素放到
        //备用数组中。然后被复制的那个数组的指针后移,继续比较,直到某一子序列全部比较完毕。
        while(i < mid && j < end)
        {
            if (num[i] <= num[j])    temp[k++] = num[i++]; 
    		else    temp[k++] = num[j++];
    			
        }
        //将未处理的剩余元素,直接复制到备用数组temp中。
        if(i < mid)    copy temp[k++] = num[i++];
        else           copy temp[k++] = num[j++];
    }

    以上就是算法的描述,我们来看一张图解分析。

    这张图描述了对一个数组使用归并排序的具体过程。归并排序的C/C++实现代码如下:

    #include <iostream>
    int num[10] = { 2,3,1,5,7,6,8,0,9,4 };	//待排序数组
    int temp[10];	//备用数组
    using std::cout;
    void MergeSort(int *num, int start, int end);
    void Merge(int * num, int start, int mid, int end);
    int main()
    {
    	MergeSort(num, 0, 9);
    	for (int i = 0; i < 10; i++)
    	{
    		cout << num[i] << std::endl;
    	}
    	system("pause");
    }
    
    void MergeSort(int * num, int start, int end)
    {
    	int mid = (start + end) / 2;
    	if (start < end)
    	{
    		MergeSort(num, start, mid);
    		MergeSort(num, mid + 1, end);
    		Merge(num, start, mid, end);
    	}
    }
    
    void Merge(int * num, int start, int mid, int end)
    {
    	int i, j, k;
    	i = start;
    	j = mid + 1;
    	for (k = start; i <= mid && j <= end ; k++)
    	{
    		if (num[i] <= num[j])
    		{
    			temp[k] = num[i++];
    		}
    		else
    		{
    			temp[k] = num[j++];
    		}
    	}
    	while (i <= mid)
    	{
    		temp[k++] = num[i++];
    	}
    	while (j <= end)
    	{
    		temp[k++] = num[j++];
    	}
    	for (int i = start; i <= end; i++)	//将temp中的数据拷贝回num中
    	{
    		num[i] = temp[i];
    	}
    }
    

    快速排序也是分治法的一个典型代表。快速排序是使得下标s之前的元素都比num[s]小,在s之后元素都比num[s]大。这样我们把num[s]的位置就确定下来了,然后对num[s]之前的元素进行快速排序,也对num[s]之后的元素快速进行排序。这样就把一个序列的顺序排好了。在这里我们的初始s = 0.代码如下:

    #include <iostream>
    void QuickSort(int *num,int start,int end);
    void swap(int &a, int &b)
    {
    	int temp = a;
    	a = b;
    	b = temp;
    }
    int main()
    {
    	int num[10] = { 2, 4, 1, 3, 5, 9, 0, 6, 7, 8 };
    	QuickSort(num, 0, 9);
    	for (int i = 0; i < 10; i++)
    	{
    		std::cout << num[i] << std::endl;
    	}
    	system("pause");
    }
    
    void QuickSort(int * num, int start, int end)
    {
    	if (start >= end)
    	{
    		return;
    	}
    	int k = num[start];        //代码这里实际是用k表示的
    	int i = start;
    	int j = end;
    	while (i != j)
    	{
    		while (j > i && k <= num[j])    //偶数次的时候移动j.
    		{
    			j--;
    		}
    		swap(num[i], num[j]);
    		while (i < j && k >= num[i])    //奇数次的时候移动i.
    		{
    			i++;
    		}
    		swap(num[i], num[j]);
    	}
    	QuickSort(num, start, i - 1);
    	QuickSort(num, i + 1, end);
    }

    在快速排序中,s的选择是非常重要的,选择的好,则快速排序的时间复杂度将会是O(nlogn);选择的不好,将会是O(n²)。在上面选择初始s = 0的情况下,如果将要排序的序列是有序的,那么时间复杂度就会是O(n²)。关于s的选择策略有很多,其中较为简单的一种是选择第一个元素,中间元素,最后一个元素这三者的中值作为num[s]。

    例:给定一个序列,输出它的前m的大的数,要求从大到小输出。

    简单点的解法就是先给序列排序,然后输出。这样做的时间复杂度是O(nlongn);我们把这个问题分而治之,只给前m大的数排序,然后输出。我们在找前m大的数的时候,必须在O(n)内找到。这样时间复杂度就是O(n+mlogm)。当m不是非常接近n的时候,该时间复杂度接近O(n)。

    #include <iostream>
    using std::cout;
    using std::cin;
    
    void Mfun(int *num, int m, int start, int end);
    void QuickSort(int * num, int start, int end);
    void swap(int &a, int &b)
    {
    	int temp = a;
    	a = b;
    	b = temp;
    }
    int count = 0;
    int main()
    {
    	int num[10] = { 3,4,2,1,5,6,0,8,7,9 };
    	int m;
    	cout << "请输入m:";
    	cin >> m;
    	Mfun(num, m, 0, 9);
    	QuickSort(num, 10 - m, 9);
    	for (int i = 9; i > 9 - m ; i--)
    	{
    		cout << num[i] << std::endl;
    	}
    	system("pause");
    }
    void Mfun(int *num, int m, int start, int end)
    {
    	if (m < end - start + 1)	
    	{
    		int i = start;
    		int j = end;
    		int k = num[start];
    		while (i != j)
    		{
    			while (j > i && num[j] >= k)
    			{
    				j--;
    			}
    			swap(num[i], num[j]);
    			while (i < j && num[i] <= k)
    			{
    				i++;
    			}
    			swap(num[i], num[j]);
    		}
    		Mfun(num, m, i + 1, end);
    	}
    	if (m > end - start + 1)
    	{
    		m = m -(end - start + 1);
    		Mfun(num, m, start - m, start - 1);
    	}
    }
    void QuickSort(int * num, int start, int end)
    {
    	if (start >= end)
    	{
    		return;
    	}
    	int k = num[start];        //代码这里实际是用k表示的
    	int i = start;
    	int j = end;
    	while (i != j)
    	{
    
    		while (j > i && k <= num[j])    //偶数次的时候移动j.
    		{
    			j--;
    		}
    		swap(num[i], num[j]);
    		while (i < j && k >= num[i])    //奇数次的时候移动i.
    		{
    			i++;
    		}
    		swap(num[i], num[j]);
    	}
    	QuickSort(num, start, i - 1);
    	QuickSort(num, i + 1, end);
    }

     在求前m大的数,使用的思想就是快速排序的思想。使得下表为end - m之后的数都比num[end - m]大,这样就找到了前m大的数字,然后对这m个数字进行排序输出即可。代码的复杂度变高了,但是时间复杂度确实降低了,这就是体现了分治法的威力。

     

  • 相关阅读:
    变量的创建和初始化
    HDU 1114 Piggy-Bank (dp)
    HDU 1421 搬寝室 (dp)
    HDU 2059 龟兔赛跑 (dp)
    HDU 2571 命运 (dp)
    HDU 1574 RP问题 (dp)
    HDU 2577 How to Type (字符串处理)
    HDU 1422 重温世界杯 (dp)
    HDU 2191 珍惜现在,感恩生活 (dp)
    HH实习 acm算法部 1689
  • 原文地址:https://www.cnblogs.com/zy666/p/10504303.html
Copyright © 2011-2022 走看看