2016年计算机考研43题——快速排序划分思想寻找中间值

目录

1. 题目描述

2. 算法设计基本思想

2.1 满足条件的划分情况

2.2 划分算法的实现

3. 算法的C++代码实现

4. 算法时间、空间复杂度分析

4.1 空间复杂度分析

4.2 时间复杂度

        4.2.1 精确递推式

        4.2.2 直观理解

1. 题目描述


2. 算法设计基本思想


2.1 满足条件的划分情况

        为了使得 |n_1 - n_2 | 有最小值,则两个集合的元素个数应该尽量接近,在此前提下,为了使得 |S_1 - S_2| 有最大值,则当且仅当一边的所有元素都小于等于另一边的所有元素满足题意.

        对于第一个条件,若的元素个数为偶数 n,则划分所得两边集合的元素个数分别为 \frac{n}{2}, \frac{n}{2} 即可;若 的元素个数为奇数 n,则划分所得两边集合的元素个数分别为⌊ \frac{n}{2} ⌋, ⌊ \frac{n}{2} ⌋ - 1,其中 ⌊ ⌋, 表示下取整符号,譬如,n = 5,则两边集合元素个数分别为 2,3.

与统计学中的顺序统计量类似地,我们定义 A(n) 表示将 A 排好序后的排在第 n 位的元素,也即 A 中第 n 小的元素.

        对于第二个条件,若的元素个数为偶数 n,可以把 A(1),A(2) ... A(\frac{n}{2})划分在一个集合中,把 A(\frac{n}{2} + 1), A(\frac{n}{2} + 2),... A(n)划分在另一个集合中即可;若 的元素个数为奇数 n,则需考虑   A (⌊\frac{n}{2}⌋ + 1) 应该归为哪一方:

M1 为A(1) 到 A(⌊\frac{n}{2}⌋)的求和值,M2 A (⌊\frac{n}{2}⌋ + 2) 到 A(n)的求和值,k =  A (⌊\frac{n}{2}⌋ + 1) 

   ​​​    1. 若将 k 归于 M1,则S1 = k + M1,S2 = M2,|S_1 - S_2| = |M1 + k - M2|,进一步地,另 x = (M2 - M1),有 |S_1 - S_2| = |k - x|

       2. 若将 k 归于 M2,则S1 = M1,S2 = k + M2,|S_1 - S_2| = |M1 - M2 - k|,进一步地,另 x = (M2 - M1),有 |S_1 - S_2| = |k + x|

        由于 k > 0, x > 0,则 |k - x| <  |k + x|,故将 k 划分至 M2最为合适.

2.2 划分算法的实现

         在2.1的证明中,我们已经知道需要无论 的元素个数为奇数还是偶数,均需要找到第⌊\frac{n}{2}⌋ + 1小的元素,并将比它小元素归在一个划分中,将它以及比它大的元素归在另一个划分中.

        显然,一个比较直观的解法,是对所给集合A从头到尾进行全序列的快速排序,再在有序序列中查找排位在⌊\frac{n}{2}⌋ + 1的元素进行划分。如此,平均时间复杂度将与快速排序相同,即O(nlog_2n).

        但由于我们仅需统计比 A (⌊\frac{n}{2}⌋ + 1) 小元素的元素和 S1,大于等于 A (⌊\frac{n}{2}⌋ + 1) 的元素和 S2,无需对这两个划分内部进行排序,因此在快速排序递归划分的过程中,若某次枢纽元素恰为      A (⌊\frac{n}{2}⌋ + 1),则可以停止排序,此时A (⌊\frac{n}{2}⌋ + 1) 前面的元素均比它小(虽然可能是无序状态),      A (⌊\frac{n}{2}⌋ + 1) 后面的元素均比它大(虽然也可能是无序状态),但不影响我们求和;同时,由于我们的目标转为找到 A (⌊\frac{n}{2}⌋ + 1) 作为枢纽的划分,因此每次划分只需递归进行元素个数大于等于⌊\frac{n}{2}⌋ + 1的子段即可,因为我们可以保证A (⌊\frac{n}{2}⌋ + 1)一定在该子段当中.

3. 算法的C++代码实现


        划分 (Partition) 部分的算法,可直接照搬一般快速排序中的划分,这里选取序列尾元素作为划分枢纽 (pivot),函数返回枢纽最终的下标:

int Partition(int a[], int low, int high){
    int i = low, j = high;    //待划分序列的左、右下标范围
    int pivot = a[high];      //选取序列尾元素作为划分枢纽
    while(1){
        while(a[i] < pivot) i++;
        while(j > i && a[j] > pivot) j--;
        if(i >= j)
            break;
        else
            swap(a[i++],a[j--]);
    }
    swap(a[i], a[high]);
    return i;
}

        由于本题只需递归划分一个子段,故在此采用迭代的方式实现A (⌊\frac{n}{2}⌋ + 1)的查询:

void Solution(int a[], int n){
    int l = 1;              //原始序列首元素下标
    int r = n;              //原始序列尾元素下标
    int i = 0;              //记录当前划分的枢纽
    bool suc = false;       //是否成功找到中间值目标
    while(!suc){
        i = Partition(a, l, r);
        //成功找到
        if(i == n / 2 + 1)  
            suc = true;
        //失败,则待找枢纽在元素个数超过 n / 2 + 1的一方
        else if(i < n / 2 + 1)  
            l = i + 1;
        else
            r = i - 1;
    }
    int S1 = 0, S2 = 0;      //记录求和结果
    cout << "S1: ";
    for(int t = 1; t < i; t++){
        cout << a[t] << " ";
        S1 += a[t];
    }
    cout << endl << "S2: ";
    for(int t = i; t <= n; t++){
        cout << a[t] << " ";
        S2 += a[t];
    }
    //输出结果
    cout << endl << "|S1 - S2| = " << abs(S1 - S2);
}

        主函数,注意元素下标从1开始:

int main(){
    int n;
    cin >> n;
    int a[n+1];
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    Solution(a, n);
}

代码运行:

输入:
5
5 1 3 4 2
输出:
S1: 2 1
S2: 5 4 3
|S1 - S2| = 9
输入:
10
5 1 9 8 2 6 3 4 7 0
输出:
S1: 0 5 1 9 2
S2: 3 4 6 7 8
|S1 - S2| = 11

4. 算法时间、空间复杂度分析


4.1 空间复杂度分析

        本题采用与快速排序类似的算法,无需增加额外空间,因此空间复杂度为 O(1).

4.2 时间复杂度

4.2.1 精确递推式

        记 C(n)为问题规模为 n 的平均比较次数,

         C(n) = (n-1) + \frac{1}{n}(\sum_{k=1}^{k=\frac{n}{2}} C(n-k) + \sum _{k=\frac{n}{2} + 2}^{k =n} C(k-1) + 0)

        其中,n - 1表示当前轮划分的比较次数,每个元素都需与枢纽元素比较,共n - 1次.

        其中,\sum_{k=1}^{k=\frac{n}{2}} C(n-k) 表示本轮枢纽落在1 ~ \frac{n}{2}时,所有可能子段(即pivot以右的子段)的查找长度之和.

        其中,\sum _{k=\frac{n}{2} + 2}^{k =n} C(k-1) 表示本轮枢纽落在\frac{n}{2} + 2时,所有可能子段(即pivot以左的子段)的查找长度之和.

        其中,0表示本轮枢纽恰好落在\frac{n}{2} + 1,则无需继续查找.

        前面的系数 \frac{1}{n} 是对所有可能情况的平均.

4.2.2 直观理解

        上述递推公式的求解较为困难,但我们可以参照快速排序平均性能的分析对本题加以理解:   

        根据《算法导论7.2 快速排序的性能》中的分析,我们可以直观地认为平均情况下,最优划分与最差划分交替出现,

                                           

        可见,紧跟最差划分后的最优划分使得剩余子段重新回归平均划分的情况,即我们可以认为:最优划分能够吸收最差划分,故直观上认为,平均情况下的划分结果是接近最优划分的.

        本题中,最优划分情况下,一次划分便可得到结果,故比较次数只有 n-1,即时间复杂度为O(n),最差划分情况下,每次划分仅排除一个元素(如第一次划分排除最大元素,第二次划分排除最小元素,第三次划分排除次大元素,以此类推),则至多需要 O(n^2)的时间复杂度才能找到   A (⌊\frac{n}{2}⌋ + 1),相当于对序列做了排序。由于划分时平均情况接近最优情况,故本题平均时间复杂度为O(n).

🚩 欢迎读者指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值