Check in algorithm question: Ques20201008

1. Problem description

Suppose S is a set of n unequal positive integers, n is an even number, and an algorithm is given to divide S into subsets S1 and S2 so that |S1|=|S2| reaches the maximum, that is, the sum of the elements of the two subsets The difference is maximized. (Requirement: T(n)=O(n)).

 

2. Problem solving ideas

Refer to the selection problem (select the k-th smallest element in A[1,…n]), the division method used (the lower limit of the complexity of the selection problem is O(n)), please refer to my other article Complexity It is the O(n) selection problem solution method . In this problem, select the n/2th smallest element (similar to the median m), and then use the median to use the partition function partition in fast sorting, which is less than or equal to The elements with the median m are divided into S1, and the elements larger than m are divided into S2. This can ensure that the final |S1|=|S2| and the final difference between the sum of the two subset elements reaches the maximum.

 

3. Code

#include <iostream>
#include <vector>
using namespace std;
class Ques20201007 {
public:
    vector<int> arrs = { 2,5,8,4,3,9,10,6,7,1,14,15 }; // n=12 整数
	void test() {
        
        int k = arrs.size() / 2; // 选取n/2大小的数
        int mid_postion=Select(arrs,0,11, k); // 返回的是中位数的索引
        swap(arrs[0],arrs[mid_postion]);
        int postion=partition(arrs,0, arrs.size()-1); // 再次划分
        // partition可以返回划分元素最终的位置,以此分为两个集合S1 S2
        vector<int> S1,S2;
        for (int i = 0; i < arrs.size();i++) {
            if (i<= postion) {
                S1.push_back(arrs[i]);
            }
            else{
                S2.push_back(arrs[i]);
            }
        }
        print();
	}
private:
    void print() {
        for (int i = 0; i < arrs.size();i++) {
            cout << arrs[i]<<" ,";
        }
        
    }
	//Q2. 设S是n个不等的正整数的集合,n为偶数,给出一个算法将S划分为子集S1和S2,使得|S1|=|S2|且
// 即两个子集元素之和的差达到最大。(要求:T(n)=O(n))

// 思路(我的):可参考选择问题(选出A[1,…n]中第k小的元素),所使用的划分法(选择问题的复杂度下限是O(n)),
//在本问题中选出第n/2小的元素(类似于中位数m)。然后再遍历一遍数组,小于等于中位数m的元素划分到S1,大于m的划分到S2。
//这样可保证最终|S1|=|S2|且最终两个子集元素之和的差达到最大。
    int partition(vector<int>& arr, int startIndex, int endIndex) { //选定作划分的数组元素(基准元素)是v=arr[m]                                      
        int pivot = arr[startIndex];//取第一个元素当做基准元素
        int left = startIndex;
        int right = endIndex;
        while (left != right) {

            while (left<right && arr[right]>pivot) {
                right--;// 控制right指针左移
            }
            while (left < right && arr[left] <= pivot) {
                left++;// 从右向左查
            }

            if (left < right) { // 交换A[i]和A[p]的位置
                swap(arr[left], arr[right]);
            }
        }
        //pivot和指针重合点交换
        arr[startIndex] = arr[left];
        arr[left] = pivot; // 划分元素在位置p,把本来在2位置的划分元素挪到4去。也相当于做一个交换
        return left; // 返回基准元素的位置
    }

    /* 二分法 查找插入位置*/
    int searchInsert(vector<int>& Array, int left, int right, int target) //vector是c++中一种容器,可看做数列
    {
        while (left <= right)
        {
            int mid = left + (right - left) / 2;
            if (Array[mid] == target)
            {
                return mid;
            }
            else if (Array[mid] < target)//如果中间值偏小,将初端右移
            {
                left = mid + 1;
            }
            else //中间值偏大则终端左移
            {
                right = mid - 1;
            }
        }
        return left;//如果目标值target比最大值还大,最后left>right,left=nums.size()
        //如果比最小值还小,最后left值为0,因为right会先减为-1后与left交换,left=0且比right大,跳出循环且符合题意,目标值变为初端
    }

    /* 插入排序,时间复杂度是n^2,但是使用二分查找优化后的插入排序复杂度是O(nlogn)*/
    void insertSort(vector<int>& arr, int start, int end)
    {
        int length = end - start + 1;
        //将arr[]按升序排列
        for (int j = 1; j < length; j++)
        {
            //利用二分法查找要插入的位置,计算机算法与分析第三章课后练习第二.1题
            //int key = arr[j+ times *5];
            int key = arr[j + start];
            int k = searchInsert(arr, start, j + start, key);//找到需要插入的位置k
            // 交换位置
            if (k != j + start) {
                for (int i = j + start; i > k; i--) {
                    arr[i] = arr[i - 1];
                }
                arr[k] = key;
            }

        }
    }
    int Select(vector<int>& arr, int m, int p, int k) {// 输入arr[0,...n],k是置顶要输出的第k小的元素
        // 将A划分为r(如r=5)个一组,共有n/5(向上取整)个组(取n/5个中位)
        // 每组找一个中位数,把它们放到集合M中
        // 可使用排序法,时间复杂度为O(c)
        // 参数说明:返回一个i值,使得A[i]是A[m..p]中第k小的元素   
        int n, i, j;
        int r = 5;
        if (p - m + 1 <= r) {
            insertSort(arr, m, p);
            return m + k; // m+k是这个的中位数
        }
        while (true) {
            n = p - m + 1;
            int t = floor(n / r);
            // 新的边界,0-m + floor(n / r) 
            if (t == 1) {
                // 当只有1组时,交换后arr[1]就是中位数了,直接去划分就好
                for (int i = 1; i <= 1; i++) {//计算中间值
                    int right = m + i * r - 1;
                    insertSort(arr, m + (i - 1) * r, right); // i代表这是第几组
                        // 将中间值收集到A[m..p]的前部
                    swap(arr[m + i - 1], arr[m + (i - 1) * r + (int)ceil(r / 2)]);
                }
            }
            else {
                for (int i = 1; i <= floor(n / r) + 1; i++) {//计算中间值
                    int right = m + i * r - 1;
                    if (i == floor(n / r) + 1) {
                        right = n - 1;
                        insertSort(arr, m + (i - 1) * r, right); // i代表这是第几组
                        // 最后一组元素个数n mod 5                
                        swap(arr[m + i - 1], arr[m + (i - 1) * r + (int)ceil((n % 5) / 2)]);
                    }
                    else {
                        insertSort(arr, m + (i - 1) * r, right); // i代表这是第几组
                        // 将中间值收集到A[m..p]的前部
                        swap(arr[m + i - 1], arr[m + (i - 1) * r + (int)ceil(r / 2)]);
                    }
                }
                int newright = m + floor(n / r);// 分成了3组就有3个中位数[0,2]再次进行排序,选最终的中位数
                // 为什么要将ceil(floor(n/r)/2 中值的中值 作为k传入=>选好的中位数现在排在最前面,还需要再中位数中选出中位数
                j = Select(arr, m, newright, ceil(floor(n / r) / 2));// 左边界m,右边界m+floor(n/r)-1,中值的中值
                // 这里期望j返回的是什么呢?返回中位数的位置?
                swap(arr[m], arr[j]);
            }
            int midposition = partition(arr, m, p); // 以最终选出的中位数arr[m],这里中位数已经被收集到前面了作为参照数,将数组s划分为两个部分,使得m所处的位置比左侧的数目大1
            if (midposition - m + 1 == k) {// 如果划分后左侧的元素的个数+1后刚好等于k,那么这个中位数就是要找的
                return midposition;
            }
            else if (midposition - m + 1 > k) {
                p = midposition - 1;


            }
            else {
                k = k - (midposition - m + 1);
                m = midposition + 1;

            }
        }

    }

};
//Ques20200925
int main() {
    Ques20201007 cla = Ques20201007();
    cla.test();
    return 0;
}

4. Running effect

Using the median of 6, divide into the front S1={1,3,2,4,5,6} and the back S2={7,8,9,10,14,15}, so the difference between the two sets The value is the largest, and the process complexity of finding the median is O(n).

 

Guess you like

Origin blog.csdn.net/Toky_min/article/details/108957187