一、绪论
1.数据结构的术语
- 数据:所有能输入计算机并被计算机程序处理的符号的总称;
- 数据元素:数据的基本单位;
- 数据项:组成数据元素的、有独立含义的、不可分割的最小单位;
- 数据对象:是性质相同的数据元素的集合,是数据的一个子集;
范围大小:数据>数据对象>数据元素>数据项
举例:数据为所有学生信息,数据对象为学生信息集合,数据元素为一个学生信息,数据项是学生信息的姓名、年龄、性别等。这里性质相同的数据元素集合可以是班干部学生、非班干部学生等。
2.数据结构
逻辑结构
- 集合结构(非线性结构)
- 线性结构:线性表、栈和队列、字符串
- 树结构(非线性结构)
- 图结构(网状结构)(非线性结构)
存储结构
- 顺序存储结构(要用索引)
- 链式存储结构(要用指针)
3.算法
特性
- 有穷性:不能死循环
- 确定性
- 可行性
- 输入
- 输出
评价算法优劣的基本标准
- 正确性;
- 可读性;
- 健壮性;
- 高效性;
算法复杂度
(1)时间复杂度(重要)
分析方法:找出语句频度最大的语句(循环最深处),计算其因问题规模n而循环的次数f(n),取其数量级用符号“O”表示。
常见的算法复杂度:O(1)、O()、O(n)、O(
)、O(
)。(按数量级排序)
这里需要注意的是递归函数,通常情况下递归函数会在数量级上加n,这是主要是看递归操作中调用递归函数时,输入的实参相对于传进来的实参看少了多少。
例题:
#include <stdio.h>
void recursion(int n) {
if (n <= 0) {
return;
}
for (int i = 0; i < n; i++) {
printf("%d\n", i);
}
recursion(n-1);
}
int main() {
int n = 5;
recursion(n);
return 0;
}
这里的算法时间复杂度为 O(),其中循环最深处的语句为
printf("%d\n", i);
它一共是循环了5+4+3+2+1=15,若传入数据为n,则一共循环n+(n-1)+(n-2)……3+2+1=n*(n+1)/2
则:取其数量级,时间复杂度为 O(
)
(2)空间复杂度(不重要)
分析方法同上,只是f(n)计算的是因问题规模而需要的空间。
二、线性表
线性表的逻辑结构特性是指数据元素之间存在这线性关系,n个数据特性相同的元素构成的有限序列,就是线性表。
线性表有顺序表和单链表两种,这里主要讲解操作和如何做题,不进行代码展示。
1.顺序表
取指定位置的值
用索引寻找顺序表第i+1的空间的值,时间复杂度为O(1),这里注意题中索引从几开始,这里是索引为0开始。
查找元素位置
寻找元素在顺序表中的位置,这里要用循环方法对顺序表的元素进行比较,元素相同时返回此时顺序表的长度,这里在题中总是喜欢索引从1开始,在0处不存元素,使用此方法时将查找元素存入0的位置,从顺序表最后一个元素开始遍历(即索引为length的位置),若顺序表中有该元素,返回其位置,若没有返回0,此方法的时间复杂度为O(n)。
给指定位置插入元素
此方法的时间复杂度为O(n)。在顺序表中,如果要插入元素,则要将其指定位置后的元素都向后移动一个位置,将插入位置留下,若在尾部插入,则时间复杂度为O(1)。
删除指定位置元素
此方法的时间复杂度为O(n)。在顺序表中,如果要删除元素,则要将其指定位置后的元素都向前移动一个位置,将删除位置填上,若在尾部删除,则时间复杂度为O(1)。
注意:这里还有许多方法,如删除指定元素,这里要先进行查找,在进行删除,这里元素若在尾部,时间复杂度也为O(n),这里要具体情况具体分析。
2.单链表
单链表的元素在实际排序中可以不是相接的,,而是用指针的方式链接的,在单链表中没有索引,注意链式存储所占空间分为两个部分,一部分是数据域存放数据,一部分是指针域存放指针。要想会做链表的相关题型,你需要知道三个东西,一个是现在是哪个结点,二是它的前驱是谁,三是它的后继是谁。
概念补充与区分
- 首元结点:链表中存储第一个数据元素的结点
- 头结点:在首元结点前的一个假设的结点,可以不存储任何信息,也可以存入一些附加信息(链表中可以没有头结点,它的存在就是为了方便链表的一些方法的实现)
- 头指针:链表中第一个结点的指针,有头结点的情况头指针指向的是头结点,没有则指向的是首元结点
取指定位置的值
该方法的时间复杂度为O(n),这里要循环将指针移动到下一个结点,要查找第一个元素就要移动几次,核心代码为p = p->next。
查找元素位置
该方法的时间复杂度为O(n),原理同上,但需要进行比较。
插入元素
该方法的时间复杂度为O(n),先循环找到要插入的位置,然后进行插入操作,若要插入元素结点为s,要插入位置元素结点为p,则核心代码为s->next = p->next;p->next = s。
删除元素
该方法的时间复杂度为O(n),先循环找到要删除的位置,然后进行删除操作,若要删除的前一个位置元素结点为p,则核心代码为p->next = p->next->next。
3.常见题型
(1)计算顺序表中的存储地址
例题:
公式:第i个元素的存储地址 = A + (i-1) * S(假设顺序表的起始地址为A,每个元素占据的存储空间为S)
顺序表中第一个元素的存储地址是100,每个元素的长度为2,则第5个元素的地址是多少?
答案:108;
解析:100 + (5-1)* 2 = 108;
这里要注意的是二维数组的地址,若告诉你首地址元素地址,问你地址为多少的位置元素为什么,倒退公式,先求出i,然后一行一行的数就行。
(2)线性表操作问题
例题:
在含n个结点的顺序表中,算法的时间复杂度O(1)的操作是()
A.访问第i个结点和求第i个结点的直接前驱;
B.在第i个结点后插入一个新结点;
C.删除第i个结点;
D.将n个结点从小到大排序
答案:A;
解析:上述操作中的知识点,其中D的时间复杂度会在后面相关章节进行讲解。
(3)存储密度问题
存储密度是指数据元素本身所占的存储量和整个结点结构所占的存储量之比,顺序表的存储密度为1,单链表因为有指针域,则存储密度一定小于1
(4)选择线性表存储结构问题
公式:顺序表的存储效率低,查找效率高;单链表存储效率高,查找效率低
例题:
线性表L在什么情况下适用于使用链式结构实现?
A.需要经常修改L中的结点值;
B.需要不断对L进行删除;
C.L中含有大量的结点;
D.L中结点结构复杂;
答案:B;
解析:符合公式存储效率高。
(5)链表和特殊链表的插入删除
双向链表:多了一个指针prior指向前面的元素,一个结点可以向两边移动
循环链表:首位相连
例题一:
在双向循环链表中,在p指针所指的结点后插入q所指向的新结点,其修改指针的操作是()。
A.p->next=q; q->prior=p; p->next->prior=q; q->next=q;
B.p->next=q; p->next->prior=q; q->prior=p; q->next=p->next;
C.q->prior=p; q->next=p->next; p->next->prior=q; p->next=q;
D.q->prior=p; q->next=p->next; p->next=q; p->next->prior=q;
答案:C;
解析:
- q->prior=p; //将结点x的前驱指针指向结点p
- q->next=p->next; //结点x的后继指针指向结点b
- p->next->prior=q; //结点b的前驱指针指向结点x
- p->next=q; //结点a的后继指针指向结点x
例题二:
在双向链表存储结构中,删除p所指的结点时须修改指针()
A.p->prior->next=p->next; p->next->prior=p->prior;
B.p->next=p->next->next; p->next->prior=p;
C.p->prior->next=p; p->prior=p->prior->prior;
D.p->prior=p->next->next; p->next=p->prior->prior;
答案:A
解析:
- p->prior->next=p->next; //将结点a的后继指针指向结点c
- p->next->prior=p->prior; //将结点c的前驱指针指向结点a
- 注意:这里两个语句前后调换也是可以的
(6)头结点有无的问题
常见题型举例:
- 对于一个头指针为head的带头结点的单链表,判断该表为空表的条件是head->next==NULL
- 对于一个头指针为head的不带头结点的单链表,判断该表为空表的条件是head==NULL