Java数据结构与算法
绪论
数据结构
是指所涉及的数据元素的集合以及数据元素之间的关系
逻辑结构的类型
-
集合
-
线性结构
-
树形结构
-
图形结构
存储结构
-
顺序存储结构
-
链式存储结构
-
索引存储结构
-
哈希(散列)存储结构
数据类型
是一组性质相同的值的集合和定义在此集合上的一组操作的总称
ADT(抽象数据类型)
算法
-
有穷性 —>不会出现无限循环
-
确定性—>不会出现二义性
-
可行性—>算法每条指令都可执行
-
输入性—>零个或者多个输入
-
输出性—>至少一个输出
算法时间复杂度
-
用T(n)的数量级表示,记作T(n)=O(f(n))
最好时间复杂度–》min{T(n)}
最坏时间复杂度–》max{T(n)}
平均时间复杂度–》概率
线性表
线性表的顺序存储结构—顺序表
1 | //顺序表泛型类 |
线性表的链式存储结构–链表
1 | package 线性表; |
建立单链表的方法(下面的代码块均位于LinkList类里)
-
头插法(数据结点次序和数组中数据次序正好相反)
1 | public void createListF(E[]e){ |
-
尾插法(数据结点次序和数组中数据次序相同)
1 | public void createListR(E[]e){ |
线性表的基本运算在单链表中的实现
-
获得序号为i的元素(0<= i <=n-1)
1 | public LinkNode<E> getI(int i){ |
-
将元素e添加到线性表末尾
1 | public void add(E e){ |
-
求线性表的长度
1 | public int size(){ |
-
设置线性表的长度
1 | public void setSize(int nLen){ |
-
获取线性表中序号为i的元素(data)
1 | public E getElem(int i){ |
-
设置线性表中序号为i的元素setElem(int i,E e) (与获取相似)
-
求线性表中第一个为e的元素的逻辑序号
1 | public int getNo(E e){ |
-
交换序号为 i 和 j 的元素
1 | public void swap(int i,int j){ |
-
插入 e 作为第i个元素(序号为i)
1 | public void insert(int i,E e){ |
-
删除第i个元素(序号为i)
1 | public void delete(int i){ |
快慢指针法(返回单链表中间位置元素)(P66)
1 | public static E middle(LinkList<E> l){ |
利用头插法让单链表l逆序,如(1,2,3,4,5)==>(5,4,3,2,1)
1 | public static void reverse(LinkList<E> l){ |
将两个递增有序单链表合并为一个递增有序单链表(二路归并 + 尾插法)
1 | public static LinkNodeClass<Integer> Merge(LinkListClass<Integer> A, |
双链表
1 | //双链表的结点泛型类 |
1 | //双链表的泛型类 |
1 | //头插法建立双链表 |
1 | //尾插法建立双链表 |
循环链表
1 | //循环单链表泛型类 |
循环双链表
1 | public class CDLinkNodeList<E> { |
顺序表和链表的比较
-
基于时间考虑
存储密度 = 结点中数据本身所占用的存储量/整个结点占用的内存量
一般情况下,存储密度越大,存储空间利用率就越大
当线性表长度变化不大,易于事先估计的情况,为了节省内存空间,宜采用顺序表
反之,当变化较大,难以估计其内存大小时,为了节省存储空间,宜采用链表
-
基于时间考虑
在顺序表进行插入删除操作时平均需要移动一半的元素;
二路归并 + 顺序存储结构求解多项式相加(主要算法)
-
用i,j分别遍历L1和L2有序多项式顺序表的元素,先建立一个空的多项式顺序表L3,在L1,L2都没有遍历完时循环
-
-
若i指向的元素的指数比较大,则根据其系数和指数新建一个元素并将其添加到L3,i++
-
若j指向的元素的指数比较大,则根据其系数和指数新建一个元素并将其添加到L3,j++
-
若i和j指向的元素的指数相同,则求出系数之和c,若c != 0,则由该系数和指数新建一个元素并将其添加到L3,否则不新建元素。最后 i++,j++
-
采用链式存储结构求解多想是相加
1 | //一个多项式对应的结点类 |
1 | //多项式单链表PolyClass类 |
栈和队列
栈
抽象来说,栈是一种只能在一段进行插入或者删除操作的线性表,允许插入和删除的一段叫做栈顶
,另一端叫做栈底
,插入操作通常称为进栈
或入栈
,删除操作通常称为退栈
或出栈
设n个元素进栈顺序为1,2,3…n,出栈顺序为,,…,若 = n, = n-1,…,= 1,
即: + = + 1, = - + 1
顺序栈
-
顺序栈四要素:
-
栈空的条件是
top = -1
; -
栈满(栈上溢出)的条件是
top = capacity -1
,采用动态扩容的方法,即栈满时将data数组的容量扩大两倍
; -
元素e进栈的操作是先将
top+1
,然后将元素放在栈顶指针top
处; -
出栈操作先将栈顶指针
top
处的元素取出,再将top - 1
;
-
顺序栈的泛型类
1 | public class S qStackClass<E>{ |
1 | //检查用户输入的括号是否配对 |
1 | //判断回文字符串 |
栈的链式存储结构(不用考虑栈满上溢出的情况)
-
栈空的条件为head.next == null, 初始时只有一个头结点
-
由于只有内存溢出才会出现栈满的情况,所有一般不考虑
-
元素e入栈时将包含该元素的结点插入作为首结点
-
出栈操作返回首结点的值并删除该结点
1 | //结点泛型类 |
1 | //链栈泛型类 |
队列
-
队列的顺序存储结构
1 | public class SqQueueClass<E> { |
假溢出
进队时rear增加,出队rear不变,front增加,当rear==MaxSize-1队满,这种队列中有空位置但是满足队满条件的上溢出称为假溢出
循环队列
循环队列首尾相连,当队尾指针rear=MaxSize-1时再前进一个位置就到达0位置,这个可以用%(求余)来实现
-
队首指针循环进1:front = (front+1) % MaxSize;
-
队尾指针循环进1:rear = (rear+1) % MaxSize;
循环队列的队头指针和队尾指针的初始化位置都是0,即front = rear = 0
-
队空条件:rear = front
-
队满的条件为(rear+1)% MaxSize == front,(相当于试探进队一次,若rear达到了front,则认为队满了)
-
元素e进队时,rear = (rear+1) % MaxSize,将元素e放置在该位置
-
元素出队时,front = (front+1) % MaxSize,取出该位置元素
1 | public class CSqQueueClass<E> { |
队列的链式存储结构
1 | //结点泛型类 |
1 | public class LinkQueueClass<E> { |
-
链队队空条件为front = rear = null或者front==null
-
只有内存溢出才会出现队满,通常不考虑
-
元素e进队操作:在单链表的尾部插入存放e的s结点,并让队尾指针指向它
-
元素出队操作:取出队首结点的data值,并将其从链队中删除
1 | //基本方法 |
串
基本概念
-
串
是由零个或多个字符组成的有限序列,里面的单个字符称为串的元素,是构成串的基本单位,串中包含的字符数称为串的长度,当个数为0为空串
串的存储结构
-
顺序存储结构–顺序串
1 | //顺序串类 |
-
串的链式存储结构–链串
1 | //链串结点类 |
1 | //链串类 |
Java 里面的字符串
串的模式匹配
-
Brute-Force算法(BF算法)(简单匹配算法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public int BF(String s, String t){
int i=0,j=0;
while( i<s.length() && j<t.length() ){//s,t都没遍历完时循环
if(s.charAt(i) == t.charAt(j)){
i++;
j++;
}
else{
i = i-j+1;
j = 0;
}
}
if(j >= t.length()) return ( i-t.length );
return -1;
} -
求模式串的next数组
1. 对于序号0,规定next[0] = -1;
2. 对于序号1,置next[1] = 0;
3. 如果{% _internal_math_placeholder 46 %}有多个相同的前后缀,应该取最长的长度为其next[{% _internal_math_placeholder 47 %}]值;
递归
条件
-
调用次数有限
-
有终止条件
-
把一个问题化成很多小问题解决,这些小问题和大问题求解方法一样,只是规模不同
递归模型
在递归算法的执行中最长的递归调用的链长称为该算法的递归调用深度
例如的递归算法在求的递归调用深度为5
递归算法时间复杂度
-
给出执行时间对应的递推式;
-
求解递推式得出算法的执行时间;
-
最后由得出时间复杂度。
递归算法的空间复杂度
-
根据递归深度得到(同时间复杂度求法,)
数组和稀疏矩阵
一维数组
d维数组
对于数组,假设每个元素占个存储单元,表示元素的存储地址
-
行优先
-
列优先
稀疏数组
-
利用一个三元组来表示,三元组每个元素的类定义如下:
1 | public class TupElem { |
-
稀疏矩阵三元组类定义
1 | public class TupClass { |
特殊矩阵的压缩存储
-
对称矩阵的压缩存储
-
-
可以使对称元素共享同一存储空间,可以将个元素压缩存储到 个元素的空间中。按照行优先存储时仅仅存储其下三角和主对角线部分的元素。
-
- A中任一元素和B中的元素之间的关系如下:
-
树和二叉树
树的基本术语
-
结点的度
:树中某个结点的子树的个数 -
树的度
:树中各结点的度的最大值(通常将度为m的树称为m次树) -
分支结点
:度不为0的结点 -
叶子结点
:度为0 的结点 -
双亲结点
、孩子结点
-
树的高度(深度)
:根节点为第一层,树的最大层级称为树的高度
树的性质
-
树中的结点数
等于
所有结点的度之和加一 -
度为m的树的第 层上最多有 个结点
-
高度为 的 次树最多有 个结点
-
具有 个结点的 次树的最小高度为
在次树中计算结点时常用的关系式有:
-
树中所有结点的度之和 = 分支数 =
-
所有结点的度之和 =
树的存储结构
-
双亲存储结构
为一种顺序存储结构,双亲存储结构结点类如下:
1 | public class PTree<E> { |
-
孩子链存储结构
1 | public class TSonNode<E> { |
二叉树(二分树)
-
二叉树的5中基本形态
-
空二叉树
-
单结点二叉树
-
右子树为空的二叉树
-
左子树为空的二叉树
-
左右子树都不为空的二叉树
-
-
满二叉树
的特点-
叶子结点都在最下一层;
-
只有度为0和度为2 的结点;
-
含个结点的满二叉树的高度为,叶子结点个数为,度为2的结点个数为。
-
-
完全二叉树
的特点:-
叶子结点只可能出现在最下面两层;
-
最下一层的叶子结点都依次排列在改层最左边的位置上;
-
如果有度为 1 的结点,只可能有一个,且该结点最多只有左孩子而无右孩子;
-
按照层序编号后,一旦出现某结点(编号为)为叶子结点或只有左孩子,则编号大于 的结点均为叶子结点。
-
-
完全二叉树
的计算:-
结点数为
偶
——>只有一个分支 -
结点数为
奇
——>无单分支 - 个双分支
-
-
二叉树的
性质
:-
非空二叉树上叶子结点数等于双分支结点数加一;
-
非空二叉树的第层上最多有个结点;
-
高度为的二叉树最多有个结点;
-
具有个结点的完全二叉树的高度为或。
-
二叉树的存储结构
-
二叉树的顺序存储结构
-
- 完全二叉树和满二叉树采用顺序存储结构比较合适,既能最大限度
节省存储空间
,又能利用下标迅速确定二叉树中位置和结点之间的关系
- 完全二叉树和满二叉树采用顺序存储结构比较合适,既能最大限度
-
- 编号为 的结点的层次为
-
二叉树的链式存储结构(二叉链)
-
-
二叉链结点类
1
2
3
4
5
6
7
8
9
10
11
12
13public class BTNode<E>{
E data;
BTNode lchild;//指向左结点
BTNode rchild;//指向右结点
public BTNode(){
lchild=rchild=null;
}
public BTNode(E e){
this.data = e;
lchild=rchild=null;
}
}二叉树的基本运算
-
-
二叉树的类设计
1
2
3
4
5
6
7public class BTreeClass{
BTNode<Character> b;//根结点
String bstr;//根结点字符串表达式
public BTreeClass(){
b = null;
}
} -
二叉树的基本运算
1
2
3
4
5
6
7
8//创建二叉树
public void CreateBTree(String str)
//返回二叉树括号表示字符串
public String toString()
//查找值为x的结点(递归查找)
FindNode(x)
//求高度h(递归)
Height()
二叉树的遍历
-
先序遍历
1
2
3
4
5
6
7
8
9
10
11//先序遍历的递归算法
public void PreOrder1(BTreeClass bt){
PreOrder11(bt.b);
}
public void PreOrder11(BTNode<Character> t){
if(t!=null){
System.out.print(t.data + " ");
PreOrder11(t.lchild);//先序遍历左子树
PreOrder11(t.rchild);//先序遍历右子树
}
}1
2
3
4
5
6
7
8
9
10
11//中序遍历的递归算法
public void InOrder1(BTreeClass bt){
InOrder11(bt.b);
}
public void InOrder11(BTNode<Character> t){
if(t!=null){
InOrder11(t.lchild);//中序遍历左子树
System.out.print(t.data + " ");//访问根结点
InOrder11(t.rchild);//中序遍历右子树
}
}1
2
3
4
5
6
7
8
9
10
11//后序遍历的递归算法
public void PostOrder1(BTreeClass bt){
PostOrder11(bt.b);
}
public void PostOrder11(BtNode<Character> t){
if(t!=null){
PostOrder11(t.lchild);//后序遍历左子树
PostOrder11(t.rchild);//后序遍历右子树
System.out.print(t.data + " ");
}
}二叉树的层次遍历
-
层次遍历基本步骤
-
- 访问根结点(第一层)
-
- 从左到右访问第二层的所有结点
-
- 从左到右访问第三层的所有结点,,第层的所有结点
-
层次遍历算法设计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public void LevelOrder(BTreeClass bt){
BTNode<Character> p;
Queue<BTNode> qu = new LinkedList<BTNode>();
qu.offer(bt.b);//根结点进队
while(!qu.isEmpty()){//队列不为空时循环
P = qu.poll();//出队第一个结点
System.out.print(p.data + " ");//输出
if(p.lchild!=null){//有左孩子,进队
qu.offer(p.lchild);
}
if(p.rchild!=null){//有右孩子,进队
qu.offer(p.rchild);
}
}
}关于二叉树构造的一些结论
-
任何个不同结点的二叉树都可以由它的中序序列和先序序列唯一地确定
-
任何个不同结点的二叉树都可以由它的中序序列和后序序列唯一地确定
线索二叉树
-
定义对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在
某种遍历次序
下该结点的前驱结点
和后继结点
的指针
,这些指针称为线索,加上线索
的二叉树称为线索二叉树
。 -
区分“前驱结点”和“孩子结点”
标识
每个这样的结点存储结构如下:
ltag | lchild | data | rchild | rtag |
---|
线索化二叉树(建立线索二叉树)(以下以中序遍历为主)
-
二叉链结点类
-
左孩子为空
^ | data | rchild |
---|
左指针指向中序遍历的直接前驱
-
右孩子为空
lchild | data | ^ |
---|
右指针指向中序遍历的直接后继
1 | public class ThNode{ |
-
中序线索化二叉树类
1 | public class ThreadClass{ |
遍历线索二叉树 <—click here to learn
哈夫曼树(最优二叉树)(P272)
-
权值越大,离根结点越近
-
权值越小,离根结点越远
-
具有个叶子结点的哈夫曼树共有个结点
哈夫曼编码
-
在一组字符的哈夫曼编码中,任一字符的哈夫曼编码
不可能
是另一字符的哈夫曼编码的前缀。
二叉树与树、森林之间的转换
-
树到二叉树的转换步骤
-
- 加线:在各兄弟结点之间加一连线
-
- 抹线:对于任意结点,除了其
最左子树
之外,抹掉该结点与其他子树之间的双亲-孩子
关系
- 抹线:对于任意结点,除了其
-
- 调整:(排列整齐)
-
一棵由树转换的二叉树与还原为树
-
- 加线:在
各结点的双亲
与该结点右链
上的每个结点之间加一条连线
- 加线:在
-
- 抹线:抹掉二叉树中所有双亲结点与其右孩子之间的
双亲-右孩子
关系
- 抹线:抹掉二叉树中所有双亲结点与其右孩子之间的
-
- 调整:(排列整齐)
森林到二叉树的转换及还原
-
森林转换为二叉树
-
- 转换:将森林中每一棵树转换为二叉树
-
- 连接:将转换后的二叉树根结点相连
-
- 调整:(排列整齐)
-
二叉树还原为森林
-
- 抹线:抹掉根结点右链上所有
双亲-右孩子
关系
- 抹线:抹掉根结点右链上所有
-
- 转换:分别将抹线后的二叉树还原为树
-
- 调整:(排列整齐)
图
基本概念
-
图由两个集合组成,记为,其中是顶点的有限集合,记为,
是连接两个不同顶点的边的有限集合,记为。+无向图:在图中,如果代表边的顶点对(或序偶)是无序的,则成为无向图,
-
有向图:顶点对有序不一样
基本术语
-
端点和邻接点
-
顶点的度、入度、出度
-
顶点的度
:顶点所关联的边的数目
-
入度、出度
:以该顶点为终点的边的数目,反之,以该端点为起点的边的数目为出度若一个图中有个顶点和条边,每个顶点的度为 ,则有:
相当于一个图中顶点的度之和
等于边数
的两倍
-
完全图
有向图
每两个顶点之间都存在着方向相反
的两条边
graph TD; 0-->1 1-->0 1-->2 2-->1 2-->3 3-->2 3-->0 0-->3 3-->1 1-->3 0-->2 2-->0
无向图
每两个端点之间都存在一条边
graph LR; 0---1 1---2 2---3 3---0 3---1 2---0
-
稠密图和稀疏图
- 当一个图接近万全图时称为稠密图
- 当一个图边数较少[有向图]称为稀疏图
-
子图
- 两个图
-
路径和路径长度
- 从的一条
路径
是一个顶点序列 路径长度
是指一条路径上经过的边的数目简单路径
是指一条路径上除开始点和结束点可以相同外,其余端点均不相同
- 从的一条
-
回路和环
- 一条路径开始点和结束点相同,该路径被称为
回路
或环
- 一条路径开始点和结束点相同,该路径被称为
-
连通、连通图、连通分量
- 图的任意两个端点都是连通的图称为
连通图
,否则为非连通图
- 无向图G的
极大连通子图
称为G的连通分量
- 图的任意两个端点都是连通的图称为
-
强连通图和强连通分量
- 强连通图:
- 强连通分量:有向图的极大强连通子图称为该图的强连通分量
-
关结点和重连通图
-
权和网
- 边上带有权的图称为带权图,也称作网
图的存储结构
-
邻接矩阵
- 存储方法
-
- G不带权
-
-
- G带权
-
-
-
- 三个邻接矩阵
-