数据结构与算法分析基础----概念初步&最大子序和四种算法C++实现&对分查找&欧几里德算法

来源:互联网 发布:nginx服务启动 编辑:程序博客网 时间:2024/06/16 01:36

本文为《数据结构与算法分析-C语言分析》Mark Allen Weiss 的读书笔记
这里写图片描述
本书讨论的是数据结构和算法分析。
数据结构:主要研究组织大量数据的方法。
算法分析:对算法运行时间的评估。

//这是本利用纯C写例子的教材,我准备用C++把书里的例子重写,先这么打算。

//争取在12月14号之前对数据结构算法分析有一个框架的了解

Chapter 1:引论

选择问题:设有一组N个数确定其中第K个最大者。
解决选择问题的“显而易见“的方法很多,书籍着重讨论哪一种方法“效率”更高。

相关的一些数学知识:
1. 指数
2. 对数
3. 级数
4. 模运算:同余(两个数字除以某个除数余数相同)
5. 归纳证明
6. 反证法
7. 递归//看着觉得很重要

Chapter 2:算法分析

讨论以下几点内容:
1. 评估一个程序所需要的时间。
2. 如何把一个程序运行时间从天甚至年降到秒。
3. 粗心使用递归的后果。
4. 处理自乘得到幂以及计算最大公约数的有效的算法。

数学基础://估计算法的消耗的理论基础(时间复杂度)
定义:如果存在正常数cn0使得当NnoT(N)cf(N),则记为T(N)=O(f(N))
定义:如果存在正常数cn0使得当NnoT(N)cg(N),则记为T(N)=Ω(g(N))
定义:T(N)=Θh(N)当且仅当T(N)=O(h(N))T(N)=Ω(h(N))
定义:如果T(N)=O(p(N))T(N)Θh(N),则记为T(N)=o(p(N))(这里是小o不是大O)

—————-以上这一大段公式打得好烦,以后争取少打点,但数学表达起来真的挺漂亮—————–

模型:跑算法自然是要一个计算模型,这里的模型基本上是一台标准的PC,在机器中指令被顺序地执行。

需要分析的问题:要分析的最重要的资源一般就是运行时间。

最大子序列和问题:给定整数A1,A2,...,AN(可能有负数),求jk=nAk的最大值,如果都为负值,最大子序列和为0. (其实顾名思义就是求一个序列里的最大自序列之和)

例:如果输入-2,11,-4,13,-5,-2则答案为20

这个问题有非常多的算法,这些算法性能差异很大,四种算法在某台计算机的运行时间如下:
这里写图片描述
可以看出来算法的优劣对于运行时间的影响还是非常大的。(不包括读入数据的时间)

运行时间计算:
1.抛弃常数项
2.抛弃低阶项
3.只计算大O

例如下面这个计算Ni=1i3的例子:

CPP

#include<iostream>using namespace std;int 第一个计算总和简单的例子(int N);int main() {    cout<<"计算的总和为:"<< 第一个计算总和简单的例子(100)<<endl;    system("pause");}int 第一个计算总和简单的例子(int N) {    int 总和=0;    for (int i = 1; i <= N; i++)总和 += i*i*i;    return 总和;}

分析:
1.int 总和=0;和return 总和;各占一个时间单元
2.总和 += i*i*i;每执行一次占4个单元总计4N个时间单元
3.for (int i = 1; i <= N; i++)总计2N+2个时间单元,因为初始化int i=1一个时间单元,i <= N总计N+1次,i++总共N次
4.综上,这个函数模块总共花费6N+4个时间单元,抛却常数项和低阶项我们可以说这个函数是O(N)。

—————-上面的工作也就展示下如何计算时间模块,之后肯定不能每次都这么做————-

一般法则:
1.FOR循环:一次for循环的运行时间最多是for循环内部语句的运行时间乘以迭代次数
2.嵌套的For循环:从里向外分析,乘一下就好。
3.顺序语句:求和即可(感觉是废话)
4.IF/ELSE语句:加一下就好

解决上述最大子序和的四种方法
1.无脑循环,循环三次,时间复杂度为O(N3)

代码如下:

int 最大子序和算法1(const int a[],int N) {//不修改加上const是好习惯    //无脑循环    int 单次之和=0,最大和=0;    for (int i = 0; i < N; i++) {        for (int j = i; j < N; j++) {            单次之和 = 0;            for (int k = i; k <= j; k++)                单次之和 += a[k];            if (单次之和 > 最大和)最大和 = 单次之和;        }    }    return 最大和;}

测试代码为:

     int 一个数组[] = { -3,-4,6,-3,6,-9,-10 };    cout << "{ -3,-4,6,-3,6,-9,-10 }最大子序列之和为:" << 最大子序和算法1(一个数组,7) << endl;

2.时间复杂度为O(N2),在上面方法的基础之上把数组求和的过程融合到第二级遍历之上了,故而节约了不少计算资源。
代码如下:

int 最大子序和算法2(const int a[], int N) {    //循环两次O(N2)    int 单次之和 = 0, 最大和 = 0;    for (int i = 0; i < N; i++) {        单次之和 = 0;        for (int j = i; j < N; j++) {            单次之和 += a[j];            if (单次之和 > 最大和)最大和 = 单次之和;        }    }    return 最大和;}

测试代码为:

    cout << "{ -3,-4,6,-3,6,-9,-10 }最大子序列之和为:" << 最大子序和算法2(一个数组,7) << endl;

3.时间复杂度为O(NlogN)的递归算法。
主要用到了二分法+递归
一般来说用到了二分法就有logN的时间复杂度……(二分法用得比较多,其他的也可以是这种复杂度。。。)
代码实现如下:

int 最大子序和算法3(const int a[], int 左,int 右) {    //递归O(NlogN),二分了大概就是logN了。。。    int 左半最大和=0, 右半最大和=0;    int 中间左半最大=0, 中间右半最大=0;    int 中间左半之和=0, 中间右半之和=0;    int 中间, i;    //下面是为了处理到最中间的情况    if (左 == 右) {        if (a[左] > 0)return a[左];        else return 0;    }    中间 = (左 + 右) / 2;//整数的除法,有截断    左半最大和 = 最大子序和算法3(a, 左, 中间);    右半最大和 = 最大子序和算法3(a, 左, 中间);//迭代    //用来计算中间一段最大    for (i = 中间; i >= 左; i--) {        中间左半之和 += a[i];        if (中间左半之和 > 中间左半最大)中间左半最大 = 中间左半之和;    }    for (i = 中间+1; i <= 右; i++) {        中间右半之和 += a[i];        if (中间右半之和 > 中间右半最大)中间右半最大 = 中间右半之和;    }    //比较左右以及中间三者最大值返回    int 最大值;    (左半最大和 > 右半最大和) ? 最大值 = 左半最大和 : 最大值 = 右半最大和;    (最大值 < (中间右半最大 + 中间左半最大)) ? 最大值 = 中间右半最大 + 中间左半最大 : 最大值 = 最大值;    return 最大值;}

测试代码为:

    cout << "{ -3,-4,6,-3,6,-9,-10 }最大子序列之和为:" << 最大子序和算法3(一个数组, 0,6) << endl;

4.时间复杂度为O(N)的算法。
//厉害了,其实和上述的中间左半右半一样……智商碾压
在每加一次的地方就比较一下避免了所有加完在比较少写了一个循环
代码实现如下(好短,刚刚都是智障么,囧)
同时也利用了如果某一段加起来小于0肯定不是最大子串的特点(还是需要一点智商的,233)

int 最大子序和算法4(const int a[], int N) {//循环一次O(N)    //在每加一次的地方就比较一下避免了所有加完在比较少写了一个循环    //同时也利用了如果某一段加起来小于0肯定不是最大子串的特点(还是需要一点智商的,233)    int 单次之和 = 0, 最大和 = 0;    for (int j = 0; j < N; j++) {        单次之和 += a[j];        if (单次之和 >= 最大和)最大和 = 单次之和;        else if(单次之和<0)            单次之和 = 0;    }    return 最大和;}

测试代码如下:

    cout << "{ -3,-4,6,-3,6,-9,-10 }最大子序列之和为:" << 最大子序和算法4(一个数组, 7) << endl;

运行中的对数如果一个算法用常数时间(O(1))把问题大小削减为一部分(通常为1/2),那么这个算法就是O(logN)。如果把问题减少为一个常数,那么就是O(N)的。
对分查找:核心思想就是分为两半快速抛却另一半,时间复杂度就变为O(logN)
例如在一个由小到大排列的数组里查找某个特定的数字:
代码实现如下

int 寻找数字(const int a[], int 数字, int N) {    int low=0, mid, high=N-1;    while (low <= high) {        mid = (high + low) / 2;        if (a[mid] < 数字)low = mid+1;        else if (a[mid] > 数字)high = mid - 1;        else return mid;    }    return -1;//没找到}

测试代码如下:

    int 另一个数组[] = { 1,2,3,4,5,7,10 };    cout << "{ 1,2,3,4,5,7,10 }中数字7的下标为:" << 寻找数字(另一个数组,7, 7) << endl;

欧几里德算法:计算最大公约数,时间复杂度没上面那个明显,不过也是可以证明为O(logN)
实现代码如下:

int 最大公约数(unsigned int M, unsigned int N) {//这个的时间复杂度也是O(logN)    //其实也相当于迭代了    unsigned int Rem;    while (N > 0) {        Rem = M%N;        M = N;        N = Rem;    }    return M;}

幂运算:一种高效的运算方法还是得二分。

初步的概念以及简单实现先到这里啦~~抽空再接着往下看算法~

祝算法学习愉快XD
这里写图片描述

最后附上可运行的所有代码:

#include<iostream>using namespace std;int 第一个计算总和简单的例子(int N);int 最大子序和算法1(const int a[], int N);//无脑循环三次int 最大子序和算法2(const int a[], int N);//循环两次O(N2)int 最大子序和算法3(const int a[],  int 左, int 右);//递归O(NlogN)int 最大子序和算法4(const int a[], int N);//循环一次O(N)int 寻找数字(const int a[], int 数字, int N);int 最大公约数(unsigned int M, unsigned int N);int main() {    int 一个数组[] = { -3,-4,6,-3,6,-9,-10 };    cout << "计算的总和为:" << 第一个计算总和简单的例子(100) << endl;    cout << "{ -3,-4,6,-3,6,-9,-10 }最大子序列之和为:" << 最大子序和算法1(一个数组, 7) << endl;    cout << "{ -3,-4,6,-3,6,-9,-10 }最大子序列之和为:" << 最大子序和算法2(一个数组, 7) << endl;    cout << "{ -3,-4,6,-3,6,-9,-10 }最大子序列之和为:" << 最大子序和算法3(一个数组, 0,6) << endl;    cout << "{ -3,-4,6,-3,6,-9,-10 }最大子序列之和为:" << 最大子序和算法4(一个数组, 7) << endl;    int 另一个数组[] = { 1,2,3,4,5,7,10 };    cout << "{ 1,2,3,4,5,7,10 }中数字7的下标为:" << 寻找数字(另一个数组,7, 7) << endl;    cout << "111231232和234的最大公约数为:" << 最大公约数(111231232, 234)<<endl;    system("pause");}int 第一个计算总和简单的例子(int N) {    int 总和 = 0;    for (int i = 1; i <= N; i++)总和 += i*i*i;    return 总和;}int 最大子序和算法1(const int a[], int N) {//不修改加上const是好习惯                                    //无脑循环    int 单次之和 = 0, 最大和 = 0;    for (int i = 0; i < N; i++) {        for (int j = i; j < N; j++) {            单次之和 = 0;            for (int k = i; k <= j; k++)                单次之和 += a[k];            if (单次之和 > 最大和)最大和 = 单次之和;        }    }    return 最大和;}int 最大子序和算法2(const int a[], int N) {    //循环两次O(N2)    int 单次之和 = 0, 最大和 = 0;    for (int i = 0; i < N; i++) {        单次之和 = 0;        for (int j = i; j < N; j++) {            单次之和 += a[j];            if (单次之和 > 最大和)最大和 = 单次之和;        }    }    return 最大和;}int 最大子序和算法3(const int a[], int 左,int 右) {    //递归O(NlogN),二分了大概就是logN了。。。    int 左半最大和=0, 右半最大和=0;    int 中间左半最大=0, 中间右半最大=0;    int 中间左半之和=0, 中间右半之和=0;    int 中间, i;    //下面是为了处理到最中间的情况    if (左 == 右) {        if (a[左] > 0)return a[左];        else return 0;    }    中间 = (左 + 右) / 2;//整数的除法,有截断    左半最大和 = 最大子序和算法3(a, 左, 中间);    右半最大和 = 最大子序和算法3(a, 左, 中间);//迭代    //用来计算中间一段最大    for (i = 中间; i >= 左; i--) {        中间左半之和 += a[i];        if (中间左半之和 > 中间左半最大)中间左半最大 = 中间左半之和;    }    for (i = 中间+1; i <= 右; i++) {        中间右半之和 += a[i];        if (中间右半之和 > 中间右半最大)中间右半最大 = 中间右半之和;    }    //比较左右以及中间三者最大值返回    int 最大值;    (左半最大和 > 右半最大和) ? 最大值 = 左半最大和 : 最大值 = 右半最大和;    (最大值 < (中间右半最大 + 中间左半最大)) ? 最大值 = 中间右半最大 + 中间左半最大 : 最大值 = 最大值;    return 最大值;}int 最大子序和算法4(const int a[], int N) {//循环一次O(N)    //在每加一次的地方就比较一下避免了所有加完在比较少写了一个循环    //同时也利用了如果某一段加起来小于0肯定不是最大子串的特点(还是需要一点智商的,233)    int 单次之和 = 0, 最大和 = 0;    for (int j = 0; j < N; j++) {        单次之和 += a[j];        if (单次之和 >= 最大和)最大和 = 单次之和;        else if(单次之和<0)            单次之和 = 0;    }    return 最大和;}int 寻找数字(const int a[], int 数字, int N) {    int low=0, mid, high=N-1;    while (low <= high) {        mid = (high + low) / 2;        if (a[mid] < 数字)low = mid+1;        else if (a[mid] > 数字)high = mid - 1;        else return mid;    }    return -1;//没找到}int 最大公约数(unsigned int M, unsigned int N) {//这个的时间复杂度也是O(logN)    //其实也相当于迭代了    unsigned int Rem;    while (N > 0) {        Rem = M%N;        M = N;        N = Rem;    }    return M;}
1 0
原创粉丝点击