文章目录
- 第一章 绪论
- 1.1 数据结构的基本概念
- 1.1 练习题
- 1.2 算法和算法评价
- 1.2 练习题
- 第二章 线性表
- 2.1 线性表的定义和基本操作
- 2.1 练习题
- 2.2 线性表的顺序表示
- 2.2 练习题
由于最近实在没时间导致这个更新鸽了。。。实在不好意思。
第一章 绪论
1.1 数据结构的基本概念
- 数据结构的三要素:逻辑结构、存储结构(物理结构)、数据的运算。
- 数据的逻辑结构分为线性结构和非线性结构,线性表是典型的线性结构,集合、树和图是典型的非线性结构。
- 数据的存储结构主要有顺序存储、链式存储、索引存储和散列存储。
- 顺序存储:优点是可以实现随机存取,每个元素占用最少的存储空间;缺点是只能使用相邻的一整块存储单元,因此可能产生较多的外部碎片。
- 链式存储:优点是不会出现碎片现象,能充分利用所有存储单元;缺点是每个元素因存储指针而占用额外的存储空间,且只能实现顺序存取。
- 索引存储。在存储元素信息的同时,还建立附加的索引表。索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)。其优点是检索速度快,缺点是附加的索引表额外占用存储空间。另外,增加和删除数据时也要修改索引表,因而会花费较多的时间。
- 散列存储。根据元素的关键字直接计算出该元素的存储地址,又称哈希(Hash)存储。优点是检索、增加和删除节点的操作都很快;缺点是若散列函数不好,则可能出现元素存储单元的冲突,而解决冲突会增加时间和空间开销。
- 可以用抽象数据类型(ADT)定义一个完整的数据结构。
- 在存储数据时,不仅要存储数据元素的值,而且要存储数据元素之间的关系。
1.1 练习题
- 以下属于逻辑结构的是()
A、顺序表
B、哈希表
C、有序表
D、单链表
解析:有序表是指关键字有序的线性表,仅描述元素之间的逻辑关系,它既可以链式存储也可以顺序存储,故属于逻辑结构。顺序表、哈希表、单链表是三种不同的数据结构,既描述逻辑结构,又描述存储结构和数据运算。
答案:C - 以下与数据的存储结构无关的术语是()
A、循环队列
B、链表
C、哈希表
D、栈
解析:循环队列(易错点)是用顺序表表示的队列,是一种数据结构。栈是一种抽象数据类型,可采用顺序存储或链式存储,只表示逻辑结构。
答案:D - 以下关于数据结构的说法中,正确的是()
A、数据的逻辑结构独立于其存储结构
B、数据的存储结构独立于其逻辑结构
C、数据的逻辑结构唯一决定了其存储结构
D、数据结构仅由其逻辑结构和存储结构决定
解析:数据的逻辑结构只采用抽象表达方式,独立于存储结构,数据的存储方式有多种不同的选择;而数据的存储结构是逻辑结构在计算机上的映射,它不能独立于逻辑结构而存在。数据结构包括三个要素,缺一不可。
答案:A - 链式存储设计时,结点内的存储单元地址()
A、一定连续
B、一定不连续
C、不一定连续
D、部分连续,部分不连续
解析:链式存储设计时,各个不同结点的存储空间可以不连续,但结点内的存储单元地址必须连续。
答案:A - 对于两种不同的数据结构,逻辑结构或物理结构一定不相同吗?
解答:应该注意到,数据的运算也是数据结构的一个重要方面。
对于两种不同的数据结构,它们的逻辑结构和物理结构完全有可能相同。比如二叉树和二叉排序树,二叉排序树可以采用二叉树的逻辑表示和存储方式,前者通常用于表示层次关系,而后者通常用于排序和查找。虽然它们的运算都有建立树、插入结点、删除结点和查找结点等功能,但对于二叉树和二叉排序树,这些运算的定义是不同的,以查找结点为例,二叉树的时间复杂度为O(n),而二叉排序树的时间复杂度为O(log2n)。 - 试举一例,说明对相同的逻辑结构,同一种运算在不同的存储方式下实现时,其运算效率不同。
解答:线性表既可以用顺序存储方式实现,又可以用链式存储方式实现。在顺序存储方式下,在线性表中插入和删除元素,平均要移动近一半的元素,时间复杂度为O(n);而在链式存储方式下,插入和删除的时间复杂度都是O(1)。
1.2 算法和算法评价
- 算法是对特定问题求解步骤的一种描述,是指令的有限序列,它还具有5个重要特性:有穷性、确定性、可行性、输入、输出。通常,设计一个“好”的算法应考虑达到以下目标:正确性、可读性、健壮性、效率与低存储量需求。
- 算法的时间复杂度记为T(n)=O(f(n)),通常采用算法中基本运算的频度f(n)来分析算法的时间复杂度,基本运算的频度不仅与问题规模n有关,而且与输入中各元素的取值有关。
- 空间复杂度是问题规模n的函数,记为S(n)=O(g(n)),算法原地工作是指算法所需的辅助空间为常量,即O(1)。
1.2 练习题
- 一个算法应该是()
A、程序
B、问题求解步骤的描述
C、要满足五个基本特性
D、A和C
解析:本题是中山大学某年的考研真题,容易误选C,五个特性只是算法的必要条件,不能成为算法的定义。
答案:B
第2题代码:
x=2;
while(x<n/2)
x=2*x;
- 【2011统考真题】设n是描述问题规模的非负整数,下面的程序片段的时间复杂度是()。
A、O(log2n)
B、O(n)
C、O(nlog2n)
D、O(n2)
解析:每执行一次,x乘2,设执行次数为t,则有2t+1 < n/2,所以 t < log2(n/2) - 1 = log2n - 2,得T(n) = O(log2n)
答案:A
第3题代码:
int fact(int n){
if(n<=1) return 1;
return n*fact(n-1);
}
- 【2012统考真题】求整数n(n≥0)的阶乘的算法如下,其时间复杂度是()
A、O(log2n)
B、O(n)
C、O(nlog2n)
D、O(n2)
解析:计算阶乘n!的递归代码,一共执行 n 次递归调用,T(n)=O(n)。
答案:B - 【2013统考真题】已知两个长度分别为m和n的升序链表,若将它们合并为长度为m+n的一个降序链表,则最坏情况下的时间复杂度是()。
A、O(n)
B、O(mn)
C、O(min(m,n))
D、O(max(m,n))
解析:两个升序链表合并,两两比较表中元素,每比较一次,确定一个元素的链接位置(取较小元素,头插法)。当一个链表比较结束后,将另一个链表的剩余元素插入即可。最坏的情况是两个链表中的元素依次进行比较,所以时间复杂度为O(max(m,n))。
答案:D
第5题代码:
count=0;
for(k=1;k<=n;k*=2)
for(j=1;j<=n;j++)
count++;
- 【2014统考真题】下列程序段的时间复杂度是()。
A、O(log2n)
B、O(n)
C、O(nlog2n)
D、O(n2)
解析:和第2题差不多,内层循环的时间复杂度是O(n),外层循环的时间复杂度是O(log2n),根据乘法规则可知,该段程序的时间复杂度T(n)=T1(n) × T2(n) = O(n) × O(log2n) = O(nlog2n)。
答案:C
第6题代码:
int func(int n){
int i=0, sum=0;
while(sum<n) sum += ++i;
return i;
}
- 【2017统考真题】下列函数的时间复杂度是()
A、O(log2n)
B、O(n1/2)
C、O(n)
D、O(nlog2n)
解析:每执行一次,i自增1,我们如果把 i 当作一个首项为0,公差为1的等差数列的话,那么sum就是等差数列的和,由等差数列求和公式得,sum=(i+1)*i/2,设循环次数为 t ,则 t 满足(t+1)*t/2<n,因此时间复杂度为O(n1/2)。
答案:B
第7题代码:
x=0;
while (n>=(x+1)*(x+1))
x=x+1;
- 【2019统考真题】设n是描述问题规模的非负整数,下列程序段的时间复杂度是()
A、O(log2n)
B、O(n1/2)
C、O(n)
D、O(n2)
解析:假设第 k 次循环终止,则第 k 次执行时,(x+1)2 > n,x 的初始值为0,第k次判断时,x=k-1,即k2 > n,k > n1/2,因此该程序段的时间复杂度为O(n1/2),因此选B。
答案:B - 某算法的时间复杂度为O(n2),表明该算法的()。
A、问题规模是n2
B、执行时间等于n2
C、执行时间与n2成正比
D、问题规模与n2成正比
解析:时间复杂度T(n)=O(n2),执行时间与n2成正比。T(n)是问题规模n的函数,其问题规模仍然是n而不是n2。
答案:C
第9题代码:
int m=0, i, j;
for(i=1;i<=n;i++)
for(j=1;j<=2*i;j++)
m++;
- "m++"的执行次数为()。
A、n(n+1)
B、n
C、n+1
D、n2
解析:m++语句的执行次数为
答案:A
第二章 线性表
2.1 线性表的定义和基本操作
- 线性表的定义。线性表是具有相同数据类型的 n (n ≥ 0)个数据元素的有限序列,其中 n 为表长,当n = 0时线性表是一个空表。若用 L 命名线性表,则其一般表示为 L = (a1,a2,…,ai,ai+1,…,an)式中,a1是唯一的“第一个”数据元素,又称表头元素;an是唯一的“最后一个”数据元素,又称表尾元素。除第一个元素外,每个元素有且仅有一个直接前驱。除最后一个元素外,每个元素有且仅有一个直接后继。这种线性有序的逻辑结构正是线性表名字的由来。
- 线性表的基本操作。
线性表的主要操作如下:
InitList(&L):初始化表。构造一个空的线性表。
Length(L):求表长。返回线性表 L 的长度,即 L 中数据元素的个数。
LocateElem(L,e):按值查找操作。在表 L 中查找具有给定关键字值的元素。
GetElem(L,i):按位查找操作。获取表 L 中第 i 个位置的元素的值。
ListInsert(&L,i,&e):插入操作。在表 L 中的第 i 个位置上插入指定元素e。
ListDelete(&L,i,&e):删除操作。删除表 L 中的第 i 个位置的元素,并用 e 返回删除元素的值。
PrintList(L):输出操作。按前后顺序输出线性表 L 的所有元素值。
Empty(L):判空操作。若 L 为空表,则返回 true ,否则返回 false 。
DestroyList(&L):销毁操作。销毁线性表,并释放线性表 L 所占用的内存空间。
注意:①基本操作的实现取决于采用哪种存储结构,存储结构不同,算法的实现也不同。②“&”表示C++中的引用调用。若传入的变量是指针型变量,且在函数体内要对传入的指针进行改变,则会用到指针变量的引用型。在C中采用指针的指针也可达到同样的效果。 - 总结:线性表的特点:表中元素的个数有限,表中元素具有逻辑上的顺序性,表中元素有其先后次序。表中元素都是数据元素,每个元素都是单个元素。表中元素的数据类型都相同,这意味着每个元素占有相同大小的存储空间。表中元素具有抽象性,即仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容。
2.1 练习题
- 线性表是具有 n 个()的有限序列。
A、数据表
B、字符
C、数据元素
D、数据项
解析:线性表由具有相同数据类型的有限数据元素组成的,数据元素由数据项组成的。
答案:C - 以下()是一个线性表。
A、由 n 个实数组成的集合
B、由100个字符组成的序列
C、所有整数组成的序列
D、邻接表
解析:线性表定义的要求为:相同数据类型、有限序列。选项C的元素个数是无穷个,错误;选项A集合中的元素没有前后驱关系,错误;选项D属于存储结构,线性表是一种逻辑结构,只有选项B符合要求。
答案:B
2.2 线性表的顺序表示
- 线性表的顺序存储又称顺序表。它是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。第 1 个元素存储在线性表的起始位置,第 i 个元素的存储位置后面紧接着存储的是第 i + 1 元素,称 i 为元素 ai在线性表中的位序。因此,顺序表的特点是表中元素的逻辑顺序与其物理顺序相同。
- 线性表中元素的位序是从 1 开始的,而数组中元素的下标是从 0 开始的。
- 假定线性表的元素类型为 ElemType,则线性表的顺序存储类型描述为
#define MaxSize 50 //定义线性表的最大长度
typedef struct
{
ElemType data[MaxSize]; //顺序表的元素
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义
- 一维数组可以是静态分配的,也可以是动态分配的。在静态分配时,由于数组的大小和空间事先已经固定,一旦空间占满,再加入新的数据将会产生溢出,进而导致程序崩溃。而在动态分配时,存储数组的空间是在程序执行过程中通过动态存储分配语句分配的,一旦数据空间占满,就另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储数组空间的目的,而不需要为线性表一次性地划分所有空间。
#define InitSize 100 //表长度的初始定义
typedef struct
{
ElemType *data; //指示动态分配数组的指针
int MaxSize, length; //数组的最大容量和当前个数
}SeqList; //动态分配数组顺序表的类型定义
- C的初始动态分配语句为
L.data=(ElemType*)malloc(sizeof(ElemType)*InitSize);
- C++的初始动态分配语句为
L.data=new ElemType[InitSize];
- 动态分配并不是链式存储,它同样属于顺序存储结构,物理结构没有变化,依然是随机存取方式,只是分配的空间大小可以在运行时决定。
- 顺序表最主要的特点是随机访问,即通过首地址和元素符号可在时间O(1)内找到指定的元素。
- 顺序表的存储密度高,每个节点只存储数据元素。顺序表逻辑上相邻的元素物理上也相邻,所以插入和删除操作需要移动大量元素,平均时间复杂度为O(n)。
- 按值查找操作(顺序查找)在顺序表L中查找第一个元素值等于e的元素,时间复杂度为O(n)。
2.2 练习题
- 线性表的顺序存储结构是一种()
A、随机存取的存储结构
B、顺序存取的存储结构
C、索引存取的存储结构
D、散列存取的存储结构
解析:本题易误选B。存取方式是指读写方式。顺序表是一种支持随机存取的存储结构,根据起始地址加上元素的序号,可以很方便地访问任意一个元素,这就是随机存取的概念。
答案:A - 一个线性表最常用的操作是存取任一指定序号的元素并在最后进行插入删除操作,则利用()存储方式可以节省时间。
A、顺序表
B、双链表
C、带头结点的双循环链表
D、单循环链表
解析:只有顺序表可以按序号随机存取,且在最后进行插入和删除操作不需要移动任何元素。
答案:A - 在 n 个元素的线性表的数组表示中,时间复杂度为O(1)的操作是()。
I、访问第 i (1 ≤ i ≤ n)个结点和求第 i (2 ≤ i ≤ n)个结点的直接前驱
II、在最后一个结点后插入一个新的结点
III、删除第 1 个结点
IV、在第 i (1 ≤ i ≤ n)个结点后插入一个结点
A、I
B、II、III
C、I、II
D、I、II、III
解析:在最后位置插入新结点不需要移动元素,时间复杂度为O(1);被删结点后的结点需依次前移,时间复杂度为O(n)。
答案:C - 设线性表由 n 个元素,严格说来,以下操作中,()在顺序表上实现要比链表上实现的效率高。
I、输出第 i (1 ≤ i ≤ n)个元素值
II、交换第 3 个元素与第 4 个元素的值
III、顺序输出这 n 个元素的值
A、I
B、I、III
C、I、II
D、II、III
解析:对于II,顺序表上实现仅需 3 次交换操作;链表上则需要分别找到两个结点前驱,前 4 个结点断链后再插入到第 2 个结点后面,效率较低。
答案:C - 若长度为 n 的非空线性表采用顺序存储结构,在表的第 i 个位置插入一个数据元素,i 的合法值应该是()。
A、1 ≤ i ≤ n
B、1 ≤ i ≤ n + 1
C、0 ≤ i ≤ n - 1
D、0 ≤ i ≤ n
解析:线性表元素的序号是从 1 开始,而在第 n + 1 个位置插入相当于在表尾追加。
答案:B - 【2010统考真题】设将 n(n>1)个整数存放到一维数组 R 中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(X0,X1,…,Xn-1)变换为(Xp,Xp+1,…,Xn-1,X0,X1,…,Xp-1,)。要求:
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
(3)说明你所设计算法的时间复杂度和空间复杂度。
解答:
(1)算法的基本设计思想:可将这个问题视为把数组 ab 转换成数组 ba (a代表数组的前 p 个元素,b代表数组中余下的 n - p 个元素),先将 a 逆置得到 a-1b ,再将 b 逆置得到 a-1b-1 ,最后将整个a-1b-1逆置得到 (a-1b-1)-1 = ba。设 Reverse 函数执行将数组元素逆置的操作,对 abcdefgh 向左循环移动 3 (p=3)个位置的过程如下:
Reverse(0, p - 1)得到 cbadefgh;
Reverse(p, n - 1)得到 cbahgfed;
Reverse(0, n - 1)得到 defghabc;
注:Reverse 中,两个参数分别表示数组中待转换元素的始末位置。
(2)使用C语言描述算法如下:
(3)上述算法中三个Reverse 函数的时间复杂度分别为O(p/2)、O((n-p)/2)、O(n/2),故所设计的算法的时间复杂度为O(n),空间复杂度为O(1)。
//第2小问代码
void Reverse(int R[], int from, int to)
{
int i, temp;
for(i = 0; i < (to - from + 1) / 2; i++)
{
temp = R[from + i];
R[from + i] = R[to - i];
R[to - i] = temp;
}
}
void Converse(int R[], int n, int p)
{
Reverse(R, 0, p - 1);
Reverse(R, p, n - 1);
Reverse(R, 0, n - 1);
}
- 【2011统考真题】一个长度为L(L≥1)的升序序列S,处在第个位置的数称为S的中位数。例如列S1=(11,13,15,17,19),则S1中的中位数是15。两个序列的中位数是含它们所有元素的升序序列的中位数。例如,若92=(2,4,6,8,20),则.S1和S2的中位数是11。现有两个等长升序序列A和B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列A和B的中位数。要求:
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
(3)说明你所设计算法的时间复杂度和空间复杂度。
解答:
(1)算法的基本设计思想如下:
分别求两个升序序列A、B的中位数,设为a和b,求序列A、B的中位数的过程如下:
①若a=b,则a或b即为所求中位数,算法结束。
②若a<b,则舍弃序列A中较小的一半,同时舍弃序列B中较大的一半,要求两次舍弃的长度相等。
③若a>b,则舍弃序列A中较大的一半,同时舍弃序列B中较小的一半,要求两次舍弃的长度相等。
在保留的两个升序序列中,重复过程①、②、③,直到两个序列中均只含一个元素时为止,较小者即为所求的中位数。
(2)本题代码如下:
(3)算法的时间复杂度为O(log2n),空间复杂度为O(1)。
int M_Search(int A[], int B[], int n)
{
int s1 = 0, d1 = n - 1, m1, s2 = 0, d2 = n - 1, m2;
//分别表示序列A和B的首位数、末位数和中位数
while(s1 != d1 || s2 != d2)
{
m1 = (s1 + d1) / 2;
m2 = (s2 + d2) / 2;
if(A[m1] == B[m2]) return A[m1]; //满足条件①
if(A[m1] < B[m2]) //满足条件②
{
if((s1 + d1) % 2 == 0) //若元素个数为奇数
{
s1 = m1; //舍弃A中间点以前的部分且保留中间点
d2 = m2; //舍弃B中间点以后的部分且保留中间点
}
else //若元素个数为偶数
{
s1 = m1 + 1; //舍弃A中间点及中间点以前的部分
d2 = m2; //舍弃B中间点以后的部分且保留中间点
}
}
else //满足条件③
{
if((s2 + d2) % 2 == 0) //若元素个数为奇数
{
d1 = m1; //舍弃A中间点以后的部分且保留中间点
s2 = m2; //舍弃B中间点以前的部分且保留中间点
}
else //若元素个数为偶数
{
d1 = m1; //舍弃A中间点以后的部分且保留中间点
s2 = m2 + 1; //舍弃B中间点及中间点以前的部分
}
}
}
return A[s1] < B[s2] ? A[s1] : B[s2];
}
- 【2013统考真题】已知一个整数序列A=(a0,a1,…,an-1),其中0 ≤ ai < n(0 ≤ i < n)。若存在ap1=ap2=…=apm=x且m>n/2(0 ≤ pk < n,1 ≤ k ≤ m),则称 x 为A的主元素。例如A=(0,5,5,3,5,7,5,5),则5为主元素;又如A=(0,5,5,3,5,1,5,7),则A中没有主元素。假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出A的主元素。若存在主元素,则输出该元素;否则输出-1。要求:
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
(3)说明你所设计算法的时间复杂度和空间复杂度。 - 【2018统考真题】给定一个含n(n≥1)个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小正整数。例如,数组{-5, 3, 2, 3}中未出现的最小正整数是1;数组{1, 2, 3}中未出现的最小正整数是4。要求:
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
(3)说明你所设计算法的时间复杂度和空间复杂度。