数据结构学习

文章目录

线性表

顺序存储

数组,适合元素个数较稳定,多存取少删改的情景。
内存需要预先分配,占用连续内存空间。

链式存储

单链表

指针域+数据域=节点
适合多增删少存取的情景,建立:头插&尾插法。

静态链表

对于无指针的语言,类似单链表的思路实践于数组中,通过游标实现。

例题: 快速找到位置长度单链表的中间节点
1、遍历一遍确定长度L,再从头结点出发循环L/2次,O(1.5L)
2、利用快慢指针,快指针移动速度是慢指针的两倍,快指针指向末节点时,慢指针正好位于中间,O(0.5L)

循环链表

单链表不从头结点出发,就无法访问到全部结点。
循环链表将单链表的末指针由空指针改为指向头结点,得到循环链表。头结点不一定要有。
可用O(1)的时间访问到最后一个元素。
eg: 连接两个循环链表、判断单链表中是否有环(用两个指针)。

例题:约瑟夫环 n个人围一圈,每次数到m的倍数出列,最后剩下? 用循环链表模拟。

双向链表

有前驱和后继两个指针,空间代价换时间。

栈stack

后进先出的特殊线性表,要求只在表尾进行插入和删除。
表尾:栈顶 top 表头:栈底 bottom
插入:push 进栈 压栈 入栈
删除:pop 出栈 弹栈

栈的顺序存储

《数据结构学习》

栈的链式存储

队列

循环队列

递归

。。。。浏览器卡住,刚刚写的都没了T.T

汉诺塔

经典:汉诺塔,c++代码
PS:感觉汉诺塔和九连环有点像

点击链接查看C++代码

八皇后问题

在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法?

点击链接查看C++代码

字符串

子串与主串关系类似子集。
字符串通常研究匹配、存储等。

BF 算法

匹配时不断后移,效率较低

KMP 算法

避免不必要的回溯,问题由模式串决定,而非目标串决定
找到一个讲的比较好的: 阮一峰
首先,要了解两个概念:“前缀”和”后缀”。 “前缀”指除了最后一个字符以外,一个字符串的全部头部组合;”后缀”指除了第一个字符以外,一个字符串的全部尾部组合。

“部分匹配值”就是”前缀”和”后缀”的最长的共有元素的长度。以”ABCDABD”为例:(next数组)
《数据结构学习》

- ”A”的前缀和后缀都为空集,共有元素的长度为0;

- ”AB”的前缀为[A],后缀为[B],共有元素的长度为0;

- ”ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

- ”ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

- “ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1;

- “ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为2;

- ”ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

移动位数 = 已匹配的字符数 - 对应的部分匹配值(最后一个匹配字符)

“部分匹配”的实质是,有时候,字符串头部和尾部会有重复。比如,“ABCDAB”之中有两个”AB”,那么它的”部分匹配值”就是2(”AB”的长度)。搜索词移动的时候,第一个”AB”向后移动4位(字符串长度-部分匹配值),就可以来到第二个”AB”的位置。

KMP算法的改进

如目标串:a a a a b c d e
子串: a a a a a x
用KMP算法,到第五位以后每次后移一个,但是前面都是a,不会 跟b匹配成功。
在计算next数组即移动位数时,若与前一个元素相同,则回溯到他的next值。

linux的tree命令可以显示目录树结构。
线性表是一对一,而树可以研究一对多的关系(普通家族关系可以理解为树吧)。
tree是n个结点的有限集,n=0为空树:

  • root有且仅有一个
  • n>1时,其余结点又可成为subtree
  • subtree数量没有限制,但相互不会相交
  • 结点拥有的子树数成为度 degree,度为0的结点为叶leaf或终端结点
  • child parent sibling 关系

树的存储结构

child parent sibling 关系

双亲表示法

以双亲作为索引的关键词:
假设以一组连续空间存储树的结点,在每个结点中,附设一个指示双亲结点在数组中位置的元素

孩子表示法

①根据树的度,声明足够空间存放子树指针的结点,但会造成空间浪费
②对①的改进,可对每个结点声明不同大小的空间,但初始化和维护成本高,造成时间浪费
③通过数组和链表的搭配 :子节点用链表,没有子节点用空指针
《数据结构学习》《数据结构学习》

双亲孩子表示法

在上面的表加一列,加上双亲索引信息
《数据结构学习》

二叉树 binary tree

二叉树是n个节点的有限集合,为空集或由一个根节点和两棵互不相交,分别称为根节点的左子树和右子树的二叉树组成。
这是递归形式的定义。

  • 每个结点最多两棵子树
  • 左右子树有顺序,不能颠倒
    例如,普通树如果有3个节点,只有两层或三层两种情况;但二叉树会有五种形态。

特殊二叉树

  • 斜树:倾斜方向始终一致
  • 满二叉树:所有分支结点都存在左子树和右子树,并且所有叶子都在同一层
  • 完全二叉树:若编号为 i (1<=i<=n) 的结点与同样深度的满二叉树中编号为 i 的结点位置完全相同,则这棵为完全二叉树
    叶子结点只能出现在最下两层;最下层的叶子一定集中在左下位置;倒数第二层若有叶子一定都在右部连续位置

同样结点数的二叉树,完全二叉树的深度最小。
满二叉树一定是完全二叉树,反之不一定。

二叉树的性质

画图很容易理解

  • 二叉树的 i 层最多有 2^(i-1) 个结点
  • 深度为 k 的二叉树最多有 2^k -1个结点
  • 任意二叉树,若终端结点数为 n0 ,度为2的结点数为 n2 ,则 n0=n2+1
  • 具有n个结点的完全二叉树深度为:floor( log2n)+1
  • 若对有n个结点的完全二叉树的结点按层序编号,对任意结点 i 有:
    ① i >1,则双亲是节点 floor(i/2)
    ② 若 2i>n ,则i 无左孩子,否则左孩子是结点 2i
    ② 若 2i+1>n ,则i 无右孩子,否则右孩子是结点 2i+1

二叉树的存储

顺序存储:用一维数组存储二叉树的各结点,可参考完全二叉树,不存在的用 ^ 代替
链式存储:顺序结构可能会浪费空间;为每个结点设计一个数据域和两个指针域

二叉树的遍历

从根结点出发,按某种次序依次访问二叉树中所有结点,每个结点仅被访问一次。
主要有四种:前序、中序、后序、层序

  • 前序(根左右):根结点 – 前序遍历左子树 – 前序遍历右子树
  • 中序(左根右):根结点 – 中序遍历根结点的左子树 – 根结点 – 中序遍历右子树
  • 后序(左右根):从左到右 先叶子 后结点遍历左右子树,最后访问根结点
  • 层序:按层 从左到右遍历,思路最简单
if (t)   // 不为空指针则返回真
{
    visit(t->data,level);    //前序
    pre(t->lchild,level+1);
    pre(t->rchild,level+1);
}

中序、后序则调换语序。

线索二叉树

若对树用中序遍历,则每隔一个可有位置存放前驱后继指针。
如何识别是存放地址指针还是遍历前驱后继指针?可以再加一个状态变量。(空间换时间?)

树、森林、二叉树转换

普通树转化为二叉树:
连接兄弟,去除所有除长子的连线。 (结果只有左孩子)

森林转化为二叉树:
先将每棵树转化为二叉树,接着连接各根 (左右孩子都有)

二叉树转化为树、森林:
逆向,加线,去线

树与森林的遍历

树的遍历

先根遍历:先访问根结点,再依次先根遍历根的每棵子树
后根遍历:先依次遍历每根子树,再访问根结点

森林

类似树的遍历,分为前序遍历和后序遍历,对每棵树先根遍历和后根遍历

  • 树、森林的前序遍历和二叉树的前序遍历结果相同
  • 树、森林的后序遍历和二叉树的中序遍历结果相同

赫夫曼树

  • 把二叉树简化为叶子结点带权weight的二叉树
  • 计算每个结点的路径长度
  • 计算 WPL :树的带权路径长度,即所有叶子结点的带权路径长度之和。

WPL值越小说明二叉树树性能越优,最小的数是赫夫曼树。

构造方法:
从小到大、从左、从下至上摆放

赫夫曼编码是首个实用压缩编码方案
定长编码:如ASCII码
变长编码:单个编码长度不一致,根据整体出现频率调节
前缀码:没有任何码字是其他码字的前缀(贪心算法?)

最小生成树

prime 普利姆

最小生成树(MST):权值最小的生成树。
构造网的最小生成树必须解决下面两个问题:

1、尽可能选取权值小的边,但不能构成回路;

2、选取n-1条恰当的边以连通n个顶点;

MST性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。 

基本思想:假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:

在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。

此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。

Prim算法的核心:始终保持TE中的边集构成一棵生成树。

注意:prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边数目无关,而kruskal算法的时间复杂度为O(eloge),与边的数目有关,适合稀疏图。
demo:
《数据结构学习》

kruskal克鲁斯卡尔

假设连通网N=(V,{E})。则令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通分量。在E中选择最小代价的边,若该边依附的顶点落在T中不同的连通分量中,则将该边加入到T中,否则舍去此边而选择下一条代价最小的边,依次类推,直到T中所有顶点都在同一连通分量上为止。
《数据结构学习》

最短路径

Dijkstra迪杰斯特拉

O(n^2)

Floyd弗洛伊德

O(n^3)

拓扑排序

对一个有向图构造拓扑序列的过程

关键路径

ete(k)=lte(k) a(k)为关键路径

查找

静态查找

可用线性表结构,如排序、二分

顺序查找

遍历,O(n)

插值查找

折半查找O(log2n);按比例查找,在数据比较均匀(线性?)的情况下,O(log2n)
斐波那契查找(黄金比例):

线性索引查找

分块索引:块内无序,块间有序
倒排索引:根据属性值查找记录

动态查找

二叉排序树

增删效率与查找效率兼顾

  • 左子树不为空时,则左子树上所有结点的值均小于其根结构的值
  • 右子树不为空时,则右子树上所有结点的值均大于其根结构的值
  • 左、右子树也分别为二叉排序树(递归)

平衡二叉排序树

左右子树深度差的绝对值不超过1
将二叉树上结点的左子树的深度减去右子树的深度,得到平衡因子 BF

多路查找树

减少对硬盘的IO操作
2-3树
2-3-4树

m阶B树属性:

  • 如果根结点不是叶结点,则其至少有两棵子树
  • 每一个非根的分支结点都有 k-1 个元素(关键字)和 k 个孩子
  • 所有叶子结点位于同一层次
  • 每一个分支结点包含以下信息数据: n A0 K1 A1 K2 A2 K3 A3… Kn An (K为关键字,A为指向子树根结点的指针)

哈希表

散列技术:在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)
f 称为散列函数(哈希函数)
采用散列技术将记录存在一块连续的存储空间中,这块空间称为哈希表。

构造方法

好的散列函数:计算简单+分布均匀

  • 直接定址法:简单,适合分布连续均匀的情况
  • 数字分析法:适合关键字位数比较大的情况,如抽取手机号后四位
  • 平方取中法:平方后取中间若干位数,适合关键字位数不大,不知道分布的情况
  • 折叠法:将关键字分割并叠加,适合知道关键字位数
  • 除留余数法:除数选择很重要
  • 随机数法:关键字长度不等值时合适

思考的角度:

  • 计算散列地址的时间
  • 关键字changd
  • 散列表大小
  • 关键字分布情况
  • 记录查找的频率

处理散列冲突

  • 开放定址法:
    发生冲突时,寻找下一个空的散列地址,寻找方法可用线性变化、平方等

  • 再散列函数法:

  • 链地址法:

  • 公共溢出区法:

    原文作者: 汉诺塔问题
    原文地址: https://blog.csdn.net/JShawn/article/details/88688323
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞