程序=数据结构+算法
阅读说明:- 本文是 《大话数据结构》这本书的阅读笔记,也就是说我写的这些,书中都有,想要看更详细的,可以下载原书看。原书的百度网盘:密码:u942;
- 只记录原理和思维,不涉及编码实现。想要找代码实现的小伙伴们,这篇文章里面没有哦;
- 如有不正确的地方,欢迎指正。
(大家一起进步丫)
第二章 - 算法
1.什么是算法?
Ans:算法是 解决特定问题 求解步骤 的描述。
2.算法的复杂度分为哪两种?
Ans:时间复杂度 和 空间复杂度。一般而言,提算法复杂度,是指算法的时间复杂度。
3.常用的时间复杂度所耗费的时间从小到大依次为?
Ans: O ( 1 ) < O ( l o g n ) < O ( n ) < O ( n l o g n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1)<O(log_n)<O(n)<O(nlog_n)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n) O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
第三章 - 线性表
1.线性表的存储方式?以及各存储方式各自的优缺点。
Ans:存储方式有两种:顺序存储和链式存储。
2.单链表相关问题
3.1 单链表
3.1.1 相关概念
- 数据域:存储数据元素信息的域
- 指针域:存储直接后继位置的域
- 指针(链):指针域中存储的信息
- 结点(存储映像):数据域+指针域
- 头指针:链表中第一个结点的存储位置
-
头结点:在单链表的第一个节点前附设一个结点
头结点的数据域可以不存储任何信息。也可以存储如线性表的长度等附加信息。
3.1.2 读取
核心思想:工作指针后移。(事先不知道要循环多少次【没有定义表长】,所以不可以用for循环)
简单来讲:从头开始找,直到找到第 i 个元素为止。该算法的时间复杂度取决于 i 的位置。最坏情况的时间复杂度为 O(n)。
3.1.3 插入
Question:假设存储元素 e 的结点为 s ,要实现结点p、p->next 和 s 之间逻辑关系的变化,只需要将结点 s 插入到结点 p 和 p->next 之间即可。如何插入呢?
Ans:只需要让 s->next 和 p->next 的指针做一点改变即可。
特殊情况,对于单链表的表头和表尾的数据插入,操作是相同的!
总结
3.1.4 删除
变换指针即可!!!
对于插入和删除 操作越频繁的线性链表,采用链式存储越方便!!!
3.1.5 整表创建
-
顺序存储结构的创建,其实就是一个数组的初始化,即声明一个类型和大小的数组并赋值的过程。
-
链表存储结构不像顺序存储结构那么集中,是一种动态结构。
-
对于每个链表来说,它所占用空间的大小和位置是不需要预先分配划定的,可以根据系统的情况和实际的需求即时生成。
头插法:
简要理解:最新的元素在链表的最“前”面!!!(但是,传统思维是 最新的元素要在链表的末尾!)
示意图见下:
尾插法:
即:最新的元素在链表的末尾!!!(待补充!!!)
3.1.6 整表删除
3.2 静态链表
3.2.1 相关概念
- 用数组代替指针来描述单链表
- 数组由两个数据域组成,data 和 cur ,数据域data,用来存放数据,游标 cur 相当于单链表中的 next 指针,存放该元素的后继在数组中的下标。
- 也可称 游标实现法
图片解释:
- 对数组第一个和最后一个元素作特殊处理,不存储数据。
- 第一个元素:即下标为 0 的元素的 cur 中存放备用链表的第一个结点的下标
- 数组的最后一个元素:该cur中存放第一个有数值的元素的下标,相当于单链表中的头节点的作用。
- 备用链表:未被使用的数组元素
举个例子:
- 其中:Δ 作为下标为 0 的元素,其cur中存放备用链表(空)的第一个结点的下标,即为7(如图所示);
- 数组的最后一个元素 庚 ,该cur中存放第一个有数值的元素的下标,即为 0(如图所示).
3.2.2 插入
核心:每当进行插入时,从备用链表上取得第一个结点作为待插入的新结点。
Q:如图,想要将 丙 插入到 乙 和 丁 之间,如何操作?
Ans:
- 首先将 丙 放在下标为 7 的位置,即备用链表第一位;
- 更新 乙 的 cur 为 7 ;
- 更新 丙 的cur 为 3,即 丁 所在的位置。
插入成功!!!示意图如下!
3.2.3 删除
核心:使得删除的位置成为第一个优先空位,把它存入第一个元素的cur中。
了解了静态链表的插入,静态链表的删除也就方便理解了。
Q:链表如上图所示,此时,删除 甲 ?如何操作?
Ans:
- 首先更新下标为 0 的元素的 cur,更新为 甲 的下标;
- 更新 已删除元素甲 的 cur 为 备用链表 的第一个元素的 下标,本图中为8.
删除成功,示意图如下。
3.2.4 优缺点
3.3 循环链表
3.3.1 相关概念
- 解决的问题:如何从当中一个结点(任意一个几点)出发,访问到链表的全部结点。
- 空循环链表 示意图
- 非空循环链表 示意图
3.3.2 双表合并
Q:合并如图所示 两个 循环链表,如何操作?
Ans:
3.4 双向链表
3.4.1 相关概念
- 定义:双向链表就是在单链表的每个结点中,再设置一个指向其前驱节点的指针域。
- 双向链表中的结点都具有两个指针域,一个指向直接后继,另一个指向直接前驱
- 双向链表的循环带头结点 的 空链表 示意图
- 非空循环 双向链表 示意图
3.4.2 插入
Q:假设存储 s 的结点为 s ,要实现将结点 s 插入到结点p 和 p->next 之间,如何操作?
Ans:
s - >prior = p ; /*把 p 赋值给 s 的前驱*/
s - >next =p - >next; /*把 p->next复制给s的后继*/
p - >next ->prior = s; /*把 s 赋值给p->next的前驱*/
P - >next = s;/*把 s 赋值给 p 的后继*/
注意顺序:
先搞定 s 的前驱和后继,再搞定后结点的前驱,最后搞定前结点的后继!
3.4.3 删除
Q:删除P结点,如何操作?
Ans:两步
p->prior->next=p->next;/*把p->next赋值给p->prior 的后继*/
p->next->prior=p->prior;/*把p->prior赋值给p->next的前驱*/
第四章 栈与队列
4.1栈的顺序存储结构
1. 栈的最简单的理解
先进后出
2. 是不是先进栈的元素,只能是最后出栈呢?
当然不是!
书上的例子:假设有3个整型数字元素1、2、3依次进栈,会有哪些出栈次序?
- 1、2、3进,然后3、2、1出。出栈次序为321
- 1进,1出;2进,2出;3进,3出。出栈次序为123.
- 1进,2进,2出,1出,3进,3出。出栈次序为213
- 1进,1出;2进,3进,3出,2出。出栈次序为132
- 1进,2进,2出,3进,3出,1出。出栈顺序231
可见,只是3个元素,就有五种可能的出栈次序,如果元素数量多,出栈的顺序将更多。
3. 栈最大的缺陷?
必须事先确定数组存储空间大小,万一不够用,需要编程手段来扩展数组的容量,比较麻烦
- 两栈共享空间
前置条件:
- 两个相同类型的栈,
- 并为它们开辟了数组空间
可能出现一种情况:栈A满了,栈B还有很多存储空间剩余。
那么,如何解决上述问题呢?
Ans:用一个数组来存储两个栈。但是需要一些技巧。
具体操作:
1. 数组有两个端点,两个栈有两个栈底(bottom)
2. 让栈A的栈底为数组的始端,即下标为0处;
3. 栈B的栈底为数组的末端,即下标为 n-1 处;
这样的话,两个栈如果增加元素,都是像中间延申。
Q:什么时候栈满?
Ans:栈A和栈B的头指针相差1
4.2 栈的链式存储结构
也称:链栈
- 结构
- 插入操作
假设元素值为 e 的新结点是 s ,top 为栈顶指针。示意图如下:
- 出栈 - 删除 操作
- 链栈的优点
- 基本不存在栈满的情况;
- 不需要实现确定 长度;
- 存取时定位方便。
- 链栈的缺点
- 相比于 顺序栈 ,可能存在内存空间浪费的情况;
- 链栈 每个元素都有指针域,增加了内存开销。
顺序栈和链栈的选择
- 栈的使用过程中,元素变化不可预料,时大时小,最好使用链栈;
- 如果变换在可控范围,顺序栈较好。.
4.3 栈的应用
4.3.1 递归
递归的定义
- 直接调用自己 或者 通过一系列的调用语句间接的调用自己 的函数 ;
- 每个递归定义必须至少有一个条件,满足时 递归不再进行,即不再引用自己而是返回值退出。
- 斐波那契数列
特别经典的兔子问题:
- 迭代 和 递归 的区别:
- 迭代使用的是 循环结构;
- 递归使用的是 选择结构
4.3.2 四则运算表达式求值
加减乘除 - 括号
逆波兰表示(后缀表达式)
9 + (3 - 1) * 3 + 10 ÷ 2
# 变换为
9 3 1 - 3 * + 10 2 / +
####
计算准则:
1.数字进栈
2.符号,则将处于栈顶的两个数字出栈,进行运算,运算结果进栈
####
9
3
1
计算 3-1
9
2
3
计算 2*3
9
6
计算 9+6
15
10
2
计算 10/2
15
5
计算 15+5
中缀表达式变为后缀表达式
9 + (3 - 1) * 3 + 10 ÷ 2
# 变换为
9 3 1 - 3 * + 10 2 / +
####
变换准则:
1.遇到数字输出
2.遇到符号,判断其与栈顶符号的优先级,若其是 右括号 或 优先级低于栈顶 ,则栈顶元素依次出栈并输出。并将当前符号进栈。
####
9 + (3 - 1) * 3 + 10 ÷ 2
栈内容:
+
( #输出:9 3
- #输出:9 3 1
) #输出:9 3 1 -
--------------------
+ #输出:9 3 1 -
* #输出:9 3 1 - 3 因为*的优先级高于+,所以+不出栈,*进栈
--------------------
+ #输出:9 3 1 - 3 * + 10
/ #输出:9 3 1 - 3 * + 10 2 / +
4.4 队列
先进先出
概念:
1.空队列:队首和队尾重合
4.4.1 队列顺序存储
常规存储
出队列 - 常规
出队列 - 不全部移动
容易出现‘假溢出’问题:即队列前面都是空的,后面满了。
循环队列
存在问题:rear=front
时,不是空队列,而是满队列。(见下图)
分辨 空队列 和 满队列
解决方式:
1.设置一个标志位,如front=rear,且flag=1时,队列为满。front=rear且flag=0时,队列为空:
2.队列为空,front=rear;队列满,使得front和rear相差1
4.4.2 队列链式存储
此处不做多余介绍,类似 单链表
第五章 串
一些定义:
空串
空格串:只有空格
5.1 朴素模式匹配算法
S="goodgoogle" T="google"
###朴素匹配
1.从主串S的第一位开始,S和T的前3位都匹配,但是第4位不匹配
2.主串S的第二位 o 和 T的第一位不匹配,失败
....
5.主串S的第5位,和T的第一位匹配,依次匹配。匹配成功。
5.2 KMP模式匹配算法
是对朴素模式匹配的一种改进。(需要单拉出来记一下)待补充!!!
S="abcdefgab" T="abcdex"
###
朴素匹配:匹配到第6个,发现不一样
第六章 树
定义:
1.根结点
2.叶结点(也称终端结点)
3.分支结点(也称非终端结点)
4.度:结点拥有的子树数
5.深度(也称高度):树中结点的最大层数
6.有序树:树中结点的各子树从左到右是有次序的,不能互换
7.森林:m 棵互不相交的树的集合
6.1 树的存储结构
存储结构
- 顺序存储
- 链式存储
6.1.1 双亲表示法
每个结点,附设一个指示器指示其双亲结点到链表中的位置。(结点除了知道自己是谁,还知道它的双亲在哪里)
其中:data
是数据域;parent
是指针域,存储改结点的双亲在数组中的下标
约定 根结点 的指针域 为 -1
例子如下:
6.1.2 孩子表示法
- 把每个结点的孩子结点排列起来,以单链表形式存储,n个结点有n个孩子链表
- 如果是叶子结点,此单链表为空;
- n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。