【读书笔记】- 《算法笔记》

【摘出其中有用的片段,便于随时查看!】【未完待续

进度:
完成章节(1,3,7,8,9)
第二章还剩指针 & 引用;
第六章剩 最后一节
第八章只有一点BFS,加入一章吧!
第十章 - 图 (遍历完成)


目录:

      • 第一章、本书介绍
      • 第二章、C/C++快速入门
          • 基本数据类型
          • 运算符
          • 输入输出
          • 数组
          • 字符串相关
          • 指针 & 引用
          • | 空间分配 (malloc & new)
          • 结构体
          • 复杂度:
          • 黑盒测试:
          • 补充知识点:(math函数、浮点数比较)
      • 第六章 C++标准模板库(STL)介绍
      • 第七章 数据结构专题
          • 静态链表
      • 第九章 数和二叉树
      • 第十章 图


第一章、本书介绍

书本内容

第4章对常用的基本算法思想进行介绍,非常重要,建议读者多花一些时间仔细思考和训练;

推荐使用方法:阅读一节本书内容,然后做一节习题集对应小节的题目!
推荐使用的编译器: Dev-C++、C-Free、Code::Blocks,而VS系列是较为厚重的编译器,不建议(博主用的VS Code,感觉很漂亮!而且对于Python和前端的支持很好啊!)

| 如何高效做题

  • 按照算法专题进行集中性的题目训练时算法学习的较好方法,因为这可以一次性地对某个算法有一个较为深入且细致的训练。
  • 如果做一道题是暂时没有想法,那么可以先放着,跳过去做其他题目,过一段时间再回来重新做或许就柳暗花明了。【你可以设置一个未解决题目列表,每次有题目暂时不会就扔到队列里,然后隔三差五取出来里面的题目再想一下,想不出来就再扔到队列里;当然如果题目本身较难,做了很久也没有想法,也可以看看题解,知道做法之后再自己独立写出代码】
  • 在做题时要适当总结相似题目的解题方法,这也是进行专题训练时可以顺带完成的事,可起到事半功倍的效果。

第二章、C/C++快速入门

基本数据类型
  1. 变量定义: 变量名第一个字符必须是字母或下划线,除第一个字符外的其他字符必须是字母、数字和下划线。
  2. 符号常量 & const常量:

    #define 标识符 常量
    const 数据类型 变量名 = 常量;

    两种方法都可用,推荐const写法!

    题外话:
    define 除了可以定义常量外,还可定义任何语句或片段

    //其中的括号必须加,因为宏定义是直接将对应部分替换,然后进行编译和运行的
    
    #define ADD(a, b) ((a) + (b))
    
    int main() {
        int num1 = 3, num2 = 5;
        printf("%d", ADD(num1, num2));
        return 0; 
    }
    
  3. 数据类型

类型 取值范围 大致范围
int -2^31 ~ 2^31 - 1 -2 x 10^9 ~ 2 x 10^9
long long -2^63 ~ 2^63 - 1 -9 x 10^18 ~ 9 x 10^18
float -2^128 ~ 2^128 实际精度 6 ~ 7位
double -2^1024 ~ 2^1024 实际精度 15 ~ 16位
char -128 ~ 127 -128 ~ 127
bool 0(false) or 1(true)

int:

  • 占32bit(即32位),也即4Byte(4字节),取值范围-2^31 ~ 2^31 - 1,可记住绝对值在10^9范围以内(或说32位)的整数都可以定义为int型;

long long:

  • 如果是10^18以内或者说64位整数,就要用long long存放

浮点型:(对于浮点型来说,尽量都用double来存储)

  • float: 有效精度6 - 7位
  • double:有效精度 15 -16位 (scanf%lf, printf%f)

字符:

  • \n 代表 换行
  • \0代表 空字符NULL,其ASCII码为0,注意\0不是空格

bool:

  • bool在C++中可直接使用,但在C语言中必须添加stdbool.h头文件才可以使用。
  • 整型变量在赋值给bool变量是会自动转换为true(非零)或 false(零),对于计算机来说,true和false在存储时分别为1和0,因此使用%d输出bool变量时,true和false会输出1和0
运算符
  1. 取模运算符的优先级和除法运算符相同,
  2. 位运算符的优先级没有算术运算符高
  3. 位运算符:

    无穷大数INF可以设置成(1<<31) - 1(必须加括号,因为 位运算符的优先级没有算术运算符高),但一般更常用的是2^30 - 1,这样可以避免相加溢出,

    const int INF = (1 << 30) - 1
    const int INF = 0x3fffffff //二进制表示,两者等价

    六种位运算符整理

    运算符 含义 语法 效果
    << 左移 a << x 按二进制位 左移 x 位
    .>> 右移 a >> x
    & 位与 a & b 按二进制对齐,按位进行与运算
    I 位或 a I b
    ^ 位异或 a ^ b 按二进制对齐,按位进行异或
    ~ 位取反 ~a
输入输出
  • scanf
    • 不要在同时在一个程序中使用cout和printf,有时候会出问题。
    • 标准化输入: scanf("%d:%d:%d", &hh, &&mm, &&ss);
    • 结束标志:%d & %s (空格或换行);%c 可以读入空格或换行
      因此,混合读入%d%c时需要加空格,即:
int n;
char c;
scanf("%d%c", &n, &c); //会把空格读入,产生错误
scanf("%d %c", &n, &c); //能正确读入
  • printf
    • 如果想要输出%/,需要在前面再加一个%/
    • %md : 可使不足m位的int变量以m位进行右对齐输出,其中高位用空格补齐,若本身超过m位,则保持原样
    • %0md不同在于 变量不足m位时,将在前面补0而非空格
    • %.mf:可让浮点数保留m位小数输出(四舍六入五成双)
  • getchar & putchar

    输入 或 输出 单个字符(包括空格、换行、Tab)

  • cin & cout
    • 输入一整行:getline()
//读入char型数组中:
char str[100];
cin.getline(str, 100);

//读入string容器
string str;
getline(cin, str);

// 输出换行,以下两种都可以
cout << "\n" << endl;

数组
  • 允许大小
    如果数组大小较大 (大概10 ^ 6 级别),则需要将其定义在主函数外面,否则会使程序异常退出;原因是函数内部申请的局部变量来自系统栈,允许申请的空间较小;而函数外部申请的全局变量来自静态存储区,允许申请的空间较大。

  • 统一赋值
    memset(数组名, 值, sizeof(数组名);
    需要在开头添加头文件string.h且只建议初学者使用memset赋值0-1,如果要对数组赋其他数字,那么请使用fill()函数(但memset的执行速度更快)
    注: 对二维数组或多为数组的赋值方法也是一样的(仍然只写数组名),不需要该改变任何东西

  • 字符数组
    可以通过直接赋值字符串来初始化(仅限于初始化,程序其他位置不允许这样直接赋赋值整个字符串)
    eg: char str[15] = "Good Story!";

字符串相关
  • gets() & puts()

    gets:输入一行字符(注:gets识别换行符\n作为结束)【因此使用scanf后,使用gets需要先用getchar()吸收换行符】

    puts:用来输出一行字符串,并紧跟一个换行

  • 字符数组的存储方式

    • 在一位字符数组(或 二维数组的第二维)的末尾都有一个空字符\0,以表示存储的字符串的结尾;
    • 空字符\0在使用getsscanf输入是会自动添加在输入的字符串后面,并占用一个字符位,而putsprintf就是通过识别\0 作为字符串的结尾来输出的;
    • 特别提醒1: 结束符\0 的ASCII码为0 ,占用一个字符位,因此开字符数组时其长度必须要比实际长度至少多1 ;【\0 和空格不是不是用一个东西,空格的ASCII码是32,切勿混淆】
    • 特别提醒2: int 型数组的末尾不需要加\0,只有char 型数组需要;
    • 特别提醒3: 如果不是使用scanf 函数的%s 格式或gets 函数输入字符串(例如使用getchar),请一定在输入的字符串后加入’\0‘,否则putsprintf 会因为无法识别字符串末尾而输出一大堆乱码。
char str[15];
for (int i = 0; i < 3; i++) {
    str[i] = getchar();
}
puts(str);

//输入: T^T
//输出: T^T腫?w?@
  • 头文件string.h 下的常用函数(字符数组)

    • strlen(str):得到字符数组中第一个\0 前的字符的个数
    • strcmp(str1, str2):比较字典大小
    • strcpy(str1, str2):把字符数组str2 复制给str1,包括结束符\0
    • strcat(str1, str2):把字符数组str2 接到 str1 后面
  • sscanf & sprintf

    • sscanf:把字符数组str 中的内容以%d 的格式写到n
    • sprintf:把n%d 的格式写到str 字符数组中
      sscanf 还支持正则表达式,结合正则表达式,很多问题可以迎刃而解】
sscanf(str, "%d", &n);
sprintf(str, %d", n); //参数顺序一样,区别只在于n前是否取地址
int n;
double db;
char str[100] = "2048:3.14,hello", str[200];
sscanf(str, "%d:%.2f,%s", &n, &db, str2);
printf("n = %d, db = %.2f, str2 = %s\n", n, db, str2);  
// 输出: n = 2048, db = 3.14, str2 = hello
int n = 12;
double db = 3.1415; 
char str1[100], str2[100] = "good";
sprintf(str, "%d:%.2f,%s", n, db, str2);
printf("str = %s\n", str); 
//输出: str = 12:3.14,good
指针 & 引用

*
*
*
*
*

| 空间分配 (malloc & new

malloc 函数: C 中,头文件stdlib.h
new 运算符:C++ 中 (推荐使用

  • malloc函数 & free函数

    C中用于申请动态内存的函数,返回类型是申请的同变量类型的指针,申请失败则返回空指针NULL;用法:

int* p = (int*)malloc(sizeof(int));
node* p = (node*)malloc(sizeof(node));

/*  含义:
以需要申请的空间大小sizeof(node)为malloc函数的参数,
这样malloc函数会向内存申请一块大小为sizeof(node)的空间,并返回指向这块空间的指针,
但此时该指针是个位确定的指针void*,因此需要把它强制转换为node*型的指针
*/
  • new运算符 & delete

    new是C++中用来申请动态空间的运算符,
    返回类型同样是申请的同变量类型的指针,但申请失败 会启动C++异常机制处理而不是返回空指针,没有特殊处理的情况下会直接退出程序,用法:

int* p = new int;
node* p = new node;
  • 内存泄露

    指使用mallocnew开辟的内存空间在使用过后没有释放,导致其在程序结束之前始终占据该内存空间,这在大程序中很容易导致内存耗尽

  • 释放内存

    • free函数(对应malloc,成对出现)

      用法free(p) - - P为需要释放的内存空间的指针变量
      效果
      1、释放指针变量P所指向的内存空间
      2、将指针变量P指向空地址NULL

    • delete运算符

      用法和效果均与free相同

    注: 一般考试中,分配的空间在程序结束时即被释放,因此即使不释放空间,也不会产生什么影响(内存大小一般足够一道题使用);但从编程习惯上,读者应养成即时释放空间的习惯!
    《算法笔记》为了使算法讲解更侧重于对思路的讲解,故在代码中没有释放空间

结构体
  • 初始化

    当然可以通过逐项赋值,但是当结构体内部变量较多时,不太方便,这时建议使用构造函数进行赋值。

    结构体内部有一个默认的构造函数,但不可见;由于这个构造函数的存在,我们才可以不进行初始化就定义结构体变量,因为它没有让用户提供任何初始化参数。

struct student {
    int id;
    char gender;

    //用以不初始化就定义结构体变量(否则不能不初始化)
    student() {}

    //只初始化gender属性
    student(char _gender) {
        gender = _gender;
    }

    //同时初始化id和gender
    student(int _id, char _gender) {
        id = _id;
        gender = _gender;
    }

};

注意:自己重新定义了构造函数,则不能不经过初始化就定义结构体变量,即默认的结构体被覆盖了。可以手动把原有的默认构造函数加上,这样就既能不初始化就定义结构体变量,又能享受初始化带来的便利。

  • 结构体 的优先级
    • 重载运算符(两种)
/*写在结构体内*/

    //价格高的优先级高( 正好与排序函数sort中的cmp效果相反 )
    struct fruit {
        string name;
        int price;
        friend bool operator < (fruit f1, fruit f2) {
            return f1.price < f2.price;     
        }
    };

    //之后在优先队列中就可以直接定义使用了,其内部已满足价格高的优先级高
    priority_queue q;

-----------------------------------------------------
/*写在结构体外*/
    struct fruit {
        string name;
        int price;
    }f1, f2, f3;

    //把小于号改成一对小括号,并将重载的函数用struct包装起来
    struct cmp {
        bool operator () (fruit f1, fruit f2) {
            return f1.price > f2.price;
        }
    };

    //定义时也有变化,把greater<>部分 换成 cmp
    priority_queuevector, cmp > q;

    /* 注意:
    /* 重载大于号会编译错误,因为从数学上来说只需要重载小于号,
    /* 即 f1>f2 等价于 f2

结构体内的数据较为庞大(例如出现字符串或数组),建议使用引用来提高效率,此时比较类的参数中需要加上 const&

friend bool operator < (const fruit &f1, const fruit &f2) {
    return f1.price > f2.price;
}

bool operator () (const fruit &f1, const fruit &f2) {
    return f1.price > f2.price;
}
复杂度:

对于一般的OJ系统来说,一秒能承受的运算次数大概是10^7 ~ 10^8,因此,O(n^2) 的算法当n 的规模为1000时是可以承受的,而100000 时则不可承受。

黑盒测试:
  • PAT为单点测试

  • 多点测试:

    即需程序一次运行所有数据,故要程序可反复执行代码的核心部分。

//scanf()函数的返回值是其成功读入的参数个数,
//而读取失败时会返回-1(C语言中EOF表示-1)
while(scanf("%d", &n) != EOF){ 
    ……
}
//若为输入为字符串,也可用gets()判断
while(scanf("%s", str) != EOF)
while(gets(str) != NULL) 
补充知识点:(math函数、浮点数比较)
  • 对于main()函数来说,返回0的意义在于告知系统程序正常结束。

  • 常用 math 函数

    fabs(double x) : 取绝对值
    floor(double x) ceil(double) : 向下向上取整
    pow(double r, double p): 返回 r ^ p,要求 r 和 p 都是double型
    sqrt(double):double型变量的算术平方根
    round(double x)0:四舍五入
    log(double x):ln(x)
    sin() cos() tan() asin() acos() atan():三角函数

  • 浮点数的比较

    如果没有经过容易损失精度的运算,就不需考虑误差,可直接比较;但当有这种操作时,精度的损失就不可忽视,需要引入极小数eps对误差进行修正;

    eps一般取10^-8,不会导致误判或漏判

    判等 a == b:如果a落在[b - eps, b + eps]中,就应当判等

    判大于等于 a >= b:在大于的基础上,把误差从eps变为 -eps,包含进了等于的情况

const double eps = 1e-8;

//为了使用方便,把比较操作写成宏定义形式
#define Equ(a, b) ((fabs((a) - (b))) < (eps)) // ==
#define More(a, b) (((a) - (b)) > (eps))      // >
#define Less(a, b) (((a) - (b)) < (-eps))     // <
#define MoreEqu(a, b) (((a) - (b)) > (-eps))  // >=
#define LessEqu(a, b) (((a) - (b)) < (eps))   // <=

补充说明:

1、由于精度问题,在经过大量运算后,可能一个变量中存储的0是很小的负数,这时如果对其开根号sqrt(),就会出错。同样的问题还出现在asin(x)x存放+1acos(x)x存放-1时。这种情况需要eps保证其值在定义域内。

2、在某些由编译环境产生的原因下,本应为0.00的变量在输出是会变成-0.00。这个问题是编译环境自身的bug,只能把结果存放在字符串中,然后与-0.00进行比较,如果比对成功,加上eps来修正为0.00

  • 圆周率π

    写成常量acos(-1.0)即可


第六章 C++标准模板库(STL)介绍

(单独整理在一篇博客里了)


第七章 数据结构专题

静态链表

第九章 数和二叉树


第十章 图


*
*
*
* 整理自《算法笔记》!!

你可能感兴趣的:(读书笔记)