数据结构系列学习(一) - An Introduction to Data Structure

目录

引言:

学习:

概念:

什么是数据?

什么是数据元素?

什么是数据项?

什么是数据对象?

什么是数据结构(data structure)?

什么是逻辑结构?

什么是物理结构(存储结构)?

逻辑结构分类:

集合:

线性结构(线性表):

线性表:

线性表的特点:

线性表按照数据的存储方式分为两种:

树形结构:​​​​​​​

图状结构(网状结构):

时间复杂度(Time Complexity):

常数阶O(1):

线性阶O(n):

平方阶O(n^2):

对数阶(Ologn):

总结:

参考资料:


引言:

在之前C语言的学习过程中,分别对顺序结构设计、选择程序设计、循环结构设计、数组、指针、结构体进行了系统性的学习,至此,C语言的学习已经基本结束。

C语言学习目录及经典问题解析:

C语言 - 四种方法解决杨辉三角问题

 C语言-多层for循环详解

 C语言-8月5日-结构体和变量

C语言-7月31日-指针的总结以及typedef关键字

C语言-8月1日-递归与动态内存管理

C语言-7月21日-指针的深入

C语言-7月19日-指针的学习

C语言-7月18日-二维数组的学习

昨晚进行了数据结构系统性学习的第一节课,数据结构本身就是计算机学科一门有着举足轻重作用的一门课,之前所写的文章都是数据结构之中一些零散的知识点,例如各种排序算法,但我从来都没有对各式各样的数据结构例如:集合、队列、树、图这些进行一步步深入的探讨,从这篇文章开始,我将对数据结构内容的学习过程在这个系列中的每篇文章进行总结,为的就是在日后要对这些知识点进行复习的时候能迅速回想起来当时的想法。

学习:

对数据结构的学习,必须要知道数据结构中的各个名词的含义,以便于日后在向别人解释程序中的各个部件时,我们能用专业且严谨的术语叙述出来。

概念:

什么是数据?

数据是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。数据不仅仅包括整形、实型等数值类型,还包括字符及声音、图像、视频等非数值类型。

什么是数据元素?

数据元素是组成数据、有一定意义的基本单位,在计算机中通常作为整体处理,也被称为记录。

什么是数据项?

一个数据元素可以由若干个数据项组成,数据项也是数据不可分割的最小单位。

什么是数据对象?

数据对象是性质相同的数据元素的集合,是数据的子集。

什么是数据结构(data structure)

数据结构(data structure)是相互之间存在一种或多种特定关系的数据元素的集合。

数据结构分为逻辑结构和物理结构:

什么是逻辑结构?

数据与数据之间的抽象关系,与物理地址无关。

什么是物理结构(存储结构)?

数据与数据之间,他们在存储器中的存储方式。

下面我们来详细说说数据和数据之间的逻辑结构分类:

逻辑结构分类:

根据严蔚敏的《数据结构(C语言版)》,任何数据元素都不是孤立存在的,而是在它们之间存在某种关系,这种数据元素相互之间的关系称为结构(structure)。根据数据元素之间关系的不同特性,通常有以下四种结构:

集合:

结构中的数据元素之间除了“同属于一个集合”的关系外,别无其他关系。

我们在高中数学必修一中学习过的集合数学问题,就是典型的例子,例如在这里我们给出一个集合C,C中包含的元素有{1,2,3,4,5,6}。

集合图示:

线性结构(线性表):

结构中的数据元素之间存在一个对一个的关系。

计算机中的栈、线性表、队列、一维数组就是典型的例子,例如我在这里给出一个元素个数为5的数组ar,int ar[5] = {1,2,3,4,5}; ar中的五个整形元素就存在一对一的关系,ar中的元素3,上一个元素是2,下一个元素是4 .

线性表:

线性表有且只有一个开始节点,有且只有一个结束节点,并且除了开始节点外,其余节点都有直接前驱,除了结束节点外,其余节点都有直接后继,都可以表示成{a1,a2,a3,a4...,a(n - 1),a(n)};

线性表的特点:

1. 唯一的头

2. 唯一的尾

3.除首位节点外,其余节点只有一个前驱和一个后继。

4. 逻辑关系一对一的:线性表、栈、队列、字符串、一维数组。

线性表按照数据的存储方式分为两种:

顺序表:数据节点之间,逻辑相邻,物理不一定相邻。

链表:数据节点之间,逻辑相邻,物理不一定相邻。

顺序表:将逻辑上相邻的数据元素,存储在物理上也相邻的存储单元中。

线性结构图示:

树形结构:

结构中的数据元素之间存在一个对多个的关系。

现实生活中的族谱就是一个典型的树形结构的例子,父母生下两个孩子,两个孩子又有了各自的家庭;思维导图也是一个很好的树形结构例子。

树形结构图示:

图状结构(网状结构):

结构中的数据存在多个对多个的关系。

在我们平时开车或者去某个地方时经常要看地图,地图就是一个很好的例子,全国的高速公路密集地交错着

图状结构图示:

时间复杂度(Time Complexity):

从算法的效率分析:时间效率,空间效率

时间复杂度(Time Complexity):算法中基本操作执行的次数和问题规模n之间的关系,一般记作T(n) = O (f(n))

在进行算法分析时,语句总的执行次数T(n)时关于问题规模n的函数。进而分析T(n)随着n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n) = O (f(n))。它表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)时问题规模n的某个函数。

一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。

推导大O阶:

step 1:用常数1取代运行时间中的所有加法常数;

step 2:在修改后的运行次数函数中,只保留最高阶项;

step 3:如果最高阶项存在且不是1,则去除与这个项相乘的常数。

这三步下来得到的就是大O阶。 

常数阶O(1):

#include<stdio.h>
int main()
{
    int a = 10,b = 20;//执行一次
    int sum = a + b;//执行一次
    printf("%d\n",sum);//执行一次
    return 0;//正常退出
}

很明显,这是一个顺序结构,其中总共有四个语句,其中return 0是程序的正常退出,所以运行次数函数为:f(n) = 3,根据推导O阶的方法,我们将O中的常数3化为1,并且保留最高阶项,但是这个函数根本没有最高阶项,所以此程序最终的时间复杂度就是:O(1)

同时我们也把O(1)叫常数阶。

线性阶O(n):

#include<stdio.h>
int main()
{
int n,temp;
for(int i=1; i<=n; i++){
    tmp += arr[i];
}
return 0;
}

在这个程序中,我定义了临时变量temp和循环条件 i < n,循环语句从1开始,执行n次。

所以运行次数函数为:f(n) = n,根据推导O阶的方法,第一步我们应该将O中的常数化为1,但我们发现这个运行次数函数里面并没有常数项,所以直接进行第二步,此函数最高阶项为n,我们保留n去除其他的常数项,最终得到的大O阶为:T(n)= O(n)

同时我们也把O(n)叫做线性阶。

平方阶O(n^2):

#include<stdio.h>
int main()
{
int n,temp;
for(int i=1; i<=n; i++){
    for(int j = 1;j <= n;j++){
    tmp += arr[i];    
    }    
}
return 0;
}

在之前的C语言 - 多层for循环详解这篇文章曾经讲过多层for循环的逻辑原理,当我们执行至外层循环条件时,直接穿入下一层的循环变量,再到循环语句,直到将内层循环全部执行完,再跳转至外层循环中,循环变量加1,再次进入下一层循环执行n次。所以运行次数函数为:f(n) = n^2

根据推导大O阶的方法,我们先将O阶中的常数化为1,但此时的O阶中并没有常数,所以跳转至下一步,我们保留最高阶项n^2,现在执行第三步操作,去除与最高项相乘的常数,但是在式子中并没有与n^2相乘的常数项,最终得到的大O阶为:T(n) = O(n^2)

同时,我们也把O(n^2)叫做平方阶。

对数阶(Ologn):

#include<stdio.h>
int main()
{
    int n = 0;
    scanf("%d",&n);
    int x = 1;
    for(int i = 1;i < n;i*=2)
    {
        ++x;
        printf("x = %d,i = %d\n",x,i);
    }
    return 0;
}

例如在这里我给出一个程序,定义整形变量n作为循环条件并由用户决定n值的大小,定义整形值x,在循环中,对x的值进行累加并对x的值进行输出,每执行一次循环,循环变量i的值就变为原来的两倍,例如在程序程序中我输入n的值为10,运行结果为:

我们现在来分析程序的运行步骤:

第一次循环:当i = 1时,我们对x值进行累加,并将x的值进行输出,此时x的值为2;

第二次循环:此时进行循环操作,循环变量i * 2,i的值变为2,再次对x的值进行累加并输出,此时x的值为3;

第三次循环:继续进行循环操作,循环变量i * 2,i的值变为4,再次对x的值进行累加并输出,此时x的值为4;

第四次循环:继续进行循环操作,循环变量i * 2,i的值变为8,再次对x的值进行累加并输出,此时x的值为5;

第五次循环:继续进行循环操作,循环变量i * 2,i的值变为16,16 > 10,循环变量已经不满足循环变量,此时跳出循环。

综上,循环一共执行了四次,直到当i不满足i < n的条件之后跳出了循环,循环一共执行了4次。

我们再对i和n的关系进行分析:

四次循环中的每一次循环分别对应的i值都是:2^0、2^1、2^2、2^3,直到2^4大于n时循环结束,循环条件和循环操作共同决定着循环次数,那么也就是在取整的前提下,程序运行的次数为:

程序运行次数 =  log2^{10}  + 1= 3 + 1(...)

注:这里的...代表未完美接近要求数值的余数。 

我们思考,如果此时n的值变为100,那么同样再取整的前提下,此时程序运行的次数应该是最接近于100的2的6次幂再加一,也就是7次,我们运行程序来验证:

结果是正确的,此时如果我把循环操作从原来的i *= 2(i = i * 2)变为i *= 3(i = i * 3),此时程序运行的次数又将如何表示?很简单,我们只需要将log函数中的底从2换为3即可,也就是:

程序运行次数 =  log 3^{100} + 1 = 4 + 1(...)

注:这里的...代表未完美接近要求数值的余数。 

运行程序进行验证:

结果是正确的。 

那么也就是说,程序运行的次数我们是可以通过数学关系来表示的,且这种数学关系就是对数函数,如果我们使用大O阶的方式来进行表述的话,可以将这类程序的时间复杂度总结为:

T( n )=  O( logn )

 同时,我们也把O( logn )叫做对数阶。

到这里,常见的时间复杂度我们大致上已经分析完了,但是程序是千变万化的,还有很多不常见的时间复杂度的大O阶表示,这里我们给出一个表格,用于时间复杂度的说明:

这个表格中有几种不常见的大O阶,例如指数阶,nlogn阶,这些阶在数据结构和算法中其实都有 非常典型的例子,例如递归形式求斐波那契序列(Fibonnaci)的时间复杂度就是O(2^n),再例如二路归并排序算法(包含最坏情况和最优情况)的时间复杂度就是O(nlogn) 。

总结:

数据结构是大学本科阶段计算机的核心课程之一,这也是我写的数据结构系列的第一篇文章,旨在正式从C语言的学习切入数据结构的学习和介绍数据结构的基本概念,在文章中分别介绍了:数据、数据元素、数据项、数据对象、数据结构、数据结构中的物理结构和逻辑结构、及逻辑结构分类。后续也介绍了时间复杂度的概念,并推导了常见的大O(阶),数据结构的前期介绍已经基本完成,后面我将会对顺序表、单链表、双向链表等典型数据结构作详细介绍并用代码进行实现。

参考资料:

严蔚敏 - 《数据结构(C语言版)》 - 清华大学出版社

程杰 - 《大话数据结构》 - 清华大学出版社

猜你喜欢

转载自blog.csdn.net/weixin_45571585/article/details/127258129