精华内容
下载资源
问答
  • 解递归方程

    千次阅读 2018-05-10 16:54:10
  • 解递归方程时间复杂度

    千次阅读 2018-01-21 20:10:26
    很多时候递归求解是很不错的阶梯思路。但是递归求解的复杂度分析比较麻烦。下面给出基于master理论的求解方法: 此外,近一个月将补充使用递归树等方法进行求解的思路。 方法 一般的递归程序可以看作 T(n) = aT...

    综述

    很多时候递归求解是很不错的阶梯思路。但是递归求解的复杂度分析比较麻烦。下面给出基于master理论的求解方法:
    此外,近一个月将补充使用递归树等方法进行求解的思路。

    方法

    一般的递归程序可以看作
    T(n) = aT(n/b) + f(n)且f(n) 为theta(n^d)的形式。
    其中a是子问题的个数。n/b是子问题的规模。f(n)是本轮操作的复杂度。

    注意这里使用theta分析复杂度和O分析是一样的,也即O分析得到同样的结果。

    这里写图片描述
    例如
    考虑归并排序
    T(n) = 2T(n/2)+O(n)
    其中a=2,b=2,d=1;
    所以有:a=b^d即:
    T(n)为O(nlogn)

    补充方法

    对于T(n) = aT(n/b) + f(n)
    我们可以通过g(n)与f(n)比较来得到结果
    其中g(n) = n^(loga(b))

    若g(n)/f(n)>lg(n) -> T(n) = g(n)
    若g(n)=f(n)       -> T(n) = lg(n)* f(n)或lg(n) *g(n)
    若f(n)/g(n)>lg(n) -> T(n) = f(n)

    注意对于该范围之外的master理论无法求解

    展开全文
  • 一、分而治之的思想 ... ③把这些小实例的组合成原始大实例的 二、实际应用之找出假币 问题描述 一个袋子有16个硬币,其中只有一个是假币,这个假币比其他的真币重量轻(其他所有真币的重量都是相同的)...

    一、分而治之的思想

    • 分而治之方法与软件设计的模块化方法非常相似
    • 分而治之通常不用于解决问题的小实例,而要解决一个问题的大实例。一般步骤为:
      • ①把一个大实例分为两个或多个更小的实例
      • ②分别解决每个小实例
      • ③把这些小实例的解组合成原始大实例的解

    二、实际应用之找出假币

    问题描述

    • 一个袋子有16个硬币,其中只有一个是假币,这个假币比其他的真币重量轻(其他所有真币的重量都是相同的),现在要找出这个假币

    普通的解决方法

    • 一般的方法就是逐个的进行比较,一旦遇到质量比较轻的就找到假币了
    • 步骤为:
      • ①比较硬币1与硬币2,如果某个硬币比较轻,那么这个轻的硬币就是假币,终止寻找;否则进行下一步
      • ②继续比较硬币2与硬币3,如果某个硬币比较轻,那么这个轻的硬币就是假币,终止寻找;否则进行下一步
      • ......以此类推,直到找到假币终止寻找

    分而治之的解决方法

    • 分而治之的思想是把一个问题的大实例分解为两个或更多的小实例,然后进行比较
    • 此处我们将大实例分解为两个小实例,步骤为:
      • ①把16个硬币分为两组A和B,每组8个硬币,计算每组硬币的总质量并进行比较,较轻的那一组肯定含有假币
      • ②将较轻的那一组继续分组,分为A和B,每组4个硬币,然后计算每组硬币的总质量并进行比较,同理,较轻的那一组肯定含有假币
      • ③将较轻的那一组继续分组,分为A和B,每组2个硬币,然后计算每组硬币的总质量并进行比较,同理,较轻的那一组肯定含有假币
      • ④最终只剩下两个硬币,因此不需要再进行分组了,直接比较,较轻的那一个硬币肯定是假币

    三、实际应用之金块问题

    问题描述

    • 一个老板有一袋金块,每块金块的重量都不同,现在想要找出最重的那个金块与最轻的那个金块

    普通的解决方法

    • 普通的解决办法就是逐个比较,找出最重和最轻的金块
    • 步骤一般为:
      • 假设金块的总数为n
      • 先逐个比较每个金块,找出最重的金块
      • 找出最重的金块之后,从剩余的n-1个金块中再找出最轻的金块
    • 比较次数:因为找出最重的金块的比较次数为n-1次,从剩余的金块中再找出最轻的金块用了n-2次,所以总的比较次数为2n-3
    template<typename T>
    bool find_max_min(T arr[], int n,int &maxIndex,int &minIndex)
    {
        if (n <= 0)
            return false;
    
        //先找出最大的
        maxIndex = 0;
        for (int i = 1; i < n; ++i) {
            if (arr[maxIndex] < arr[i])
                maxIndex = i;
        }
    
        //再从剩余的当中找出最小的
        minIndex = 0;
        for (int j = 1; j < n; j++) {
            if (j == maxIndex)
                continue;
            if (arr[minIndex] > arr[j])
                minIndex = j;
        }
        
        return true;
    }
    
    
    int main()
    {
        int arr[] = { 1,4,2,5,1,8,5 };
        int maxIndex, minIndex;
    
        if (find_max_min(arr, sizeof(arr) / sizeof(int), maxIndex, minIndex)) {
            std::cout << "max:" << arr[maxIndex] << endl;
            std::cout << "min:" << arr[minIndex] << endl;
        }
    
        return 0;
    }

    分而治之的解决方法

    • 当n<=2时,一次比较就足够了
    • 当n>2时,总体步骤如下:
      • ①将金块分为A和B两个部分
      • ②分别找出A和B中最重的和最轻的,设A中最重和最轻的金块分别为Ha和La,A中最重和最轻的金块分别为Hb和Lb(这一步可以使用递归来实现)
      • ③比较Ha与Hb就可以找出最重的,比较La与Lb就可以找出最轻的
    • 演示案例:
      • ①假设n=8,有8块金块
      • ②将8个金块分为两个部分A和B,各有4个金块
      • ③因为A中有4个金块,我们将其再分为两个部分A1和A2,每个部分有2个金块
        • 然后通过一次比较可以找出A1中较重的金块Ha1和La1
        • 再通过一次比较可以找出A2中较轻的金块Ha2和La2
        • 然后再通过一次比较Ha1与Ha2找出A中最重的金块Ha,通过一次比较La1与La2找出A中最轻的金块La
      • ④因为B中有4个金块,我们将其再分为两个部分B1和B2,每个部分有2个金块
        • 然后通过一次比较可以找出B1中较重的金块Hb1和Lb1
        • 再通过一次比较可以找出B2中较轻的金块Hb2和Lb2
        • 然后再通过一次比较Hb1与Hb2找出A中最重的金块Hb,通过一次比较Lb1与Lb2找出A中最轻的金块Lb
      • ⑤最后进行一次比较Ha和Hb找出最重的金块,再进行一次比较La和Lb找出最轻的金块。步骤结束
    • 可以看出在上面的分而治之中总共需要比较10次

    • 设c(n)为所需要的比较次数。为了方便,假设n是2的幂:
      • 如果是分而治之法:当n=2时,c(n)=1;对于较大的n,c(n)=2c(n/2)+2
      • 如果是逐个比较方法:c(n)=3n/2-2
      • 因此,使用分而治之方法比逐个比较的方法少用了25%的比较次数

    分而治之编码实现

    • 如果使用递归,则步骤如下:
      • ①在上图的二叉树中,沿着根至叶子的路径,把一个大实例划分成为若干个大小为1或2的小实例
      • ②在每个大小为2的实例中,比较确定哪一个较重和哪一个较轻。在节点D、E、F、G完成这种比较。大小为1的实例只有一个金块,它既是最轻的也是最重的
      • ③对较轻的金块进行比较以确定哪一个最轻,对较重的金块进行比较以确定安一个最重。对节点A、B、C执行这种比较
    • 如果使用非递归的方法,代码如下:
      • 复杂度分析:当n为偶数时,在for循环外部又一次比较,内部有3(n/2-1)次比较。总的比较次数为3n/2。当n为奇数时,在for循环外部没有比较,内部有n(n-1)/2次比较。因此,无论n为奇数或偶数,当n>0时,总的比较次数为[3n/2]-2。这是在最早最大值最小值的算法中,比较次数最少的算法
    template<typename T>
    bool find_max_min(T arr[], int n,int &maxIndex,int &minIndex)
    {
        //如果数组大小小于等于0,直接退出
        if (n <= 0)
            return false;
        //如果只有一个金块,那么它既是最重的也是最轻的
        if (n == 1) {
            maxIndex = minIndex = 0;
            return true;
        }
        //如果金块数量大于等于2,开始下面的部分
    
        int s = 0;//用于标识比较的开始起点
        if (n % 2 == 1) {
            //如果金块数量为奇数,设定最大索引与最小索引为0,然后从s=1索引处开始进行比较
            maxIndex = minIndex = 0;
            s = 1;
        }
        else {
            //如果金块数量为偶数,从前两个元素中提取出较小元素与较大元素的索引,然后从s=2索引处开始比较
            if (arr[0] > arr[1]) {
                maxIndex = 0;
                minIndex = 1;
            }
            else {
                maxIndex = 1;
                minIndex = 0;
            }
            s = 2;
        }
    
        //从s开始进行比较,每两个元素比较一次
        for (int index = s; index < n; index += 2) {
            if (arr[index] > arr[index +1]) {
                if (arr[index] > arr[maxIndex])
                    maxIndex = index;
                if (arr[index + 1] < arr[minIndex])
                    minIndex = index + 1;
            }
            else {
                if (arr[index] < arr[minIndex])
                     minIndex = index;
                if (arr[index + 1] > arr[maxIndex])
                    maxIndex = index + 1;
            }
        }
    
        return true;
    }
    
    
    int main()
    {
        int arr[] = { 1,4,2,5,0,1,8,3,8 };
        int maxIndex, minIndex;
    
        if (find_max_min(arr, sizeof(arr) / sizeof(int), maxIndex, minIndex)) {
            std::cout << "max:" << arr[maxIndex] << endl;
            std::cout << "min:" << arr[minIndex] << endl;
        }
    
    	return 0;
    }

    四、实际应用之矩阵乘法

    • 待续

    五、编码案例(残缺棋盘)

    • 待续

    六、编码案例(归并排序)

    • 可以将分而治之的思想来设计排序算法,把n个元素按非递减顺序排列
    • 这种排序算法的思想是:
      • 若n为1,则算法终止
      • 否则,将序列划分为k个子序列(k是不小于2的整数),先对每一个子序列排序,然后将有序子序列归并为一个序列

    二路划分

    • 假设将n元素的序列仅仅划分为两个子序列,称之为二路划分
    • 一种二路划分的方法:
      • 把前面n-1个元素放到第一个子序列中(称为A),最后一个元素放到第二个子序列中(称为B)。然后对A递归进行排序,然后再将B仅有的一个元素归并插入到A中
      • 这种方法是插入排序的递归形式。该算法的复杂度为O
    • 另一种二路划分的方法:
      • 将关键字最大的元素放入B,剩余元素放入A,然后对A进行递归排序。然后将A和B归并,此时直接将B添加到A的尾部就可以了

    分而治之的方法

    • 上述方案是将n个元素划分为两个极不平衡的子序列A和B。A有n-1个元素,而B仅有一个元素
    • 现在我们划分的平衡一点,假设A包含n/k个元素,B包含其余的元素。递归地应用分而治之对A和B进行排序,然后采用一个被称之为归并的过程,将有序子序列A和B归并成一个序列
    • 假设有8个元素,关键字分别为[10、4、6、3、8、2、5、7]。则有:
      • 如果选定k=2:
        • 则可以划分出两个子序列[10、4、6、3]与[8、2、5、7]
        • 将上面两个子序列进行排序,可以得到两个有序子序列[3、4、6、10]、[2、5、7、8]
        • 现在从头元素开始比较,将两个有序子序列归并到一个子序列。元素2与3比较,2被移到归并序列;3与5比较,3被移动到归并序列;4与5比较,4被移到归并序列;5与6比较,以此类推....
      • 如果选定k=4:
        • 则可以划分出两个子序列[10、4]与[6、3、8、2、5、7]
        • 将上面两个子序列进行排序,可以得到两个有序子序列[4、10]、[2、3、5、6、7、8]
        • 根据相似的原理进行归并,便可以得到有序序列
    • 下面是对分而治之排序算法的伪代码描述。当生成的较小的实例个数为2,且A划分后的子序列具有n/k元素时,元素便可以得到最后结果:

    • 下面是相关的证明:

    二路归并排序代码实现

    • 上面的伪代码是在k=2时的排序算法,称为归并排序,更准确的说,是二路归并排序
    • 下图是在k=2时的归并排序的C++函数(非完整版,简略版):
      • 用一个数组a存储元素序列E,并用a返回排序后的序列
      • 当序列E被划分为两个子序列时,不必把它们分别复制到A和B中,只需简单地记录它们在序列E中的左右边界
      • 然后将排序后的子序列归并到一个新数组b中,最后再将它们复制回a中

    • 如果仔细考察上面的程序,就会发现,递归只是简单地对序列反复划分,知道序列的长度变为1,这时再进行归并。这个过程用n为2的幂来描述会更好:
      • 长度为1的子序列被归并为长度为2的有序子序列
      • 长度为2的子序列被归并为长度为4的有序子序列
      • 这个过程不断重复,直到归并为一个长度为n的序列
      • 下图是n=8时的归并(和复制)过程

    二路归并的迭代器算法(直接归并排序)

    • 有很多方面可以改进上面的C++函数。例如消除递归
    • 二路归并排序的一种迭代器算法是这样的:
      • 首先将每两个相邻的大小为1的子序列归并
      • 然后将每两个相邻的大小为2的子序列归并
      • 如此重复,直到只剩下一个有序序列
    • 轮流地将元素从a归并到b,从b归并到a,实际上消除了从b到a的复制过程,算法如下
    • 下面的函数用归并排序对数组元素a[0:n-1]排序
    template<typename T>
    void mergeSort(T a[], int n)
    {
        T *b = new T[n];
        int segmentSize = 1;
    
        while (segmentSize < n)
        {
            mergePass(a, b, n, segmentSize); //从a到b的归并
            segmentSize += segmentSize;
    		
            mergePass(b, a, n, segmentSize); //从b到a的归并
            segmentSize += segmentSize;
        }
    
        delete[] b;
    }
    • 为了完成排序代码,需要函数mergePass,不过这个函数仅用来确定需要归并的子序列的左右边界。实际的归并是由函数merge完成的
    //从x到y归并相邻的数据段
    template<typename T>
    void mergePass(T x[], T y[], int n, int segmentSize)
    {
        //下一个数据段的起点
        int i = 0;
    
        //从x到y归并相邻的数据段
        while (i <= n - 2 * segmentSize)
        {
            merge(x, y, i, i + segmentSize - 1, i + 2 * segmentSize - 1);
            i = i + 2 * segmentSize;
        }
    
        //少于两个满数据段
        if (i + segmentSize < n)
            //剩余两个数据段
            merge(x, y, i, i + segmentSize - 1, n - 1);
        else
            //只剩一个数据段,复制到y
            for (int j = i; j < n; j++)
                y[j] = x[j];
    }
    
    //把两个相邻数据段从c归并到d
    template<typename T>
    void merge(T c[], T d[], int startOfFirst, int endOfFirst, int endOfSecond)
    {
        int first = startOfFirst;    //第一个数据段的索引
        int second = endOfFirst + 1; //第二个数据段的索引
        int result = startOfFirst;   //归并数据段的索引
    
        //直到有一个数据段归并到归并段d
        while ((first <= endOfFirst) && (second <= endOfSecond))
        {
            if (c[first] <= c[second])
                d[result++] = c[first++];
            else
                d[result++] = c[second++];
        }
    
        //归并剩余元素
        if (first > endOfFirst)
            for (int q = second; q <= endOfSecond; q++)
                d[result++] = c[q];
        else
            for (int q = first; q <= endOfFirst; q++)
                d[result++] = c[q];
    }
    • 下面验证一下结果:

    自然归并排序

    • 在自然归并排序中,首先认定在输入序列中已经存在的有序段
    • 例如:
      • 在输入数列[4、8、3、7、1、5、6、2]中可以认定4个有序段:[4、8],[3、7],[1、5、6],[2]
      • 从左至右扫描序列元素,若位置i的元素比位置i+1的元素大,则位置i便是一个分割点。然后归并这些有序段,直到剩下一个有序段
      • 归并有序段1和2可得有序段[3、4、7、8],归并有序段3和4可得有序段[1、2、5、6]。最后归并这两个有序段可得到[1、2、3、4、5、6、7、8]。这样,只需要两次归并
    • 自然归并的最好情况是:输入序列已经有序。自然归并排序只认定了一个有序段,不需要归并,但上面的mergeSort()函数仍要进行[\log_{2}10]次归并,因此自然归并排序所需时间为Θ(n),而上面的mergeSort()函数需要用时Θ(n\log n)
    • 自然归并的最坏情况是:输入序列按递减顺序排序。最初认定的有序段有n个。这是的归并排序和自然归并排序需要相同的归并次数,但自然归并排序为记录有序段的边界需要更多的时间。因此,在最坏情况下的性能,自然归并排序不如直接归并排序
    • 在一般情况下,n个元素序列有n/2个有序段,因为第i个元素关键字大于第i+1个元素关键字的概率是0.5。如果开始的有序段仅有n/2个,那么自然归并排序所需的归并比直接归并排序的要少。但是自然归并排序在认定初始有序段和记录有序段的边界时需要额外时间。因此,只有输入序列确实有很少的有序段时,才建议使用自然归并排序

    七、编码案例(快速排序)

    • 基本思想为:
      • 把n个元素分为三段:左端left、中间段middle、右端right
      • 中间段仅有一个元素,左端的元素都不大于中间段的元素,右端的元素都不小于中间段的元素。因此可以对left段和right段独立排序,排序之后不用归并,直接就是有序的了
      • middle的元素称为“支点”或“分割元素”
    • 下面是快速排序的简单描述:

    演示说明

    • 设有一个序列[4、8、3、7、1、5、6、2]:
      • 假设以元素6作为支点(middle),则left段包含4、3、1、5、2,right段包含8、7
      • 将left排序的结果为1、2、3、4、5,将right排序的结果为7、8
      • 最终得到有序序列[1、2、3、4、5、6、7、8]
    • 设有一个序列[4、3、1、5、2]:
      • 假设以元素3作为支点(middle),则left段包含1、2,right段包含4、5
      • 将left排序的结果为1、2,将right排序的结果为4、5
      • 最终得到有序序列[1、2、3、4、5]

    C++代码实现

    • 下面是quick()的定义:
      • quickSort把数组的最大元素移动到数组的最右端,然后调用递归函数_qucikSort执行排序
    template<typename T>
    int indexOfMax(T a[], int n)
    {
        int max = 0;
        int index = 0;
        for (int index = 1; index < n; index++){
            if (a[index] > a[max])
                max = index;
        }
        return max;
    }
    
    template<typename T>
    void quick(T a[], int n)
    {
        if (n <= 1)
            return;
    
        //把最大元素移动到数组的最右端
        int max = indexOfMax<T>(a, n);
        std::swap(a[max], a[n - 1]);
    
        //然后调用_quickSort进行递归排序
        _quickSort(a, 0, n - 2);
    }
    • quick()函数中为什么要将最大元素放置于最右边的原因:下面的_qucikSort()要求每一个数据段,或者其最大元素位于右端,或者其后继元素大于数据段的所有元素,因此,把最大元素移到最右端;如果这个条件不满足,例如,当支点是最大元素时,第一个do循环语句的结果是左索引值leftCursor将大于n-1
    • 下面是_quickSort()的定义:
      • _qucikSort把数据段划分为左、中、右支点(pivot)总是待排序数段的左元素。其实还可以选择性能更好的排序算法。在后面我们将讨论这种算法
      • 在do-while语句中,把关系操作符<和>改为<=和>=,程序依然正确(这时,数据段最右边的元素比支点要大)

    template<typename T>
    void _quickSort(T arr[], int leftEnd, int rightEnd)
    {
        if (leftEnd >= rightEnd)
            return;
    
        int leftCursor = leftEnd;   // 记录该区间最左索引
        int rightCursor = rightEnd; // 记录该区间最右索引
        int pivot = arr[leftEnd];   // 我们以区间最左边的值为基准(哨兵)
    
        // 遍历整个区间
        while (leftCursor < rightCursor)
        {
            // 从右向左找出一个比哨兵小的值, 其索引为rightCurosr
            while (leftCursor < rightCursor && arr[rightCursor] >= pivot)
                rightCursor--;
            // 然后将这个比哨兵小的值放到区间左边, 之后将leftCursor++
            // 备注, 此处不需要担心arr[leftCursor]的值是否找不到了, 因为我们已经用pivot将其记录下来了
            if(leftCursor < rightCursor)
                arr[leftCursor++] = arr[rightCursor];
    
            // 从左向右找出一个比哨兵大的值, 其索引为leftCursor
            while (leftCursor < rightCursor && arr[leftCursor] <= pivot)
                leftCursor++;
            // 然后比这个比哨兵大的值放到右边, 之后将rightCursor
            if(leftCursor < rightCursor)
                arr[rightCursor--] = arr[leftCursor];
            
            // 这一轮比较完了, 如果中间还有元素没有进行比较, 进行下一轮
        }
    
        // 把哨兵的值放到leftCursor所指的位置(中间), 因为此时leftCursor是
        arr[leftCursor] = pivot;
    
        _quickSort(arr, leftEnd, rightCursor - 1);  // 对左区间递归排序
        _quickSort(arr, rightCursor + 1, rightEnd); // 对右区间递归排序
    }
    • 下面进行测试
    int main()
    {
        int a[] = { 4,5,1,4,2,3,8 };
        quick<int>(a, sizeof(a) / sizeof(int));
    
        for (auto val : a) {
            std::cout << val << " ";
        }
    
        return 0;
    }

    复杂度分析

    • 上面的quickSort()所需的递归栈空间为O(n)。若使用栈来模拟递归,则需要的空间可以减少为O(logn)。在模拟过程中,首先对数据段left和right中较小者进行排序,把较大者的边界放入栈中
    • 在最坏情况下,例如数据段left总是空,这时的快速排序用时为Θ(n^{2})。在最好情况下,即数据段left和right的元素数目总是大致相同,这时的快速排序用时为O(nlogn)。而快速排序的平均复杂度也是Θ(nlogn)

    • 下图对本专栏的排序算法在平均情况下和最坏情况下的复杂度做了比较:

    三值取中快速排序

    性能测量

    C++排序方法

    八、编码案例(选择问题)

    问题描述

    • 选择问题就是:从n个元素的数组中找出第k小的元素
    • 选择问题的一个实际应用就是寻找“中值”元素此时k=[n/2]。例如查找中间工资、中间年龄等等

    解决方法①

    • 选择问题可在O(nlogn)时间内解决。一种方法是:
      • 首先对n个元素的数组a进行排序(堆排序或归并排序等)
      • 然后取出a[k-1]的元素
    • 使用快速排序可以获得更好的平均性能。尽管该算法在最坏情况下的渐进复杂度比较差,仅为O(n^{2})
    • 代码如下:
    template<typename T>
    T select(T a[], int n, int k)
    {
        if (n < 1 || k < 1 || k > n)
            throw std::out_of_range("out_of_range");
    
        //排序,排序之后a为1 2 3 4 4 5 8
        std::sort(a, a + n);
        return a[k - 1];
    }
    
    int main()
    {
        int a[] = { 4,5,1,4,2,3,8 };
        int ret_val = select<int>(a, sizeof(a) / sizeof(int), 5);
        if (ret_val) 
            std::cout << "select success:" << ret_val << std::endl;
        else 
            std::cout << "select failed:" << ret_val << std::endl;
    
        return 0;
    }

    解决方法②

    • 修改上面的快速排序函数quickSort(),我们可以得到选择问题的一个较快的求解方法
    • 原理为:
      • 如果在两个while循环之后,将支点元素a[leftEnd]交换到a[j],那么a[leftEnd]便是a[leftEnd:rightEnd]中第j-leftEnd+1小的元素
      • 如果要寻找的第k小的元素在a[leftEnd:rightEnd]中:
        • 如果j-leftEnd+1等于k,那么答案就是a[leftEnd]
        • 如果j-leftEnd+1<k,那么要寻找的元素时right中的第k-j+leftEnd-1小的元素,否则是left中第k小的元素
      • 因此,只需进行0次或1次递归调用
    • 代码如下:在select中的递归调用可用for或while循环来代替
    template<typename T>
    int indexOfMax(T a[], int n)
    {
        int max = 0;
        int index = 0;
        for (int index = 1; index < n; index++) {
            if (a[index] > a[max])
                max = index;
        }
        return max;
    }
    
    template<typename T>
    T select(T a[], int n, int k)
    {
        if (k<1 || k>n)
            throw std::out_of_range("out_of_range");
    
        //把最大元素移动到数组的最右端
        int max = indexOfMax<T>(a, n);
        std::swap(a[max], a[n - 1]);
    
        //然后调用_quickSort进行递归排序
        return _select(a, 0, n - 1, k);
    }
    
    template<typename T>
    T _select(T a[], int leftEnd, int rightEnd, int k)
    {
        if (leftEnd >= rightEnd)
            return a[leftEnd];
    
        int leftCursor = leftEnd, rightCursor = rightEnd + 1;
        T pivot = a[leftEnd];
    
        //将位于左侧不小于支点的元素和位于右侧不大于支点的元素交换
        while (true)
        {
            do { //寻找左侧不小于支点的元素
                leftCursor++;
            } while (a[leftCursor] < pivot);
            do { //寻找右侧不大于支点的元素
                rightCursor--;
            } while (a[rightCursor] > pivot);
    
            //没有找到交换的元素对,退出
            if (leftCursor >= rightCursor)
                break;
            std::swap(a[leftCursor], a[rightCursor]);
        }
    
        if (rightCursor - leftEnd + 1 == k)
            return pivot;
    
        //放置支点
        a[leftEnd] = a[rightCursor];
        a[rightCursor] = pivot;
    
        //第一个数据段递归调用
        if (rightCursor - leftEnd + 1 < k)
            return _select(a, rightCursor + 1, rightEnd, k - rightCursor + leftEnd - 1);
        else
            return _select(a, leftEnd, rightCursor - 1, k);
    }
    
    int main()
    {
        int a[] = { 4,5,1,4,2,3,8 };
        std::cout << "select success:" << select<int>(a, sizeof(a) / sizeof(int), 5) << std::endl;
    
        return 0;
    }

    复杂度分析

    • 解决方法②所用的程序在最坏情况下的复杂度是Θ(n^{2})。所谓最坏情况就是left总是为空,第k小元素总是位于right。如果left和right总是一样大小或大小相差不超过一个元素,那么对上面的程序来说,可得到以下递归表达式:

    九、编码案例(相距最近的点对)

    • 待续

    十、分而治之解递归方程

    解递归方程的方法

    • 解递归方程有若干技术:替代法、归纳法、特征根法和生成函数法
    • 本节描述一种查表法,可以用来求解许多与分而治之算法有关的递归方程

    相关等式

    十一、复杂度的下限

    最小最大问题的下限

    排序算法的下限

    展开全文
  • 算法复杂性经常描述为递归方程,解递归方程得到算法复杂性的具体表示 用特征方程解递归方程 用生成函数解递归方程 用递推方法解递归方程
  • 递归方程的求解

    万次阅读 2016-04-21 13:01:39
    递归方程的求解 生成函数求解递归方程 生成函数及其性质 生成函数求递归方程 用特征方程求解递归方程 用递推法求解递归方程 用递推法求解常系数递归方程 用递推法求解变系数递归方程 函数换名 递归方程的求解@(VTK)...

    递归方程的求解

    @[递归方程, 生成函数, 递推法]

    生成函数求解递归方程

    生成函数及其性质。

    定义1:a0,a1,a2,是一个实数序列,构造如下的函数:

    G(z)=a0+a1z+a2z2+=k=0akzk

    则称G(z)称为a0,a1,a2,的生成函数。

    生成函数的性质:
    - 加法:设G(z)=k=0akzk是序列a0,a1,a2,的生成函数,H(z)=k=0bkzk是序列b0,b1,b2,的生成函数,则:

    αG(z)+βH(z)=αk=0akzk+βk=0bkzk=k=0(αak+βbk)zk

    是序列αa0+βb0,αa1+βb1,αa2+βb2,的生成函数。

    • 移位:设G(z)=k=0akzk是序列a0,a1,a2,的生成函数,则:

      zmG(z)=k=0akmzk

      是序列a0,a1,a2,的生成函数。

    • 乘法:设G(z)=k=0akzk是序列a0,a1,a2,的生成函数,H(z)=k=0bkzk是序列b0,b1,b2,的生成函数,则:

      G(z)H(z)=(a0+a1z+a2z2+)(b0+baz+b2z2+)=a0b0+(a0b1+a1b0)z+(a0b2+a1b1+a2b0)z2+=k=0ckzk

    是序列c0,c1,c2,的生成函数,其中cn=k=0akbnk

    • z变换:设G(z)=k=0akzk是序列a0,a1,a2,的生成函数,则:
      G(cz)=a0+a1(cz)+a2(cz)2+a3(cz)3+=a0+ca1z+c2a2z2+c3a3z3+

    是序列a0,ca1,ca2,的生成函数,特别地

    11cz=1+cz+c2z2+c3z3+

    是序列1,c,c2,c3,的生成函数。

    生成函数求递归方程

    递归关系式如下:

    {h(n)=2h(n1)+1h(1)=1

    h(n)作为系数,构造生成函数:

    G(x)=h(1)x+h(2)x2+h(3)x3+=k=1h(k)xk

    为了求解h(n)的值需要对G(x)进行演算:

    G(x)2xG(x)=h(1)x+h(2)x2+h(3)x3+2h(1)x22h(2)x3=h(1)x+(h(2)2h(1))x2+(h(3)2h(2))x3+=x+x2+x3+=x1x

    所以

    G(x)=x(1x)(12x)=112x11x=(1+2x+22x2+23x3+)(1+x+x2+x3+)=(21)x+(221)x2+(231)x3+=k=1(2k1)xk

    h(n)=2n1

    用特征方程求解递归方程

    递归方程的形式如下:

    {f(n)=a1f(n1)+a2f(n2)++akf(nk)f(i)=bi

    这种递归方程称为k阶常系数线性非齐次递归方程。其通解形式为:
    f(n)=f(n)+f(n)

    - 如果g(n)是n的m次多项式,即
    g(n)=b0nm+b1nm1++bm1n+bm
    其中bi是常数,特解f(n)也是n的m次多项式:
    f(n)=A0nm+A1nm1++Am1n+Am
    ,其中Ai为待定系数。
    - 如果g(n)具有以下指数形式:
    (2.1)g(n)=(b0nm+b1nm1++bm1n+bm)an

    其中a,bi为常数,如果a不是特征方程的重根,特解f(n)为:
    f(n)=(A0nm+A1nm1++Am1n+Am)an

    ,其中Ai为待定系数。如果a是特征方程的r重根,特解f(n)为:
    f(n)=(A0nm+A1nm1++Am1n+Am)nran

    ,其中Ai为待定系数。

    用递推法求解递归方程

    用递推法求解常系数递归方程

    {f(n)=bf(n1)+g(n)f(0)=c
    其中b,c是常数,g(n)是n的函数。
    f(n)=b(bf(n2)+g(n1))+g(n)==cbn+i=1nbnig(i)

    用递推法求解变系数递归方程

    {f(n)=g(n)f(n1)+h(n)f(0)=c
    其中g(n),h(n)是n的函数。
    f(n)=g(n)(g(n)f(n2)+h(n1))+h(n)==g(n)g(n1)g(1)(f(0)+i=1nh(i)g(i)g(i1)g(1))

    函数换名

    对于分治法,函数f(n)是以f(nm)表示递归的,因此,可以考虑令:

    g(k)=f(mk)=f(n)
    ,然后就可以使用上述方法计算递归方程的解。

    展开全文
  • 递归方程的渐进阶的求法 方法的关键步骤在于预先对解答作出推测,然后用数学归纳法证明推测的正确性
  • 求解递归方程的方法

    2011-10-10 20:12:43
    求解递归方程的方法 计算机算法设计与分析 ppt文件
  • 递归方程的渐进阶的求法——差分方程法 这里只考虑形如: T(n)=c1T(n-1)+c2T(n-2)+…+ ckT(n-k)+f(n),n≥k (6.18) 的递归方程。其中ci (i=l,2,…,k)为实常数,且ck≠0。它可改写为一个线性常系数k阶非齐...
  • 求解k阶线性递归方程

    千次阅读 2018-09-13 17:24:03
    1) 求解上述特征方程的根,得到递归方程的通 2)利用递归方程初始条件,确定通中待定系数,得到递归方程 考虑2种情况: 1)特征方程的k个根不相同 2)特征方程有相重的根 特征方程的k个根不相同:假设...
  • 递归方程求解法

    千次阅读 2019-07-03 12:13:21
    递归方程求解法 特征方程求解 特征方程法本质上是先猜后证的方法。我们猜测方程的得形式为 xnx^nxn ,然后带入递归式,求解 xxx。 k阶齐次线性常系数 给出的条件有递归关系和初始: {f(n)=a1f(n−1)+a2f(n−2)+...
  • 递归方程的渐进阶的求法——套用公式法 这个方法为估计形如: T(n)=aT(n/b)+f(n) (6.17) 的递归方程解的渐近阶提供三个可套用的公式。(6.17)中的a≥1和b≥1是常数,f (n)是一个确定的正函数。 (6.17)是一类...
  • 根据递归方程求时间复杂度
  • 算法复杂性经常描述为递归方程,解递归方程得到算法复杂性的具体表示 用特征方程解递归方程 用生成函数解递归方程 用递推方法解递归方程 用递推方法解递归方程,也就是我们常用的数学归纳法,用生成函数...
  • 递归方程的渐进阶的求法——代入法 用这个办法既可估计上界也可估计下界。如前面所指出,方法的关键步骤在于预先对解答作出推测,然后用数学归纳法证明推测的正确性。 例如,我们要估计T(n)的上界,T(n)满足...
  • 递归方程 递归方程之前提到过,就是部分算法在求解的过程中使用了将一个问题划分成几个等价的小问题,在这个过程中,我们就可以列出一个等式。 (如归并排序中,将一个大数组拆分成两个小数组分别计算,然后用O(n)的...
  • 算法时间复杂度分析中递归方程求解方法综述
  • 递归方程的特征方程

    2012-08-26 11:01:15
    k阶线性齐次递推关系  an= r1an-1 + r2an-2+ …+ rkan-k 特征方程characteristic equation ...(1)如果递归关系的 an= r1an-1 + r2an-2特征方程x2= r1x + r2有两个不同的根x1, x2,则数列的显式通项公式是:
  • 算法分析与设计的递归方程求解,分析介绍的很详细
  • 主定理: 特殊递归方程 迭代展开 已知: T(n) = 2T(n-1)+1 T(1)=1 展开: T(n)=2T(n-1)+1=2(2T(n-2)+1)+1=2(2(2T(n-3)+1)+1)+1=...=== 等差数列之和: 等比数列之和: 调和数列之和: 故T(n)=O() ...
  • 递归算法时间复杂度的计算方程式一个递归方程:    在引入递归树之前可以考虑一个例子:  T(n) = 2T(n/2) + n2  迭代2次可以得:  T(n) = n2 + 2(2T(n/4) + (n/2) 2)  还可以继续迭代,将其完全展开可得...
  • 递归 与 递归方程 从递推角度看,为求解数组 A 的求和问题 sum(A,n),需要 - 递归求解规模为 n-1 的问题 sum(A,n-1) - 再累加上 A[n-1] 递推方程 看其复杂度, (1)T(n)=T(n−1)+O(1)//recurrence(2)T...
  • T(1) = c 的递归关系, 有如下结论: if (a > b^k) T(n) = O(n^(logb(a))); if (a = b^k) T(n) = O(n^k*logn); if (a < b^k) T(n) = O(n^k); 对于:T(n) = 25T(n/5)+n^2 a=25 b = 5 k=2 有:a==b^k 故T(n)=O...
  • 递归方程的求解方法

    2018-01-01 20:17:00
    转载于:https://www.cnblogs.com/xiu68/p/8168751.html
  • 时间复杂度-求解递归方程

    千次阅读 2019-06-12 22:32:58
    以上内容来自于:https://www.cnblogs.com/xiu68/p/8168751.html
  • 给定自然数 n,确定关于 x,y,z 的不定方程 的所有自然数。 当时的小象同学并不会做这道题。多年后,经过高等教育的洗礼,小象同学发现这道题其实很简单。小象同学认为你一定也会做这道题,所以把这道题留给了...
  • 为了在C语言中实现递归算法,常常使用递归函数,也就是说能调用自身的函数.C语言中的递归函数相当于数学函数的递归定义.我们研究递归就从考察直接求值数学函数的程序开始,并从它的基本机制扩展到一种通用的程序设计...
  • 在算法设计中经常需要通过递归方程估计算法的时间复杂度T(n),本文针对形如T(n)=aT(n/b)+f(n)的递归方程进行讨论,以期望找出通用的递归方程的求解方式。算法设计教材中给出的Master定理可以解决该类方程的绝大多数...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,242
精华内容 8,496
关键字:

如何解递归方程