第一章绪论

1.1 数据结构的基本概念

1.1.1 为什么要学习数据结构

软件设计是计算机学科的核心内容之一。进行软件设计时要考虑的首要问题时数据的表示、组织和处理方法,这直接关系到软件工程化程度和软件的运行效率。
随着计算机技术的飞速发展,计算机应用从早期的科学计算打大到过程控制、管理和数据处
理等领域。计算机处理的对象也从简单的数值数据,发展到各种多媒体数据。软件糸统处理的数据量越来越大,数据的结构也越来越复染。因此,针对实际问题,如何合理地织数据,如何建立合适的数据结构,如何设计好的算法,是软件设计的重要问题,而这些正是 “数据结枸” 课程讨论的主要内容。
在计算机中,现实世界中的对象用数据来描述。“数据结构” 课程的任务是,讨论数据的各种
逻辑结构、在计算机中的存储结构以及各种操作的算法设计。“数据结构”〞课程的主要目的是,培养学生掌握处理数据和编写高效率软件的基本方法,为学习后续专业课程以及进行软件开发打下坚实基础。
数据结构是软件设计的重要理论和实践基础,数据结构设计和算法设计是软件系统设计的基
础和核心。“数据结构” 课程讨论的知识内容,是软件设计的理论基础;数据结构课程介绍的技术方法,是软件设计中使用的基本方法。“数据结构”是一门理论与实践并重的课程,要求学生既要掌握数据结构的基础理论知识,又要掌握运行和调试程序的基本技能。因此,“数据结构”课程在计算机学科本科培养过程中的地位十分重要,是计算机专业本科的核心课程,是培养程序设计能力的必不可少的重要环节。
在计算机界流传着一句经典名言“数据结构十算法=程序设计”《瑞上 Niklaus Wirth 教授),这句话简介明了地说明了程序(或软件)与数据结构和算法的关系,以及“数据结构” 课程的重要性。

1.1.2 什么是数据结构

数据(data)是描述客观事物的数字、字符以及所有能输入到计算机中并能被计算机接受的各种符合的集合。
数据元素(data element) 是指表示一个事物的一组数据称,数据元素是数据的基本单位。
数据项(data item) 是数据元素中有独立含义的、不可分割的最小标识单位。
关键字(key) 是数据元素中用来识别该元素的一个或多个数据项
关键字(primary key) 能够唯一识别数据元素的关键字称为主关键字

数据元素可以是一个不可分割的原子项,也可以由多个数据项组成,例如:
一个整数、一个字符都可以称为一个数据元素,它们的数据项分别是一个整数、一个字符;
学生表有姓名、年龄、学号、成绩,那么学生表里面的数据也是一个数据元素,它的数据项分别是姓名、年龄、学号、成绩。

数据结构(data structure) 是指数据元素之间存在的关系。一个数据结构是由n(n>=0)个数据元素的有限集合,数据元素之间具有某种特定的关系。

数据结构的概念包含三个方面:数据的逻辑结构、数据的存储结构和数据的操作

1.1.2.1 数据的逻辑结构

数据的逻辑结构是指数据元素之间的逻辑关系,用一个数据元素的集合和定义在此集合上的若干关系来表示,常被简称为数据结构。
数据结构分为三种:线性结构和非线性结构。其中非线性结构又分为树结构和图结构。
在这里插入图片描述

图1-1

图1-1以图示法表示数据的逻辑结构,一个圆表示一个数据元素,圆中的字符表示数据元素的标记或取值,连线表示数据元素之间的关系。

1)线性结构
最简单的数据结构,数据元素之间具有某种线性关系,即除第一个和最后一个元素外,每个元素有且仅有一个前驱元素和一个后继元素,第一个没有前驱元素,最后一个没有后继元素。线性表、串、栈和队列都是线性结构。
以图1-1为例,A没有前驱元素,C没有后继元素,B的前驱元素是A,后继元素是C

2)树结构
树结构是数据元素之间具有层次关系的一种非线性结构。树中的元素通常称为结点。
树结点的层次关系是指,根结点没有父母节点,除根结点之外的其他结点有且仅有一个父母结点,所有结点可以有零到多个孩子结点。
以图1-1为例,A是根结点,它没有父母结点;B的父母结点是A,孩子结点是D,E,F; D的父母结点是B,它没有孩子结点。
常用的比赛安排图就是树结构。

3)图结构
图结构也是非线性结构,每个数据元素可以有多个前驱元素和后继元素。
交通线路图、飞机航班图都是图结构。

1.1.2.2 数据的存储结构

数据元素及其关系在计算机中的存储表示或实现称为数据的存储结构,也称为物理结构。

数据的逻辑结构从逻辑角度观察数据,与数据的存储无关,是独立于计算机的。而数据的存储结构是逻辑结构再计算机内存中的实现,是依赖于计算机的。

数据的存储方式有两种:顺序存储结构和链式存储结构

顺序存储结构使用一组连续的内存单元依次存放数据元素,元素在内存中的物理存储顺序与它们的逻辑次序相同,即每个元素与其前驱元素及其后继元素的存储位置相邻。这样,元素的物理存储次序体现数据元素间的逻辑关系,通常使用数组来实现。

链式存储结构使用若干地址分散的存储单元存储数据元素,逻辑上相邻的数据元素在物理位置上不一定相邻,数据元素间的关系需要采用附加信息特别指定。通常,采用指针变量记载前驱或后继元素的存储地址,由数据域和地址域组成的一个结点表示一个数据元素,通过地址域吧相互直接关联的结点链接起来,结点间的链接关系体系数据元素之间的逻辑关系。

线性表可采用上述两种存储结构。线性表(a0, a1, …, an-1)的两种存储结构如下图所示:
在这里插入图片描述

图1-4
图1-4(a)采用顺序储存结构存储线性表,数据元素占用所有存储空间,个元素连续存储,逻辑上相邻的元素在存储位置上也相邻,数据的存储结构体系了数据的逻辑结构;图1-4(b)采用链式存储存储线性表,各元素分散存储,每个元素必须用一个包含数据域和地址域的结点存储,数据域保持数据元素,地址域保存数据元素的前驱和(或)后继结点的地址,结点间的链接关系体系数据的逻辑结构。

如果一个数据元素由多个数据项组成,则数据域有多个。例如,学生表的顺序存储和链式存储结构如下图所示:
在这里插入图片描述
顺序存储结构和链式存储结构是两种最基本、最常用的存储结构。除此之外,将顺序存储和链式存储进行组合,还可以构造出一些更复杂的存储结构。

1.1.2.3 数据操作

数据操作是指对一种数据结构中的数据元素进行各种运算或处理。每种数据结构都有一组数据造作,其中包含以下基本操作:

  1. 初始化
  2. 判断是否为空
  3. 统计数据元素个数
  4. 判断是否包含指定元素
  5. 遍历
  6. 获取指定元素值
  7. 设置指定元素值
  8. 插入指定元素
  9. 删除指定元素
  10. 查找指定元素

数据操作定义在数据的逻辑结构上,对数据操作的实现定义在数据的存储结构。例如,线性表包含上述一组数据操作,采用顺序存储结构或链式存储结构,都可实现上述操作。

1.1.3 数据类型与抽象数据类型

1.1.3.1 数据类型

类型(type) 是具有相同逻辑意义的一组值的集合。
数据类型(data type) 是一个类型和定义在这个类型上的操作的集合。
数据类型定义了数据的性质、取值范围以及对数据所能进行的各种操作。
数据类型和数据结构两个概念的侧重点不同。数据类型研究的是每种数据所具有的特性,以及对这种特性的数据能够进行哪些操作;数据结构研究的是数据元素之间的关系,数据结构与数据元素的数据类型无关,也不随数据元素值的变化而变化。

1.1.3.2 抽象数据类型

抽象数据类型(Abstract Data Type, ADT) 是指一个数学模型以及定义在该模型上的一组操作。
个人理解:就像是C#里面的类

1.2 算法

1.2.1 什么是算法

曾获图灵奖的著名计算机科学家D.Knuth对算法做过一个学术界广泛接受的描述性定义:
一个**算法(algorithm)**是一个有穷规则的集合,其规则确定一个解决某一特定类型问题的操作序列。

算法的规则必须满足以下5个特性:

  1. 有穷性:对于任意一组合法的输入值,算法在执行有穷步骤后一定能结束。即算法的操作步骤为有限个,且每步都能在有限时间内完成。
  2. 确定性:对于每种情况下所应执行的操作,在算法中都有确切的规定,使算法的阅读者或执行者都能明确其含义及如何执行,并且在任何条件下,算法都只有一条执行路径。
  3. 可行性:算法中的所有操作都必须足够基本,都可以通过已经实现的基本操作运算有限次实现之。
  4. 有输入:算法有零个或多个输入数据。输入数据是算法的加工对象,既可由算法指定,也可以在算法执行过程中通过输入得到。
  5. 有输出:算法有一个或多个输出数据。输出数据是一组与输入数据有确定关系的量值,是算法加工后得到的结果,这种确定关系即为算法的功能。

有穷性和可行性是算法最重要的特征。

算法设计应满足以下5个目标:

  1. 正确性:算法应确定地满足应用问题的需求,这是算法设计的基本目标
  2. 健壮性:即使输入数据不正确,算法也能做出适当处理,不会导致不可控结果
  3. 高时间效率:算法执行时间越短,时间效率越高
  4. 高空间效率:算法执行时占用的存储空间越少,空间效率越高
  5. 可读性:算法表达思路清晰,简洁明了,易于理解

算法是对问题求解过程的描述,精确地指出怎样从给定的输入信息得到要求的输出信息,其中操作不中的语义明确,操作序列的长度有限。

算法可以用自然语言或者伪代码描述,但是计算机无法执行。因此,数据结构和算法的实现需要借助程序设计语言,将算法表达成基于一种程序语言的可执行程序。

算法建立在数据结构之上,对数据结构的操作需要用算法去描述。
算法设计依赖于数据的逻辑结构,算法实现依赖于数据的存储结构。

1.2.2 算法分析

算法分析主要包括时间代价和空间代价两个方面。

算法的时间代价是指算法执行时所花费的CPU时间量,是算法中涉及的存、取、转移、加、减等各种基本运算的执行时间之和,与参与运输的数据量有关,很难事先计算得到。

算法的时间效率是指算法的执行时间随问题规模的增长而增长的趋势,通常采用时间复杂度(time complexity)来度量。当问题的规模以某种单位从1增加到n时,解决这个问题的算法在执行时所耗费的时间也以某种单位从1增加到T(n),则称此算法的时间复杂度为T(n)。当n增大时,T(n)也随之增大。

采用算法的渐进分析中的大O表示法作为算法时间复杂度的渐进度量值。大O表示法是指,当且仅当存在正整数c和n0, 使得T(n) <= cf(n) 对所有的n>=n0成立时,称该算法的时间增长率与f(n)的增长率相同,记为T(n)=O(f(n)).

若算法的执行时间是常数级,不依赖于数据量n,则时间复杂度为O(1); 若算法的执行时间是n的线性关系,则时间复杂度为O( n n n)。同理,对数级、平方级、立方级、指数级的时间复杂度分别为 O( l o g 2 n log_{2}n log2n)、O( n 2 n^{2} n2) 、O( n 3 n^{3} n3) 、O( 2 n 2^{n} 2n)。这些函数按数量级递增排列具有下列关系:
O(1)<O( l o g 2 n log_{2}n log2n)< O( n n n)<O( n l o g 2 n nlog_{2}n nlog2n)<O( n 2 n^{2} n2) <O( n 3 n^{3} n3) <O( 2 n 2^{n} 2n)

如何估算时间复杂度?一个算法通常由一个控制结构和若干基本操作组成,则
算法的执行时间 = ∑ i \sum\limits_{i} i 基本操作( i i i)的执行次数 × \times × 基本操作( i i i)的执行时间

由于算法的时间复杂度表示算法执行时间的增长率而非绝对时间,因此可以忽略一些次要因素,算法的执行时间绝大部分花在循环和递归上。设基本操作的执行时间为常量级O(1),则算法的执行时间是算法的基本操作执行次数之和,以此作为估算算法时间复杂度的依据,可表示算法本身的时间效率。
每个算法渐进时间复杂度中的f( n n n),可由统计程序得到,与程序结构有关。循环语句的时间代价一般可用以下三个原则进行分析:

  1. 一个循环的时间代价 = 循环次数 × \times ×每次执行的简单语句数目
  2. 多个并列循环的时间代价 = 每个循环的时间代价之和
  3. 多个嵌套的循环时间代价 = 每层循环的时间代价之积

下面我们来看几个例子。
1 一个简单语句的时间复杂度为O(1)。例如:

int a = 0;

2 执行n次循环的语句,时间复杂度为O( n n n)。例如:

int n = 8;
int count = 0;
for(int i = 1; i<= n; i++)
	count++;

个人理解:
循环次数 为n,每次循环时执行的语句数目为1,所以
一个循环的时间代价 = 循环次数 × \times ×每次执行的简单语句数目 = n n n
所以时间复杂度为O( n n n)

3 时间复杂度为O( l o g 2 n log_{2}n log2n)的循环语句如下:

int n = 8;
int count = 0;
for(int i = 1; i<= n; i*=2)
	count++;

个人理解:
这个例子中 i = i * 2, 当 i > 1时,其实循环次数i= 2 x 2^{x} 2x(x > =2),而i<=n,所以 2 x 2^{x} 2x = n n n, 所以 x x x = l o g 2 n log_2{n} log2n, 所以循环的次数就是1 + l o g 2 n log_2{n} log2n,每次循环执行的语句数据为1,所以时间复杂度为O( l o g 2 n log_{2}n log2n)

4 时间复杂度为O( n 2 n^{2} n2)的语句为:

int n = 8;
int count = 0;
for(int i = 1; i<= n; i++)
	for(int j = 1; j <= n; j++)
		count++;

个人理解:
外层循环执行时间为:外层循环执行次数n × \times × 外层循环执行语句数目1 = n
内层循环执行时间为:内层循环执行次数n × \times × 内层循环执行语句数目1 = n
所以整个循环的执行时间 = n n n × \times × n n n = n 2 n^{2} n2,所以算法复杂度为O( n 2 n^{2} n2)

5 时间复杂度为O( n l o g 2 n nlog_2{n} nlog2n)的循环语句为:

int n = 8;
int count = 0;
for(int i = 1; i<= n; i* = 2)
	for(int j = 1; j <= n; j++)
		count++;

个人理解:
外层循环次数为 1 + l o g 2 n log_2{n} log2n, 内层循环次数为 n n n,总共次数为 n × n\times n× l o g 2 n log_2{n} log2n,所以时间复杂度为O( n l o g 2 n nlog_2{n} nlog2n)

6 时间复杂度为O( n n n)的二层循环语句为:

int n = 8;
int count = 0;
for(int i = 1; i<= n; i* = 2)
	for(int j = 1; j <= i; j++)
		count++;

个人理解:
这个不理解

算法的空间代价是指算法执行时所占有的空间存储量。

执行一个算法所需要的存储空间包括三部分:输入数据占用的存储空间、程序指令占用的存储空间、辅助便利占用的存储空间。其中,输入数据和程序指令所占用的存储空间和算法无关,所以,辅助变量占用的存储空间就成为度量算法空间代价的依据。

当问题的规模以某种单位从1增加到n时,解决这个问题的算法在执行时所占用的存储空间也以某种单位从1增加到S(n),则称此算法的空间复杂度(sapce complexity)为S(n)。当n增大时,S(n)也随之增大。空间复杂度用大O表示法记为S(n) = O(f(n)),表示该算法的空间增长率与f(n)的增长率相同。

例如,交换两个变量i,j算法,除了程序指令和i、j本身占用的存储空间外,还需要一个临时变量temp,这个temp变量所占用的一个存储单元就是交换变量算法的空间复杂度O(1)。

1.2.3 算法设计

本节主要是几个算法的例子。

1 求两个整数的最大公约数
首先我们要知道什么是最大公约数,以及怎么求。

最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个。a,b的最大公约数记为(a,b),同样的,a,b,c的最大公约数记为(a,b,c),多个整数的最大公约数也有同样的记号。求最大公约数有多种方法,常见的有质因数分解法、短除法、辗转相除法、更相减损法。与最大公约数相对应的概念是最小公倍数,a,b的最小公倍数记为[a,b]。(来自百度)

如果数a能被数b整除,a就叫做b的倍数,b就叫做a的约数。(来自百度)
例如:4能被2整除,4是2的倍数,2是4的约数。

辗转相除法是求两个自然数的最大公约数的一种方法,也叫欧几里德算法。
例如,求(319,377):
∵ 319÷377=0(余319)
∴(319,377)=(377,319);
∵ 377÷319=1(余58)
∴(377,319)=(319,58);
∵ 319÷58=5(余29)
∴ (319,58)=(58,29);
∵ 58÷29=2(余0)
∴ (58,29)= 29;
∴ (319,377)=29。
简单表达就是:
GCD(319,377)=GCD(377,319)=GCD(319,58)=GCD(58,29)=GCD(29,0)=29
总结一下就是:
GCD(a,b)=GCD(b,a%b) 0<=a%b<b
C#代码实现如下:

        public static void ShowGCD(int a, int b)
        {
            if (a < b) // if a is smaller than b, then change their position
            {
                int temp = a;
                a = b;
                b = temp;
            }
            while (b > 0)
            {
                int temp = a % b;
                a = b;
                b = temp;
            }

            Console.WriteLine("gcd is {0}", a);
        }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值