数据结构-----绪论

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 1、数据结构起源
  • 2、基本概念和术语
    • 3、逻辑结构和物理结构
      • 4、数据类型和抽象数据类型
  • 5、算法和算法分析
    • 6、算法效率的度量方法
  • 7、算法时间复杂度和空间复杂度(重点)
  • 总结


前言

数据结构”是计算机软件相关专业的基础课程,几乎可以说,要想从事编程工作,无论你是否是科班出身,都绕不开这部分知识。


1、数据结构起源

  • 早期人们都把计算机理解为数值计算工具,就是感觉计算机当然是用来计算的,所以计算机解决问题,应该是先从具体问题中抽象出一个适当的数据模型,设计出一个解此数据模型的算法,然后再编写程序,得到—个实际的软件。可现实中,我们更多的不是解决数值计算的问题,而是需要一些更科学有效的手段(比如表、树和图等数据结构)的帮助,才能更好地处理问题。
  • so:数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等的学科
  • 1968年,美国的高德纳(DonaldEKnuth)教授在其所写的《计算机程序设计艺
    术》第—卷《基本算法》中,较系统地阐述了数据的逻辑结构和存储结构及其操作,开
    创了数据结构的课程体系。同年, “数据结构’’作为—门独立的课程,在计算机科学的
    学位课程中开始出现。也就是说,那之后计算机相关专业的学生开始接受“数据结构”
    的“折磨’’—其实应该是享受才对。
    之后,20世纪70年代初,出现了大型程序,软件也开始相对独立,结构程序设计成为程序设计方法学的主要内容,人们越来越重视 “数据结构”,认为程序设计的实质是对确定的问题选择一种好的结构,加上设计一种好的算法。可见,数据结构在程序设计当中占据了重要的地位。

数据结构-----绪论_第1张图片


2、基本概念和术语

下面,将对一些概念和术语赋以确定的含义。
  • 数据data):是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。对计算机科学而言,数据的含义极为广泛,如图像、声音等都可以通过编码而归至于数据的范畴
    我们这里说的数据,其实就是符号,而且这些符号必须具备两个前提:
    可以输入到计算机中、能被计算机程序处理

  • 数据元素data element):是组成数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。

  • 数据项data item):一个数据元素可以由若干个数据项组成。数据项是数据不可分割的最小单位

  • 数据对象data object):是性质相同的数据元素的集合,是数据的一个子集

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

数据结构-----绪论_第2张图片


3、逻辑结构和物理结构

按照视点的不同,我们把数据结构分为逻辑结构和物理结构。
  • 逻辑结构
    逻辑结构:是指数据对象中数据元素之间的相互关系。逻辑结构分为以下四种。
  1. 集合机构
    集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系。
    数据结构-----绪论_第3张图片

  2. 线性结构
    线性结构中的数据元素之间是是一对一的关系

数据结构-----绪论_第4张图片

  1. 树形结构
    树形结构中的数据元素之间存在一种一对多的层次关系
    数据结构-----绪论_第5张图片

  2. 图形结构
    图形结构的数据元素是多对多的关系数据结构-----绪论_第6张图片

  • 物理结构

物理结构是指数据的逻辑结构在计算机中的存储形式。

  1. 顺序存储结构
    顺序存储结构是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。在之前学习的C语言中,数组就是这样的顺序存储结构。

  2. 链式存储结构
    **链式存储结构是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。**数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,这样通过地址就可以找到相关数据元素的位置

  • 数据的逻辑结构和物理结构是密切相关的两个方面,逻辑结构是面向问题的,而物理结构是面向计算机的,其基本的目标就是将数据及其逻辑关系存储到计算机的内存中。任何一个算法的设计取决于逻辑结构,而算法的实现依赖于采用的存储(物理)结构

数据结构-----绪论_第7张图片


4、数据类型和抽象数据类型

  • 数据类型data type)是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。
    在C语言中,按照取值的不同,数据类型可以分为两类:
    数据结构-----绪论_第8张图片
  • 抽象数据类型(Abstract Data Type,简称ADT)
    抽象是指抽取出事物旦有的普遍性的本质。它是抽出问题的特征而忽略非本质的细
    节,是对具体事物的一个概括。抽象是一种思考问题的方式,它隐藏了繁杂的细节,只
    保留实现目标所必需的信息。
    抽象数据类型是指一个数学模型以及定义在该模型上的一组操作。抽象数据类型的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关,即不论其内部结构如何变化,只要它的数学特性不变,都不影响其外部的使用。
    实际上,抽象数据类型体现了程序设计中问题分解、抽象和信息隐藏的特性
  • 和数据结构的形式定义相对应,抽象数据类型可用以下三元组表示(D,S,P),其中,D是数据对象,S是D上的关系集,P是对D的基本操作集。
ADT 抽象数据类型名
{
	数据对象:<数据对象的定义>
	数据关系:<数据关系的定义>
	基本操作:<基本操作的定义>
}

其中,数据对象和数据关系的定义用伪码描述,基本操作的定义格式为:
基本操作名(参数表)
	初始条件:<初始条件描述>
	操作结果:<操作结果描述>


5、算法和算法分析

  • 算法定义
    什么是算法呢?算法是描述解决问题的方法。算法(Algorithm)这个单词最早出现
    在波斯数学家阿勒·花刺子密在公元825年(相当于我们中国的唐朝时期)所写的《印度
    数字算术》中。如今普遍认可的对算法的定义是:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作
  • 算法的特性
    一个算法还具有下列5个重要特性:
  1. 有穷性指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成
  2. 确定性算法中每一条指令必须有确切的含义,读者理解时不会产生二义性
  3. 可行性算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成
  4. 输入一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合
  5. 输出一个算法有一个或多个输出,这些输出是同输入有着某些特定关系的量
  • 算法设计的要求
  1. 正确性
    算法的正确性尾指算法至少应该旦有输入、输出和加工处理无歧义性,能
    正确反映问题的需求’能够得到问题的正确答案

    但是算法的“正确”通常在用法上有很大的差别,大体分为以下四个层次。
    (1)算法程序没有语法错误。
    (2)算法程序对于合法的输入数据能够产生满足要求的输出结果。
    (3)算法程序对于非法的输入数据能够得出满足规格说明的结果。
    (4)算法程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果。
    对于这四层含义,层次(1)要求最低,但是仅仅没有语法错误实在谈不上是好算
    法。这就如同仅仅解决温饱,不能算是生活幸福—样。而层次(4)是最困难的,我们几
    乎不可能逐一验证所有的输入都得到正确的结果。
    因此算法的正确性在大部分情况下都不可能用程序来证明’而是用数
    学方法证明的。证明一个复杂算法在所有层次上都是正确的’代价非常高
    昂。所以—般情况下,我们把层次(3)作为一个算法是否正确的标准。
  2. 可读性
    可读性:算法设计的另一目的是为了便于阅读、理解和交流。
    可读性高有助干人们理解算法,晦涩难懂的算法往往隐含错误,不易被发现,并且
    难于调试和修改。
  3. 健壮性
    当输入数据不台法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。
  4. 效率与低存储量要求
    最后,好的算法还应该具备时间效率高和存储量低的特点。时间效率指的是算法的执行时间。对干同一个问题,如果有多个算法能够解决,执行时间短的算法效率高,执行时间长的效率低。存储量需求指的是算法在执行过程中需要的最大存储空间’主要指算法程序运行时所占用的内存或外部硬盘存储空间。设计算法应该尽量满足时间效率高和存储量低的需求。

6、算法效率的度量方法

算法执行时间需通过依据该算法编制的程序在计算机上运行时所消耗的时间来度量。而度量一个程序的执行时间通常有两种方法。

  • 事后统计方法
    这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
  • 事前分析估算方法
    我们的计算机前辈们,为了对算法的评判更科学,研究出了一种叫做事前分析估算的方法:在计算机程序编制前,依据统计方法对算法进行估算

7、算法时间复杂度和空间复杂度(重点)

  • 算法时间复杂度
  1. 算法时间复杂度定义

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

  1. 什么是大O阶方法

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数
2、在修改后的运行次数函数中,只保留最高阶项
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

  1. 常数阶
// 计算Func4的时间复杂度?
void Func4(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
++count;
}
printf("%d\n", count);
}

看上面这个例子,for循环共循环了100次,通过推导大O阶方法,时间复杂度为 O(1)。所以,循环次数是确定的常数次,时间复杂度都是O(1)

  1. 对数阶
// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
	assert(a);
	int begin = 0;
	int end = n;
	while (begin < end)
	{
		int mid = begin + ((end - begin) >> 1);
		if (a[mid] < x)
			begin = mid + 1;
		else if (a[mid] > x)
			end = mid;
		else
			return mid;
	}
	return -1;
}

数据结构-----绪论_第9张图片

这就是C语言中的二分查找,前提是这个数组是有序的时候,才可以用二分查找,如果用一般的遍历数组的话,时间复杂度应该是O(n),而这里用的二分查找就使得时间复杂度变成了O(logn),这使得程序所消耗的时间大大减少了。

  1. 线性阶
// 计算Func2的时间复杂度?
void Func2(int N)
{
int count = 0;
for (int k = 0; k < 2 * N ; ++ k)
{
++count;
}
int M = 10;
while (M--)
{
++count;
}
printf("%d\n", count);
}

看上面的代码,基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N)。

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, char character )
{
while(*str != '\0')
{
if(*str == character)
return str;
++str;
}
return NULL;
}

在上面的代码中,不知道字符串的具体长度,基本操作执行最好1次,最坏N次,时间复杂度一般看最坏,所以时间复杂度为 O(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;
}

上面代码中有两个未知数,基本操作执行了M+N次,有两个未知数M和N,时间复杂度为 O(N+M)。

  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;
	}
}

数据结构-----绪论_第10张图片
上面的代码是C语言中的冒泡排序算法,它的原理就是每次排出一个数出来,基本操作执行最好N次,最坏执行了(N*(N+1)/2次,通过推导大O阶方法+时间复杂度一般看最坏,时间复杂度为 O(N^2)

在这里插入图片描述
数据结构-----绪论_第11张图片

  • 空间复杂度
    空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法.

总结

我们这—章主要谈了算法的一些基本概念。谈到了数据结构与算法的关系是相互依赖不可分割的。
算法的定义:算法是解决特定问题求解步骤的描述,在计算机中为指令的有限序列,并且每条指令表示一个或多个操作。
算法的特性:有穷性、确定性、可行性、输入、输出
算法的设计的要求:正确性、可读性、健壮性、高效率和低存储量需求
算法特性与算法设计容易混,需要对比记忆。
算法的度量方法:事后统计方法(不科学、不准确)、事前分析估算方法。
然后给出了算法时间复杂度的定义和推导大O阶的步骤。
推导大O阶:
(1)用常数1取代运行时间中的所有加法常数。
(2)在修改后的运行次数函数中,只保留最高阶项。
(3)如果最高阶项存在且其系数不是1,则去除与这个项相乘的系数。
得到的结果就是大O阶。

通过这些步骤,我们可以在得到算法的运行次数表达式后,很快得到它的时间复杂度,即大O阶。同时我也提醒了大家,其实推导大O阶很容易但如何得到运行次数的表达式却是需要数学功底的。

时间复杂度算的不是时间是执行次数,空间复杂度算的不是空间是变量的个数

你可能感兴趣的:(数据结构与算法分析,数据结构,算法,c语言)