第一章 数据结构、算法与STL

第一章 数据结构、算法与STL学习笔记

本章主要基本介绍数据结构、算法与STL的相关知识。学习后可能会较为模糊,需要在后续的长期学习中不断加深理解。

数据结构

什么是数据结构

定义:在计算机科学中,数据结构是⼀种数据组织、管理和存储的格式。它是相互之间存在⼀种或多种特定关系的数据元素的集合。

翻译:数据结构就是数据的组织形式,研究的是把数据按照何种形式存储在计算机中。

为什么会有数据结构?

  • 随着计算机的发展和应用范围的拓展,计算机需要处理的数据量越来越大,类型越来越多,关系也越来越复杂。
  • 数据结构的出现是为了解决数据存储和处理的问题。
  • 数据结构是计算机科学的基础,是计算机科学的重要组成部分。

对数据结构更深层次的理解:数据结构研究的是数据本⾝的特性、数据与数据之间的关系,以及如何在计算机中存储数据,从⽽⾼效的处理这些数据。

数据结构的三要素

  1. 逻辑结构:各元素之间的逻辑关系。

    1. 集合结构:数据元素同属于⼀个集合,数据元素之间没有任何关系。
    2. 线性结构:数据元素之间存在⼀对⼀的关系,如按字母顺序排列的通讯录。
    3. 树形结构:数据元素之间存在⼀对多的关系,如公司架构表。
    4. 图结构:数据元素之间存在多对多的关系,如人物关系表。
  2. 存储结构:又称作物理结构,即在计算机中的存储方式,包括顺序存储和链式存储。

    1. 顺序存储:将数据元素存储在⼀块连续的存储空间中,如数组。
    2. 链式存储:将数据元素存储在⼀个⼀个的节点中,节点之间通过指针来存储元素地址从而进行连接,如链表。
  3. 数据的运算:数据的运算(针对数据的各种操作),包括数据结构的实现,以及基于数据结构上的各种操作。

    翻译:在了解元素之间的关系与存储关系后,我们需要对数据进行各种操作。

    操作包括:
    创建数据结构;
    增加数据;
    删除数据;
    查找数据;
    修改数据;
    排序数据;
    输出数据;

    总结包括:创+增删查改+其他

    例如:在设计学生信息管理系统时,我们按照学号排序,此时学生之间存在先后关系,我们可以确定:
    逻辑结构:线性结构;
    存储结构:顺序存储;

    那么就有以下操作:

    1. 创建信息管理系统
    2. 增加学生信息
    3. 删除学生信息
    4. 查找学生信息
    5. 修改学生信息
    6. 按照成绩排序

    这些就是数据运算。

算法

什么是算法?

算法(Algorithm)是指对于解题⽅案的准确⽽完整的描述,是⼀系列解决问题的清晰指令,算法代表着⽤系统的⽅法描述解决问题的策略机制。也就是说,能够对⼀定规范的输⼊,在有限时间内获得所要求的输出。

  • 算法是可以没有输⼊的,⼀定要有输出的。没有输出的算法是没有意义的。

简单来说算法就是⼀系列的步骤,⽤来将输⼊数据转化成输出结果。

因此,在以往C语言和C++学习阶段的每一个练习题、写的程序都可以称为一个算法

算法好坏的度量

算法设计好后,根据其原理和问题规模,算法基本语句的执行次数和需求资源个数基本也就确定了。

例如以下代码:

const int N = 10000000;
int a[N];

int sum(int n) {
    for (int i = 0; i < N; i++) {
        a[i] = i;
    }
    int ret = 0;
    for (int i = 0; i < N; i++) {
        ret += a[i];
   
   }
   return ret; 
}
int sum(int n) {
    int ret = 0;
    for (int i = 0; i < N; i++) {
        ret += i;
    }  
    return ret;
}

以上两段代码都是计算1到N的和,但是第一段代码需要开辟一个大小为N的数组空间,而第二段代码不需要,直接进行求和。

以上算法执行所需资源与问题规模N有关,因此可以根据算法执行过程中对空间的消耗来衡量算法的好坏,即空间复杂度

基于第二种算法,我们再来看第三种算法:

int sum(int n) {
    return (1 + n) * n / 2;
}

这种算法直接利用求和公式进行计算,与第二种算法相同,需求资源与N无关,但同时执行次数与问题规模N有关,因此可以根据算法执行过程中对时间的消耗来衡量算法的好坏,即时间复杂度

综上,衡量算法好坏的标准包括对于时间和空间的消耗,即时间复杂度空间复杂度

时间复杂度

在计算机科学中,算法的时间复杂度是⼀个函数式T(N),它定量描述了该算法的运⾏时间。T(N)函数式计算了程序中语句的执⾏次数。

例:计算以下函数中++count的执行次数:

void func(int n) {
    int count = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            count++;                    /*n*n次*/
        }
    }
    for (int k = 0; k < 2 * n; k++) {
        count++;                        /*2*n次*/
    } 
    int m = 10;
    while (m--) {
        count++;                        /*10次*/
    }
}

因此,以上函数中++count的执行次数:T(N) = n*n + 2*n + 10

时间复杂度的大O表示法

显然,在以上计算中,随着N的增大,对结果影响最大的一项是n*n,2*n和10对结果影响较小,T(N)的增长趋势与n*n的增长趋势相同。在计算时间复杂度时,一般会忽略对于结果影响较小的项,这种表示时间复杂度的方式即为大O渐进时间复杂度,即、粗略的进行估算。对于以上的计算其时间复杂度可以用T(N) = O(n*n)表示。

具体规则:

  1. 用常数1取代运行时间中的所有加法常数。
  2. 在修改后的运行次数函数中,只保留最高阶项。
  3. 如果最高阶项存在且不是1,则去除与这个项相乘的常数。

根据下⾯各个算法时间复杂度的函数式,写出它⼤O表⽰下的时间复杂度。

  1. T(N) = N + 100T(N) = O(N)
  2. T(N) = 2N + 10T(N) = O(N)
  3. T(N) = 3N^2 + 2N + 10T(N) = O(N^2)
  4. T(N) = 4950,T(N) = O(1)
最优、平均和最差时间复杂度

例:在n个整形元素数组中,检测x是否存在,若存在返回其在数组中的下标,否则返回-1。

int findX(int *a, int n, int x) {
    for (int i = 0; i < n; i++) {
        if (a[i] == x) return i;
    }
    return -1;
}
  • 最优时间复杂度:在最理想的情况下,算法的时间复杂度,例如在以上算法中x为数组第一项。
  • 平均时间复杂度:算法在所有可能情况下的时间复杂度的平均值,例如在以上算法中所有情况之和除以总情况。
  • 最差时间复杂度:在最坏的情况下,算法的时间复杂度,例如在以上算法中x为数组最后一项。

则以上算法的时间复杂度为:

  • 最优时间复杂度:T(N) = O(1)
  • 平均时间复杂度:T(N) = O(N)
  • 最差时间复杂度:T(N) = O(N)

但是,⽆论是在竞赛还是⼯程中,算法的时间复杂度⼀般为最差情况。因为最差情况是⼈对⼀件事情所能承受的底线,因此findx算法的时间复杂度为O(n)。

时间复杂度计算案例

案例一:

void func(int n) {
    int count = 0;
    for (int k = 0; k < 2 * n; k++) {
        count++;
    }
    int m = 10;
    while (m--) {
        count++;
    }
    printf("%d\n", count);
}

基本语句++count的执行次数:T(N) = 2*n + 10,时间复杂度:T(N) = O(n)

案例二:

void func(int n) {
    int count = 0;
    for (int k = 0; k < 100; k++) {
        count++;
    }
    printf("%d\n", count); 
}

基本语句++count的执行次数:T(N) = 100,时间复杂度:T(N) = O(1)

案例三:

void func(int m,int n) {
    for (int i = 0; i < n; i++) {
      printf("hello\n"); 
    }
    for (int j = 0; j < m; j++) {
        printf("world\n"); 
    }
}

基本语句printf("hello\n")printf("world\n")的执行次数:T(N) = n + m,时间复杂度:T(N) = O(n + m),也可以表示为O(max(m,n))

案例四:

void func(int m,int n){
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            printf("%d",i);
        }
        printf("\n");
    }
}

基本语句printf("%d",i)的执行次数:T(N) = m*n,时间复杂度:T(N) = O(m*n)

案例五:

void func(int n) {
    int cnt = 1;
    while (cnt < n) {
        cnt = cnt * 2;
    } 
}

假设执行次数为x,则2^x = n,即x = log2(n),时间复杂度:T(N) = O(log2(n))

案例六:

long long fac(int n) {
    if (n == 1) return 1;
    return func(n - 1) + n;
}

递归算法时间复杂度的粗略求解⽅式为单次递归时间*总的递归次数。

fac(5)需要递归6次,则fac(n)需要递归n+1次,时间复杂度:T(1*(N+1)) = O(n)

空间复杂度

相较于时间复杂度,空间复杂度的计算更为简单,它是对一个算法在运行过程中临时占用存储空间大小的量度,但是在算法竞赛中,大多数题目所给的内存限制都是十分宽裕,因此空间复杂度并不太受关注,这并不表明可以不考虑空间复杂度,一旦超出给定限制,就会被系统判定为内存超限 -MLE。

例:冒泡排序

#include <iostream>
using namespace std;

const int N = 20;
int arr[N];

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " "; 
    }
    return 0;
}

空间复杂度:需要⼀个和问题规模⼀致的数组来存数据。因此空间复杂度为O(N)。

例:递归求阶乘

#include <iostream>
using namespace std;

long long fac(int n) {
    if (n == 1) return 1;
    return fac(n - 1) * n;
}

递归算法空间复杂度求解⽅式,单次递归空间复杂度*递归次数。

fac(5)需要递归6次,则fac(n)需要递归n+1次,空间复杂度:S(1*(n+1)) = O(n)

常⻅复杂度的增⻓对⽐

O(1)<O(logN)<O(N)<O(NlogN)<O(N)<O(N^2)<O(N^3)<O(N^3)<O(n!)

算法竞赛中的时间限制和空间限制

  1. 算法竞赛中,C++通常设定1到2秒的时间限制,即107到108次的运算。
  2. 通常设定128MB或256MB的空间限制,可以开一个310^7的int数组,或者50005000的二位int数组。

STL

STL是C++标准库中的⼀部分,包括容器、算法和迭代器。

STL的出现是为了解决数据结构和算法的快速开发问题,是C++的⼀部分,因此被称为C++的准标准库。

每⼀个版本的C++标准不仅规定了C++的语法、语⾔特性,还规定了⼀套C++内置库的实现规范,这个库便是C++标准库。C++标准库中包含⼤量常⽤代码的实现,如输⼊输出、基本数据结构、内存管理、多线程⽀持等。

我们可以这样简单的理解,C++给我们提供了⼀⼤堆的代码,这些代码⾥⾯包含特别多已经实现好的数据结构和算法。因此,在做题的时候,如果涉及的数据结构和算法C++标准库已经帮我们实现好了,那么就可以直接使⽤,避免重复造轮⼦。

什么是STL?

STL是Standard Template Library的简称,即标准模板库。

STL是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值