《大话数据结构》笔记

本文概述了数据结构的起源与发展,介绍了基本概念、逻辑与物理结构,探讨了算法与数据结构的关系,详细讲解了线性表、栈、队列、树和图的基础理论与典型应用,包括排序、查找、哈希表等内容,以及常见算法的时间复杂度分析。
摘要由CSDN通过智能技术生成

link:github链接

1.数据结构绪论

1.3数据结构起源

数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科。

1.4基本概念和术语

数据/数据元素/数据项/数据结构

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

也就是数据的组织形式

1.5逻辑结构和物理结构

逻辑结构:集合,线性,树形,图形

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

顺序存储和链式存储

1.6抽象数据类型

ADT abstract data type

指一个数学模型和在该模型上的一组操作

2.算法

算法:解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示为一个或多个操作。

2.2数据结构与算法关系

谈到算法是为了更好的理解数据结构

2.5算法的特性

输入(0/多)、输出(1/多)、有穷性(不会无限循环)、确定性(无二义性)、可行性(可编程)

2.6 算法设计的要求

正确性:语法,对合法的input产生正确的output,对不合法的input产生说明,考虑极端情况

可读性:便于阅读理解和交流

健壮性:不合法的input不会招致崩溃

时间效率高,存储量低

2.7算法效率的度量方法

事后统计/事前分析

与问题输入规模n的关系

2.8函数的渐进增长

判断算法效率时,系数和常数不重要,更关注最高阶项的阶数

2.9算法时间复杂度

大O记法:最高阶项,无系数

常数阶O(1)/线性阶O(n)/平方阶/对数阶

2.10常见的时间复杂度

1<logn<n<nlogn<n*n<2n<n!<nn

2.11最坏情况与平均情况

无特指则分析最坏时间复杂度

2.12算法空间复杂度

可以以空间换时间

分析算法所用的辅助单元

3.线性表

零个或多个数据元素的有限序列

3.3线性表的抽象数据类型

初始化,是否为空,清空,按位置索引,按值定位,插入,删除,返回个数

3.4线性表的顺序存储结构

用一段地址连续的存储单元依次存储线性表的数据元素

描述顺序存储结构需要三个属性:起始位置,最大存储容量(capacity),当前长度(size)

数据的长度:sizeof 所占字节数

线性表长度:元素个数,size

线性表,随机存取结构,存取性能时间O(1)

3.5顺序存储结构的插入与删除

插入:插入位置之后的元素全都要后移,O(n)

删除:删除位置之后的元素全都要前移,O(n)

优缺点:

优点:无需为元素之间的逻辑关系额外增加存储空间,快速存取

缺点:插入删除复杂度高,长度变化时,难以确定空间容量,造成存储空间碎片。

3.6线性表的链式存储结构

特点:用一组任意的存储单元存储线性表数据元素,存储地址可以不连续。每个节点存储数据和后继节点的地址。

数据域/指针域

头指针:指向头节点的指针,,常为链表的名字,必不为空

头节点:dummy head,数据无意义,指针只想第一个数据元素,用来统一第一数据节点与其他节点的操作。不是必须要素。

p->data

p->next

3.7单链表的读取

读取特定元素需要从头节点开始找

复杂度O(n)

因为链表没有定义长度,多以只能用工作指针后移

3.8单链表的插入与删除

将s插入到p和p->next中:

new listnode s;

s->next=p->next;p->next=s;

将p->next从p和p->next->next中删除:

q=p->next;p->next=q->next;

free(q);

对于频繁插入/删除数据项的操作,链表相对于vector优势明显

3.9单链表的整表创建

生成节点-》插入

3.10单链表的整表删除

删除节点,释放节点

3.11链表与vector的优缺点

频繁查找用数组,频繁插入删除用list

数目变化大用list

3.12 静态链表

对于没有指针的语言,可用数组实现静态链表。每个数据节点存储数据和下一个元素的index。

插入/删除:将未被使用和已被删除的节点组成备用链表

优缺点:与数组相比,插入删除复杂度降低,但是未解决表长度不确定的问题以及失去了数组 O(1)存取的特性

3.13循环链表

尾节点指针指向头节点

3.14双向指针

data

*next

*prev

4.栈与队列

栈是限定仅在表尾进行插入和删除操作的线性表

队列是尾部插入,头部删除的线性表

4.2栈的定义

栈顶:允许插入删除

后进先出,LIFO,last in first out

入栈(压栈),出栈

4.3栈的抽象数据类型

操作:初始化,销毁,清空,判断是否为空,读栈顶,pop,push,size

4.4栈的顺序结构存储及实现

数组尾端做栈顶

4.5两栈共享空间

从数组的两端向中间靠拢,每次push检查两个栈顶会不会相遇

条件:两个栈的空间需求相反,数据类型相同

4.6栈的链式存储结构及实现

头指针作为栈顶指针,头结点不需要了

C语言会用链栈,因为没有动态数组

4.7栈的作用

简化程序设计,思考范围缩小,聚焦问题核心,高级语言基本都有栈的封装

4.8栈的应用–递归

斐波那契数列:兔子繁殖,出生两个月后,每对生一对

斐波那契数列直接递归

递归函数:直接调用自己或者间接调用自己的函数

递归函数要有终止条件

迭代使用循环结构

递归使用选择结构

迭代代码易懂,更消耗时间和内存(调用函数,栈帧开销)

调用时,函数的局部变量,参数值,返回地址都被压入栈中。

4.9栈的应用–四则运算表达式求值

后缀(逆波兰)表示法 RPN reverse polish notation

括号成对出现,用栈很合适

所有的符号在要运算的数字的后面出现,去掉了括号

运算规则:遇到数字进栈,遇到符号,栈顶弹出两个元素进行运算

中缀表达式转后缀表达式规则:数字输出,符号则判断与栈顶符号的优先级,右括号或优先级低于栈顶符号,则栈顶元素依次出栈(当前的是右括号,则截止到左括号出栈)输出,当前符号进栈。

计算机处理中缀表达式:1.转后缀2.后缀计算

4.10队列的定义

queue,fifo

先进先出,只可以在尾部插入,头部删除

应用:操作系统,客服系统

顺序存储:头指针队头元素,尾指针队尾元素的下一个位置,则front=rear时,空队列

假溢出?:循环队列解决

防止front=rear时有满或空两种情况:将满定义为front与rear差1时

队列满的条件:(rear+1)%queuesize==front

队列长度公式(rear-front+queuesize)%queuesize

时间复杂度不高,但面临数组溢出的问题

4.13 队列的链式存储结构及实现

单链表,只能尾进头出

空间上,链队列更灵活,时间上,循环队列更快些

5.串

string:字符串,0/多个字符组成的有限序列

空串

串的相邻字符有前驱和后继的关系

子串在主串中的位置时子串第一个字符在主串中的序号

串的比较:

更小:串头相同,更短者;第一个不同的字符ascii编码更小者。

线性表更关注单个元素操作:增删改查

串更多的是查找字串位置,得到指令位置子串,替换子串等

串的结尾有\0表示结束

c中串操纵为了防止溢出注意malloc和free

5.6朴素的模式匹配算法

子串的定位操作同非常乘坐串的模式匹配。

朴素:主串遍历,每个位置作为起始位置与子串尝试匹配,n主串长度,m子串长度,最坏O(n-m+1)*m

5.7KMP模式匹配算法

避免重复遍历

设计一个next数组表征子串自身的重复性,用来跳过不必要的比较。

next数组设计:

next【0】=0;

next【1】=1;

next【i(》=2)】看【0:i-1】的串的前缀和后缀有几个重和,n个重合则值为n+1

比较不等的情况下,子串指针通过next数组回溯,减少复杂度。o(m+n)

主串与子串之间存在许多“部分匹配”的情况下有优势。

next数组可改良解决子串头长重复字符的问题。将next【0】传递给身后与他相同字符对用的next值

改进的kmp:在计算next的同时,如果a位字符与他next指向的字符相等,则a的nextval指向b的nextval,不等则和naxt一样。

回文,s==reverse(s)

6.树

树:一个root,子树不相交

一对多的数据结构

度:节点拥有的子树数目

节点:根节点/内部节点(度>0)/叶节点(度为0)

节点之间的关系:子节点/父节点/兄弟节点/祖先/子孙

深度:root深度最浅

森林:互不相交的树的集合

6.4 树的存储结构

一个存储结构设计的是否合理,取决于该存储结构的运算是否合适,是否方便,时间复杂度好不好。

孩子兄弟表示法

node:data | first child | right brother

将树转化为二叉树

6.5 二叉树的定义

左子树和右子树次序不能任意颠倒

斜树:左/右斜树

满二叉树:内部节点度为2. 同等深度的二叉树里,满二叉树节点数最多。

完全二叉树:构造满二叉树的中间状态

6.6 二叉树的性质

1.第i层最多有2^(n-1)个结点

2.深度为k的二叉树,最多有2^k - 1 个结点

3.叶节点数=度为2的节点数+1

4.完全二叉树深度【log2N】 +1 //【】,向下取整

完全二叉树的层次排列:节点i,【i/2】是其父节点,2i是其左孩子,2i+1是其右孩子

6.7 二叉树的存储结构

完全二叉树可以用数组存储,普通的也可以,将不存在的节点用^无效数据表示来占坑

链式存储:data+*左孩子 + *右孩子 (还可以指向父节点,三叉链表)

6.8 遍历二叉树

前/中/后 序遍历:指访问根节点的次序,左一直在右前。比如后序遍历,左右中。

层次遍历

遍历方法把树的节点变成某种意义的线性序列。

前中后的遍历和迭代写法

根据前序和中序 或者 后序和中序 可以推导出另一种遍历顺序。但是前序+后序不能确定一颗二叉树。

6.9 二叉树的建立

递归,生成节点(new/malloc),节点赋值

6.10 线索二叉树

创建时就记住前驱和后继。

利用空地址(n个节点有n-1个边,但是有2n个指针域)来存储某种遍历次序下的前驱和后继节点的位置。

指向前驱和后继的指针称为线索。

线索二叉树是将二叉树转化为了双向链表

线索化

节点中增加两个bool变量,ltag和rtag,表征当前左右指针是否是左右孩子。

线索化就是在便利的过程中修改空指针,将空指针改为指向前驱或者后继。

应用场景:二叉树经常需要某种遍历序列的前驱和后继。

6.11 树、森林与二叉树的转换

树–》二叉树:长子兄弟表示法

森林–》二叉树:各自转成二叉树,后续树的根作为右孩子接到前边。

6.12 赫夫曼树及其应用

压缩编码方法:赫夫曼编码

根据数据出现的频率来编码,将频率高的数据用更短的bit来编码,前缀编码,所有编码不是另外编码的前缀。

最优树/赫夫曼树:带权路径长度最小的二叉树

书的路径长度是根到每一结点的路径长度之和

赫夫曼编码需要接收端配合

7.图

图:顶点和边构成的集合。

图形结构中,节点之间的关系是任意的。

线性表–》元素,树–》结点,图–》顶点vertex

图中不允许没有顶点,顶点集合有穷非空,边集可以是空的

各种图定义:无向边,有向边(弧)

无向边用()来表示,有向边用<>来表示

简单图:没有顶点到其自身和重复的边

无向图中,如果任意两个顶点都存在边,则成称为无向完全图。n顶点–》n*(n-1)/2

稀疏图/稠密图

带权重的图通常称为网

子图,subgraph

顶点之间,相邻接,边与顶点相关联(依附)。顶点的度(degree)是与其相关联的边数。

有向图:入度和出度

顶点到顶点的路径不唯一

回路/环/简单环(只有头尾重复)

任意两个顶点都有路径(连通的),称为连通图。

连通分量:极大连通子图

有向图:强连通图,强连通分量

连通图的生成树:极小连通子图,包含n个顶点,n-1条边,构成树

7.4 图的存储结构

邻接矩阵:顶点用一维数组存储,边用二维邻接矩阵存储

无向图的边数组是一个对称矩阵

邻接矩阵处理稀疏图很浪费–》邻接表

邻接表:把数组和链表相结合的方法。

数组:存储顶点和第一个邻接点的指针,所以数组的每个entry都构成链表。

对于带权值的网,节点定义中再加一个weight即可

单链表创建:头插法

对于有向图来说,将邻接表(出度)和逆邻接表(入度)结合起来,十字链表(orthogonal list)

十字链表,顶点表节点结构:data|firstin|firstout

边表结点结构:tailvex|headvex|headlink|taillink

十字链表,容易求得顶点的入读和出度

邻接多重表:关注无向图的对边的操作,一条边在边表中用两个节点表示

边集数组:两个一维数组组成,一个存储顶点的信息,另一个存储边的信息

7.5 图的遍历

从图中某一顶点出发,遍历其他顶点,且每个顶点被访问一次,叫做图的遍历。

深度优先遍历(类似于树的前序遍历)/广度优先遍历(类似于树的层次遍历)

深度优先搜索+回溯,遍历图的顶点

对于点多边少的稀疏图来说,邻接表使算法的时间空间效率大大提高

bfs和dfs在时间复杂度上相同,不同的是对顶点访问顺序的不同

根据应用场景选择深度优先或者广度优先

7.6 最小生成树

构造连通网的最小代价生成树称为最小生成树。

普利姆算法(稠密图有优势),克鲁斯卡尔算法(稀疏图有优势)

prim算法:以顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树。

kruskal算法:找最小权值的边来构建,利用边集数组结构

7.7 最短路径

迪杰斯特拉算法:dijkstra on*n

按路径长度递增的次序产生最短路径。一步步求出起点到终点之间顶点的路径。

弗洛伊德算法:floyd

二重循环初始化+三重循环权值修正

7.8 拓扑排序

有向无环图,顶点表示活动,弧表示活动之间的优先关系,AOV网。

拓扑排序,其实就是对一个有向图构造拓扑序列的过程。

7.9 关键路径

对一个流程图获得最短时间,就要分析他的拓扑关系,找到当中最关键的流程。

8.查找

查找表

关键字

静态查找表:

动态查找表:可做插入删除

编程优化:在查找方向的尽头放置哨兵,辨别错误

二分查找,斐波那契查找

8.5 线性索引查找

索引就是把一个关键字与它对应的记录相关联的过程。

线性索引 树形索引 多级索引

线性索引,索引表:稠密索引,分块索引,到排索引

稠密索引:数据集的每个记录对应一个索引项。索引项按关键码有序。

分块索引:对数据集分块,使其分块有序,然后对每个块建立索引。块内无序,快间有序。索引表的条目存储,最大关键码,块长,块首指针。查块用二分,块内遍历 。复杂度 根号n

倒排索引:由属性值查找该属性值的各记录地址,因此称为倒排。即建立一张表,记录此关键码和出现过次关键码的指针或者主关键码。(搜索引擎)

8.6 二叉排序树

二叉搜索树,左《root《右

插入删除:旋转,3+4操作

8.7 平衡二叉树(AVL树)

平衡因子:左子树高度-右子树高度,只会为-1,0,1

查找/插入/删除:o lgn

8.8 多路查找树(B树)

为了避免频繁访问磁盘而设计。

打破一个节点只存储一个元素的限制。

多路查找树multi-way-search-tree:每一个节点的孩子数可以多于两个,且每个节点可以存储多个元素。查找树,元素之间存在某种特定关系。

2-3树,2-3-4树,B树,B+树

2-3树:每个节点具有两个孩子或者三个孩子

2结点,1元素+2孩子,类似二叉搜索树,不过两个孩子同时出现或者同时缺失。

3结点,2元素+3孩子,同样有序

2-3插入的传播效应导致了根节点的拆分,则树的高度就会增加。

2-3-4树:还有4结点,3元素+4孩子

B树平衡的多路查找树,2-3,2-3-4,是B树的特例。结点最大的孩子数组称为B树的阶。

在B树的查找过程,就是顺指针查找结点和在节点中查找关键字的交叉过程。

B树如何减少访问外存的次数?

使B树的阶数和硬盘的页面大小匹配,采用类似页式管理的方法来管理数据结构,让根节点贮存在内存中,大大减少访问外存的次数。每次磁盘访问都可以获得最大数量的数据。

B树的数据结构就是为了内外存的数据交互准备的。

B+树:B树对下层结点的遍历需要频繁访问根节点。

改进:节点中增加一个指向中序遍历后继的指针(上一层节点中出现过)

8.9 散列表查找(哈希表)概述

直接通过key找到value

value/value的位置和key有映射关系。

散列表/哈希表

散列:面向查找的数据结构,记录之间没有逻辑关系。

存储和查找:通过哈希函数处理key,计算存储地址

设计一个简单/均匀/存储利用率高的散列表:

如何处理冲突?

8.10 散列函数的构造方法

好的散列函数:计算简单+地址均匀

常用方法:

1.直接定址法:线性函数:y=a*key+b

需要事先知道关键字的分布,适合查找表较小且连续的情况。

2.数字分析法:抽取数的某些位并作各种操作(环移,反转,互加)形成地址

通常适合处理key的数字位数较大的情况,也要求key均匀

3.平方取中法:平方,再取中间某些位

适合不知道关键字分布,且位数又不是很大的情况。

4.折叠法

将key从左到右分割成几段,叠加再取后几位

实现不需要知道key的分布,适合key位数较多的情况。

5.除留余数法:最常用

y=k mod p(p小于表长,最好为质数)

6.随机数法

y=random (key),适合key的长度不等时

选择散列函数参考:计算时间,关键字长度,散列表大小,关键字分布,查找频率

8.11 处理散列冲突的方法

1.开放定址法:

线性探测法,冲突了寻找临近位置是否为空并放入。

会出现堆积现象(不冲突的两个key争夺地址)

二次探测法:寻找新位置的步长由1-2-3改为1,-1,4,-4,9.。。两个方向,步长平方增长

随机探测法:探测的步长随机生成(查找时用相同的随机种子,所以没问题)

开放定址法在散列表未填满时,总能找到不发生冲突地址。常用。

2.再散列函数法:事先准备多个散列函数,发生冲突时,更换散列函数。

3.链地址法:将冲突的数据挂在同一个单链表上,冲突时加节点

4.公共溢出区法:将冲突的数据统一放置于溢出区

8.12 散列表查找实现

散列查找取决于:

散列函数是否均匀,处理冲突的方法,散列表的装填因子(利用率)

9.排序

排序的稳定性:两个相等的值排序完前后相对位置没变,则称为稳定。

内排序/外排序:排序过程中,待排序记录是否全部放置在内存中。

排序算法主要受:时间性能(比较和移动),辅助空间,算法时间复杂性

简单算法:冒泡,简单选择,直接插入

改进算法:希尔排序,堆排序,归并排序,快速排序

冒泡排序:两两比较相邻记录的关键字,如果反序则交换,知道没有反序的记录为止。

冒泡优化:增加flag,在i循环时判断,若某一遍i循环里没有发生交换,直接退出

选择排序:冒泡的改进,交换次数少,每次在未排序序列中找到最值,与未排序头交换,已排序+1,未排序-1。

On2 不稳定

插入排序:构建有序序列,对于未排序元素,从后往前扫描查找插入位置,找到后插入。

on2,在stl中作为快排的补充,数据较少时选择插入排序。数据基本有序速度很快。

希尔排序:

插入排序改进,插入排序每次与前面一个比较,然后再往前一个,而希尔排序每次往前K个。增量逐渐减小。

当增量为1的时候,希尔排序与插入排序就完全是一样的过程;

增量的选择比较关键,不稳定。

堆排序:对选择排序的改进

堆:root是最值,用数组表示堆

构建堆,弹出顶,重新构建堆

归并排序:采用分治思想,递归分成小的子序列,使子序列有序后再合并得到完全有序的序列

快速排序:选择基准,分区,递归处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值