编程珠玑笔记 ~~ 性能部分

本文探讨了算法优化的不同方法,包括改进二分查找、性能优化策略、数据结构利用、代码级优化等,提供了具体的实现案例。

3. 数据结构程序(略过)

 

4. 编写正确的程序

       1.在返回值p中返回元素t在数组x中第一次出现时的位置。(如果t在数组中多次出现的话,原先的算法所返回的是众多位置中的任意一个)。代码应该对数组元素进行对数次比较,可能要进行logn次这样的比较才能完成二分查找


补充:(临时想到的一个题目)

查找一个数组中出现次数超过数组元素总数一半的数字

 

1. 是否排序?

       如果是排序的序列,那么n/2位置的元素就是要找的元素。

       没有排序,则较复杂。

      

       2.使用先排序,后查找的方法。

       时间复杂度 O(nlogn): 快速排序之后,直接取数组的中间元素即可。

      

       3.统计元素的方法

       将数组中的所有元素统计一遍,那么需要额外的内存,最多时需要O(n/2)

      

       4.采用计数法:

       设置一个目标元素target,与目标元素的计数counter:碰到与target相同的元素则将计数器counter加1,遇到与target不同的元素,counter减1。如果counter小于0,则将下一个元素置为目标元素。

       因为有一个元素占数组一半,因此总会找到target,其counter大于0。

 

       2.二分查找基本上比顺序查找更快一些:如果要在一个具有n个元素的表中进行查找的话,二分查找粗略说来需要进行log n次比较,而顺序查找粗略说要进行n/2次比较,虽然二分查找通常已经够快了,在少数情况下,查找还必须更快一些,尽管在算法上你不能减少比较的逻辑次数,但是你能否重写二分查找代码,使它更快些吗?为了明确起见,假设你需要在一个具有1000个整数的排序表中进行查找。

      

       3.使用C/C++语言编写下面的递归二分查找,并证明其正确性。注:单独使用这个函数,不要调用任何其他的递归函数。

intbinarysearch(DataType x[], int n, DataType t)
{
        if(x== NULL)
        {
        	return - 1;
	}
 
	intmid = n / 2;
	if(x[mid] == t)
	{
		return mid;
	}
	else if(x[mid] < t)
	{
	       binarysearch(x, mid, t);
	}
	else if(x[mid] > t)
	{
	       binarysearch( x+mid+1, n– mid - 1, t);
	}
}

5. 编程中的次要问题(略)

6. 性能透视

性能优化主要从如下的几个方面进行:

       1.算法和数据结构

       2.代码优化

       3.系统软件:包括更新数据库,不同操作系统,以及开启编译器优化等

       4.硬件:运行速度更快的硬件可以提高性能

 

7. 性能估计(略)

 

8. 算法设计

       问题:输入时一个具有n个浮点数的向量x;输出时在输入的任何相邻子向量中找出的最大和。

      

       解法1:完成该任务时迭代所有满足0 <= i <= j < N 的i 和j整数对,对每一个i和j整数对都计算x[i…j]的总和,并检查总和是否大于迄今为止的最大总和。

#include <iostream>
#define N 10
using namespace std;
 
int main()
{
    int array[10] = {31, -41,59, 26, -53, 58, 97, -93, -23, 84};
 
    int maxsofar = 0;
    for( int i = 0; i < N;i++)
    {
        for(int j = 0; j <N; j++)
        {
            int sum = 0;
            for(int k = i; k<= j; ++k)
            {
                sum +=array[k];
            }
            maxsofar =max(maxsofar, sum);
        }
    }
    cout << "thesum of subarray sum: " << maxsofar << endl;
 
    return 0;
}

       解法2:在两次循环时,将所有的可能的情况都遍历完,求出其中最大的一项。

#include <iostream>
 
#define N 10
 
using namespace std;
 
int main()
{
    int array[10] = {31, -41,59, 26, -53, 58, 97, -93, -23, 84};
    int maxsofar = 0;
    for( int i = 0; i < N;i++)
    {
        int sum = 0;
        for(int j = i; j <N; j++)
        {
            sum += array[j];
 
            maxsofar = max(maxsofar, sum);
        }
    }
    cout << "thesum of subarray sum: " << maxsofar << endl;
 
    return 0;
}

      

       解法3:仍然是二次算法,通过访问在外部循环执行之前就构建的数据结构的方式进行。

在计算之前,首先计算一个累加和序列,这样可以通过两个元素差,求出i ~ j的子序列和。

#include <iostream>
#define N 10
using namespace std;
 
int main()
{
    int array[10] = {31, -41,59, 26, -53, 58, 97, -93, -23, 84};
 
    int temp[N+1];
    int * cumarr =&temp[1];
    cumarr[-1] = 0;
    for( int i = 0; i < N;i++)
    {
        cumarr[i] =cumarr[i-1] + array[i];
    }
    int maxsofar = 0;
    for( int i = 0; i < N;i++)
    {
        for(int j = i; j <N; j++)
        {
            int sum =cumarr[j] - cumarr[i-1];
 
            maxsofar = max(maxsofar, sum);
        }
    }
    cout << "thesum of subarray sum: " << maxsofar << endl;
    return 0;
}

       解法4:采用二分法的方法进行计算。

#include <iostream>
#define N 10
using namespace std;
 
int maxsum(int array[], int l, int u)
{
    if(l > u)
    {
        return 0;
    }
 
    if(l == u)
    {
        return max(0,array[l]);
    }
 
    int m = (l + u) / 2;
    int lmax = 0, sum = 0;
    for( int i = m; i >= l;i--)
    {
        sum += array[i];
        lmax = max(lmax, sum);
    }
 
    int rmax = sum = 0;
    for( int i = m+1; i <=u; i++)
    {
        sum += array[i];
        rmax = max(rmax, sum);
    }
 
    return max( lmax + rmax,max(maxsum(array, l, m), maxsum(array, m+1, u)));
}
 
int main()
{
    int array[10] = {31, -41,59, 26, -53, 58, 97, -93, -23, 84};
 
    int maxsofar =maxsum(array, 0, N-1);
    cout << "thesum of subarray sum: " << maxsofar << endl;
 
    return 0;
}

       算法4:扫描算法,从最左端(元素x[0])开始,一直扫描到最右端(元素x[n-1]),记下所碰到过的最大总和子向量,最大值为最初是0。

#include <iostream>
#define N 10
using namespace std;
 
int main()
{
    int array[10] = {31, -41,59, 26, -53, 58, 97, -93, -23, 84};
 
    int maxsofar = 0;
    int maxendinghere = 0;
 
    for( int i = 0; i < N;i++)
    {
        maxendinghere =max(maxendinghere + array[i], 0);
        maxsofar = max(maxsofar, maxendinghere);
    }
 
    cout << "thesum of subarray sum: " << maxsofar << endl;
 
    return 0;
}

根据上述的四个算法的依次改进,我们可以得到如下的几个算法设计技术:

       1.保存状态,避免重新计算。算法2和4中使用了简单的动态编程形式,使用一些空间来保存各个结果。

       2.将信息预处理到数据结构中,算法3中的cumarr中允许对子向量中的总和进行快速计算。

       3.分治算法,算法3中使用了本思想

       4.扫描算法。通常要回答,如何将x[0..i-1]的解决方法扩展到x[0..i]的解决方案

       5.累积 使用累积表,第i个元素包含了从0到i的所有元素的和

 

       补充:

       1.在算法3中,访问cumarr[-1]。在C语言中如何处理?

       在C语言中定义数组为N+1个,然后让cumarr指针指向数组的第二个元素,也即下标为1的元素,这样就可以得到cumarr[-1]了

       2.假设我们希望查找总和接近0的子向量,而不是查找具有最大总和的子向量。能够设计那种完成此任务的最有效的算法?可以使用那些算法设计技术?如果希望查找一个总和最接近某一个给定数的子向量,结果又如何?

       使用一个累积和向量,对其进行排序,找出相邻两项差最小的即和最接近的0的子序列和。

#include<iostream>
using namespace std;
 
#define N 10
 
int comp(const void* a, const void* b)
{
    int p = *(int *)a;
    int q = *(int *)b;
    if(p > q)
    {
        return 1;
    }
    else if(p < q)
    {
        return -1;
    }
    else
        return 0;
}
 
int main()
{
    int array[10] = {31, -41,59, 26, -53, 58, 97, -93, -23, 84};
 
    int temp[N+1];
    int * cumarr =&temp[1];
    cumarr[-1] = 0;
    for( int i = 0; i < N;i++)
    {
        cumarr[i] =cumarr[i-1] + array[i];
    }
 
    qsort( cumarr, 0, N-1,comp);
 
    int minvalue = 0;
    for( int i = 1; i < N;i++)
    {
        int temp =(int)abs(cumarr[i+1] - cumarr[i]);
        minvalue = min(minvalue, temp);
    }
    cout << "Min:" << minvalue << endl;
 
    return 0;
}

 

9. 代码优化

      

       问题1 ——整数求余

       2.3中给出了一个旋转一个向量可用的三种算法,在其中一个中要使用求余的运算:

       k= (j + rotdist) % n;

       而在计算机中,求余运算符 % 的开销十分大,尽管大多数的算术运算需要大约10纳秒的时间,但是求余运算要花费近100纳秒。可以改写代码如下:

       k= j + rotdist >= n ? j + rotdist – n : j + rotdist;

 

       问题2 ——函数,宏以及内联函数

       将函数用宏或内联函数代替,可以节省掉函数调用开销。

 

       注:在递归调用中,如果用宏替代函数调用,将造成性能明显降低,具体原因不明。

 

       问题3 ——顺序查找

       对未进行排序的顺序表,通过给数组设置“哨兵”,可以省略掉是否结尾的判断,从而增加查找速度。

      int  ssearch( t)
              hold= x[n];
              x[n]= t;
              for(i = 0;  ; i++)
                     ifx[i] == t
                            break;
              x[n]= hold;
              ifi == n && x[n] != t
                     return-1;
              else
                     returni;

       对循环进行展开也可以加速运行

 

二分查找的修改:

       原始的查找方法:

       l= 0; u = n – 1;

       loop

              ifl > u

                     p= -1; break;

              m= ( l + u) / 2;

              case

                     x[m]< t : l = m + 1;

                     x[m]== t : p = m; break;

                     x[m]> t : u = m - 1;

      

       修改为如下的查找方式:

       l= -1;  u = n;

       whilel+1 != u

              m= ( l + u) / 2;

              ifx[m] < t

                     l= m;

              else

                     u= m;

       p= u;

       ifp >= n || x[p] != t

              p= -1

      

       减少了判断的次数,因此增加速率

      

       对于1000个数字的特例:

i= 512
l = -1;
 
if(x[512] < t)
    l = 1000 - 512;
while i != 1
    i = i / 2;
    if(x[l+i] < t)
        l = l + i;
 
p = l + 1;
if p > 1000 || x[p] != t
    p = -1;

补充:

       7.给定一个非常长的字节序列(假设有10亿个或万亿个),你如何有效计算1位的总数?

       计算每一个输入单元(8位或32位)中为1的位数,然后将各个单元中的1位个数相加,最后可以得到所包含的的1的总数。

       检测32位的数中的1的格式:

       unsignedint a = b;

       if(a!= 0 && (a & (a-1)))

       {

              counter++;

       }

       a -= 1;

 

10. 数据空间压缩

压缩的方法:

       1.可以将稀疏矩阵使用邻接表表示      

       2.不保存,重新计算(以时间换空间)      

       3.数据压缩      

       4.垃圾回收

 

 By Andy  @ 2013-7-15

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值