数据结构认识

**             数据结构与算法**

**                  一、数据结构定义:**

数据结构(Data Structure)是数据结构是一种 数据组织、管理和存储(增删查改) 的格式。 它是相互之间存在一种或多种特定关系的数据元素的集合。数据结构往往同高效的 检索算法和索引技术 相关 。

数据结构研究的是数据的逻辑结构和数据的物理结构以及它们之间的相互关系。它包含三个方面的内容:即数据的逻辑结构、数据的存储(物理)结构和数据的操作,只有这三个方面的内容完全相同,才能成为完全相同的数据结构。

没有⼀种单⼀的数据结构对所有解决方案都适用,所以要根据实际情况选择使用什么样的数据结构。如:线性表、树、图、哈希等

**                  二、算法定义:**

算法(Algorithm):就是良好的计算过程,他取⼀个或⼀组的值为输⼊(根据数据结构进行输入的数据的采集),并产⽣出⼀个或⼀组值作为输出。简单来说算法就是⼀系列的计算步骤,⽤来将输⼊数据转化成输出结果。

**                 三、算法好坏的衡量**

3.1 复杂度的 概念

针对一个程序,程序运⾏时需要耗费时间资源和空间(内存)资源,而与程序运行方式密切相关的是算法,其决定了程序如何运行以解决问题,因此衡量⼀个算法的好坏,⼀般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。

时间复杂度主要衡量⼀个算法的运⾏快慢 ,⽽空间复杂度主要衡量⼀个算法运⾏ 所需要的额外空间

在计算机发展的早期,计算机的存储容量很⼩,很贵。所以对空间复杂度很是在乎。但随着计算机⾏业的迅速发展,计算机的存储性能已经达到了很⾼的程度。所以我们如今已经不需要再特别关注⼀个算法的空间复杂度。当今主要靠算法复杂度来评价一个算法的好坏。

3.2大O渐进表示法

⼤O符号(Big O notation):是⽤于描述函数渐进⾏为的数学符号

⼤O阶规则

  1. 时间复杂度函数式T(N)中,只保留最⾼阶项,去掉那些低阶项,因为当N不断变⼤时,低阶项对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
  2. 如果最⾼阶项存在且不是1,则去除这个项⽬的常数系数,因为当N不断变⼤,这个系数对结果影响越来越⼩,当N⽆穷⼤时,就可以忽略不计了。
  3. T(N)中如果没有N相关的项⽬,只有常数项,⽤常数1取代所有加法常数。

3.3时间复杂度

**  3.3.1定义:**

时间复杂度用于衡量算法随输入规模增长时运行时间的变化趋势,常用大O 的渐进 表示法表示为 O(f(n)),其中 n 是输入规模,f(n) 是操作次数的数量级。它反映的是增长趋势而非具体耗时。

常见复杂度从快到慢: O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(2ⁿ) < O(n!)

体悟:如何用时间来体现算法的好坏呢,首先想到的肯定是用具体的时间长短。但我们写不出具体的时间的函数,在有判断条件的情况下会出现n个计算式,所以我们只需要知道处理一个事务,算法好的一方处理速度块即可。

所以,解决判断n个表达式的关系呢?怎样让我们直观感受到算法的优劣呢?怎么样处理硬件差异带来的计算速度差异呢?

在判断的时候:我们分为 不进行无法影响到达程序退出条件的/进行完所有判断的/任意规模的期望时间,对应最好时间复杂度,最坏时间复杂度和平均时间复杂度。

那么情况分完了,怎么直观感受算法优劣呢?我们不妨让参与评判的数据趋近于无限大,这样,算法间的差异会被放大。这种情况下我们用大O渐进法表示算法。

此处参与评判的数据就是人为输入的数据,也只有人为输入的数据能够影响到数据大小,那么,如果有多个参数需要输入,又该怎么表示算法,其实同理,不过是两个大O渐进法,取高阶项即可,同阶则用两个变量(后续均用N替代)表示

最后怎么样忽略硬件的影响呢?我们不妨等价每一条语句,只计算其计算的次数

因此:时间复杂度大白话就是:一个判断情况下一个程序中计算次数最多的指令与数据n的关系,不要自然系数,忽略低阶表达式即是时间复杂度。

**  3.3.2.分类:**

最好时间复杂度:算法最好情况下的时间复杂度

最坏时间复杂度:算法最坏情况下的时间复杂度

平均时间复杂度:算法在所有可能的情况下,按照输入实例以等概率出现时,算法计量的加权平均值(平常可以用数据结构中处于中间的数据进行测试)

通常我们用最坏时间复杂度来评判算法的优劣。

**  3.3.3.计算与举例**

计算下面的最坏时间复杂度:

例一:

复制代码
for (int i = 0; i < n; i++)			//这个for语句执行n+1次(最后结束的时候要执行判断)
{
	for (int j = 0; j < n; j++)			//这个for语句执行n(外面的for进来n次)*(n+1)次
	{
		a[i][j] = 5;						//这个执行n*n次
		for (int k = 0; k < n; k++)			//这个for语句执行n*n*(n+1)次
		{
			c[j][k] = a[i][j] + b[j][i];		//这个语句执行n*n*n次
		}
	}
}

最坏时间的表达式是 T=n+1+n*(n+1)+n*n+n*n*(n+1)+n*n*n。

最坏时间复杂度是O(n^3),舍去了常数因子,在n--->无穷时 比n^3小的含n的式子。总结下来最坏时间复杂度只需要看嵌套最深的式子的计算次数即可。

例二:常数举例

复制代码
for(int i=0;i<1000;i++)		//执行1001次
{
    printf("sdwfa\n");		//执行1000次
}

这种程序时间式子里不含n(数据大小),计算次数可以用确定数字表示的统一为O(1)时间复杂度

总的来说计算时间复杂度只需要计算次数,用次数去计算用于判断结束条件的值与n比较,计算出次数与n的关系即可。

易知其中计算次数最多的是x*=2;执行了log(n/2)次,以2为底,其实在比较两个时间复杂度时,可以用换底公式换掉底,因此说时间复杂度也是O(log n)

例三:多变量情况举例

复制代码
// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
    int count = 0;
    for (int k = 0; k < M; ++ k)
    {
        ++count;
    }
    for (int k = 0; k < N ; ++k)
    {
        ++count;
    }
    printf("%d\n", count);
}

该算法的最坏时间复杂度是O(M/N)

例四:递归的时间复杂度

等于单次递归的时间复杂度*递归次数。

**  3.4空间复杂度**

1.定义:

是对⼀个算法在运⾏过程中因为算法的需要额外临时开辟的空间。

空间复杂度(Space Complexity)是衡量算法占用内存空间随数据量增长趋势的指标。

该复杂度统计范围包括暂存空间与输出空间,暂存空间细分为变量等暂存数据、函数调用的栈帧空间(递归算法因多次调用累积栈帧空间消耗较大)及可忽略的指令空间 。分析时关注最差空间复杂度,需同时考虑最差输入数据与运行峰值内存占用。

函数运⾏时所需要的栈空间(存储参数、局部变量、⼀些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运⾏时候显式申请的额外空间来确定

2.举例:

例一:O(1)(栈帧外,变量空间)

复制代码
// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
    assert(a);
    for (size_t end = n; end > 0; --end)
    {
        int exchange = 0;
        for (size_t i = 1; i < end; ++i)
        {
            if (a[i-1] > a[i])
            {
                Swap(&a[i-1], &a[i]);
                exchange = 1;
            }
        }
        if (exchange == 0)
        break;
    }
}

在这个程序里函数栈帧在编译期间已经确定好了,只需要关注函数在运⾏时额外申请的

空间。BubbleSort额外申请的空间有exchange等有限个局部变量,使⽤了常数个额外空间

因此空间复杂度为 O(1)

例二:递归的空间复杂度(变量空间外,栈帧空间)

复制代码
// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
    if(N == 0)
    return 1;
    return Fac(N-1)*N;
}

递归的空间复杂度=递归次数*单次递归的空间复杂度

Fac递归调⽤了N次,额外开辟了N个函数栈帧,每个栈帧使⽤了常数个空间因此空间复杂度为:O(N)

持续更新😃ING>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>