一、始
在跟着dong哥学习算法的过程中,在小白阶段认识到在此之前需要先储备常见的数据结构知识,也就是一些概念和特性。就如「学习算法和刷题的框架思维 」所提到的数据结构的存储方式(数组「顺序存储」和链表「链式存储」)和上层建筑(散列表、栈、队列、堆、树、图等)。所以这里就自己总结下这些基础知识。
dong哥的网站:
labuladong 的算法小抄 :: labuladong的算法小抄 (gitee.io)
二、基础知识
2.1、数据结构概述
数据结构概念包括三部分:数据的逻辑结构、数据的存储结构和对数据的操作
2.1.1、数据的逻辑结构
数据的逻辑结构:是指数据元素之间的逻辑关系,用一个数据元素的集合和定义在此集合上的若干关系来表示,常被简称为数据结构。
根据数据元素之间不同的逻辑关系,数据结构可分为三种:线性结构、树形结构和图。
1、线性结构的逻辑特征是:若结构是非空集,则有且仅有一个开始结点和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。线性表是一个典型的线性结构。一维数组、栈、队列、串等都是线性结构。
2、非线性结构的逻辑特征是:一个结点可能有多个直接前趋和直接后继。广义表、高维数组、树和图等数据结构都是非线性结构。
2.1.2、数据的存储结构
数据的存储结构,又称物理结构。(而对于数据的逻辑结构是从逻辑关系角度观察数据,它与数据的存储无关,是独立于计算机的,是为了帮助人们更加形象的去理解数据结构。)
数据存储结构的基本形式有两种:顺序存储结构和链式存储结构。
1、顺序存储结构:数据元素在内存中的物理存储次序与它们的逻辑次序相同,其物理存储次序体现了它们之间的逻辑关系。通常,使用数组来实现顺序存储结构。
2、链式存储结构:使用若干地址分散的存储单元存储数据元素,逻辑上相邻的数据元素在物理位置上不一定相邻,数据元素之间的关系需要采用附加信息来特别指定。通常,采用指针变量记载前驱或者后驱元素的存储地址,由数据域和地址域组成的一个结点表示一个数据元素,通过地址域将相互关联的结点链接起来,结点间的链接关系体现数据元素间的逻辑关系。 结点结构如下:
结点(数据域,地址域); Node(data,next);
2.1.3、对数据的操作
1、初始化
2、判断是否为空
3、存取
4、统计数据元素个数
5、遍历(按照某种次序访问一个数据结构中的所有元素)
6、插入,删除某个元素
7、查找
8、排序
2.2、数据类型
1字节=8位 1Byte = 8bit
2.2.1、基本数据类型
-
整数类型:
byte 字节 1个字节范围: -128~127(2^8)
short 短整形 2个字节范围:-32768~32767(2^16)
int 整形 4个字节范围:-2 147 483 648~2 147 483 647 (2^32)
long 长整型 8个字节范围:-9223372036854775808~9223372036854775807(2^64)
-
浮点类型:
float 单精度 4个字节
double 双精度 8个字节
-
字符类型:
char 字符 2个字节
-
boolean类型:一个字节 true false(布尔(boolean)类型的大小没有明确的规定,通常定义为取字面值 “true” 或 “false”)
2.2.2、引用数据类型
引用数据类型包括:类、接口、数组。
- 在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。
- 对象、数组都是引用数据类型。
- 所有引用类型的默认值都是null
2.3、运算符
- 算数运算符:+、-、*、/、%(取余,模运算)、++、–
- 赋值运算符: =
- 关系运算符: >、<、>=、<=、==、!=、instancecof(指出对象是否是特定类的一个实例)
- 逻辑运算符:&&(逻辑与运算,必须全为真,结果才为真)、||(逻辑或运算,只要有一个真,结果就为真)、!(如果真则变假,如果假则变真)
- 位运算符(二进制位运算):<<(左移)、 >>(右移)、 ~(取反)、 |(或运算)、 ^(异或运算)、 &(与运算)、>>>(无符号右移,逻辑右移)
- 条件运算符:?、: (x ? y : z 如果 x==true,则结果为y,否则为z)
- 扩展赋值运算符:+=、-=、/=、*= (a+=b : a = a+b)
三、数据结构知识
3.1、线性表(List集合)
线性表(Linear List) (a0,a1,…,an-1)是由n(≥0)个类型相同的数据元素组成的有限序列,其中,元素类型可以是基本类型或类;n是线性表长度(Length),即元素个数。若n=0,空表;若n>0,(0<i<n-1)有且仅有一个前驱元素和一个后继元素, a0没有前驱元素, an-1没有后继元素。
3.1.1、线性表的顺序存储结构
-
数组
数组是实现顺序存储结构的基础。
数组(Array)存储具有相同数据类型的元素集合。一维数组占用一块内存空间,每个存储单元的地址是连续的,计算第i个元素地址所需时间复杂度是一个常量, 与元素序号i无关。存取任何一个元素的时间复杂度是O(1)的数据结构称为随机存取结构。因此,数组是随机存取结构。 -
顺序表
采用一维数组存储数据元素,数据元素在内存的物理存储次序反映了线性表数据元素之间的逻辑次序。
顺序表是随机存取结构。
3.1.2、线性表的链式存储结构
线性表的链式存储结构是用若干地址分散的存储单元存储数据元素,逻辑上相邻的数据元素在物理位置上不一定相邻,因此,必须采用附加信息表示数据元素之间的顺序关系。存储个 数据元素的存储单元称为结点(Node),一个结点至少包含以下两部分:
结点(数据域,地址域)
其中还分为单链表结点与双链表结点:
单链表结点(data数据域,next后续结点地址域)
双链表结点(data数据域,prev前驱结点地址域,next后续结点地址域)
3.1.2.1、链表遍历
链表遍历框架,兼具迭代和递归结构:
/* 基本的单链表节点 */
class ListNode {
int val;
ListNode next;
}
void traverse(ListNode head) {
for (ListNode p = head; p != null; p = p.next) {
// 迭代访问 p.val
}
}
void traverse(ListNode head) {
// 递归访问 head.val
traverse(head.next)
}
3.2、串(字符串)
字符串(String, 简称串)是由字符组成的有限序列,是常用的种非数值数据。串的逻辑结构是线性表,串是种特殊的线性表, 限制其元素 类型是字符;串的操作特点与线性表不同,主要对子串进行操作;通常采用顺序存储结构存储。
串的基本概念
串定义。s= “S0S1…Sn-1”
子串。"at"是"data"的子串。
串比较。比较相等,比较大小。
串也有顺序存储结构和链式存储结构
3.2.1、字符串类
-
String类 是引用数据类型,提供构造串对象、求串长度、取字符、求子串、连接串、比较相等、比较大小等操作。抛出字符串序号越界异常。(要熟练掌握String类的相关方法)(https://blog.csdn.net/wang_luwei/article/details/107368125 、https://blog.csdn.net/wang_luwei/article/details/107368146)
-
可变字符串类:StringBuffer与StringBuilder
- StringBuffer:可变长字符串,JDK1.0提供,运行效率慢(比String快),线程安全 (buffer:缓冲区)
- StringBuilder:可变长字符串,JDK5.0提供,运行效率快,线程不安全(单线程选择StringBuilder)
- 和String区别:(1)比String效率高、(2)比String节省内存
- 相当于String增强类
- 方法:
- append(); //添加
- insert(); //添加,插入,可指定位置 (要注意位置范围0 ~ stringBuffer.length())
- reverse();//反转
- delete(); 删除,有起始与结束下标(含头不含尾)
3.3、栈
栈(stack)是一种线性表,插入和删除操作只允许在线性表的一端进行。先进后出。
public interface Stack<T>
{
public abstract boolean isEmpty(); //判空
public abstract void push(T x); //x入栈
public abstract T peek(); //返回栈顶元素,未出栈
public abstract T pop(); //出栈,返回栈顶
}
//顺序栈类,最终类,实现栈接口,T表示元素类型
public final class SeqStack<T> implements Stack<T>
{
private SeqList<T> list; //顺序表
public SeqStack(int capacity) //构造空栈
public SeqStack() //构造空栈
public boolean isEmpty() //判空
public void push(T x) //x入栈
public T peek() //返回栈顶(未出栈)
public T pop() //出栈,返回栈顶元素
}
//链式栈类,最终类,实现栈接口,T表示元素类型
public final class LinkedStack<T>
implements Stack<T>
{
private SinglyList<T> list; //单链表
public LinkedStack() //构造空栈
public boolean isEmpty() //判空
public void push(T x) //x入栈
public T peek() //返回栈顶元素(未出栈)
public T pop() //出栈,返回栈顶元素
}
3.4、队列
队列(queue)是一种特殊的线性表,其插入和删除操作分别在线性表的两端进行。先进先出。
//队列接口,描述队列抽象数据类型,T表示元素类型
public interface Queue<T>
{
public abstract boolean isEmpty(); //判空
public abstract boolean add(T x); //x入队
public abstract T peek(); //返回队头,没有删除
public abstract T poll(); //出队,返回队头
}
//顺序循环队列类,最终类,实现队列接口,T元素类型
//front 对头元素下标 rear是下一个入队元素下标
//1、设置初始空队列为 front=rear=0,约定队列空条件是front==rear
//2、入队操作改变rear,出队操作改变front,:front=(front+1)%length;rear=(rear+1)%length ,两者的取值范围是0~length-1
//3、队列满条件是front ==(rear+1)%length,此时队列中仍有一个空位置;如果不留空位置,队列满条件也是front==rear,与队列空条件一致
//4、当队列满时再入对,将数组容量扩大一倍,按队列元素次序复制数组元素。
public final class SeqQueue<T> implements Queue<T>
{
private Object element[]; //数组
private int front, rear; //队列头尾下标
public SeqQueue(int capacity)//构造空队列
public SeqQueue() //构造空队列
public boolean isEmpty(); //判空
public boolean add(T x); //x入队
public T peek(); //返回队头,没有删除
public T poll(); //出队,返回队头
}
//链式队列
3.5、递归
定义:即用一个概念本身直接或间接地定义它自己,例如阶乘函数:f(n)=n!,定义为:
再如,Fibonacci数列的第n项f(n)递归定义为:
-
递归定义必须满足以下两个条件:
- ①边界条件:至少有一条初始定义是非递归的,如1!=1。
- ②递推通式:由已知函数值逐步递推计算出未知函数值,如用(n-1)! 定义n!。
- 边界条件与递推通式是递归定义的两个基本要素,缺一不可, 并且递推通式必须在经过有限次运算后到达边界条件,从而能够结束递归,得到运算结果。
-
递归算法
- 存在直接或间接调用自身的算法称为递归算法。递归定义的问题可用递归算法求解,按照递归定义将问题简化,逐步递推,直到获得一个确定值。例如,设f(n)=n!,递推通式是f(n)=nxf(n-1),将f(n)递推到f(n-1),算法不变,最终递推到f(1)=1获得确定值,递归结束。
3.6、数组
数组是包含子结构的线性结构,是线性表的扩展,其元素可以是一个子结构。
在程序语言设计中,数组已被实现为一种数据类型。
数组是顺序存储的随机存取结构,数组是其他数据结构实现顺序存储的基础。
一维数组的逻辑结构是线性表,多维数组是线性表的扩展。
3.6.1、一维数组
定义:
- 数组是相同类型数据的有序集合
- 数组描述的是相同类型的若干数据,按照一定的先后次序排列组合而成
- 其中,每一个数据称为一个数组元素,每个数组元素可以通过一个下标来访问它们。(下标是从0开始的)
声明与创建:
- 使用数组,要先声明数组之后,才能使用数组。语法为:(静态初始化):
int[] a = {1,2,3}; //首选
或
int a[] = {1,2,3}; //效果相同,但不是首选方法
- 动态初始化: 声明创建 + 赋值
int[] a = new int[2];
a[0] = 1;
a[1] = 2;
-
数组的默认初始化
数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。(即在动态创建数组之后,赋值之前,数组中的元素都有一个默认值,默认值由数组类型决定)
3.6.1.1、数组遍历
- 普通的for循环
int[] arrays = {1,2,3};
for (int i = 0; i < arrays.length; i++) {
System.out.println(arrays[i]);
}
- For-Each(增强for循环,就是打印数组元素)
增强for循环,没有下标,就是输出数组元素
for(声明语句 : 表达式){
//代码句子
}
for(元素类型 元素名称 : 遍历数组名(集合)(或者能进行迭代的) ){
//代码句子
}
public class test1 {
public static void main(String[] args) {
int[] a = {1,2,3};
for (int i : a) {
System.out.println(i);
}
}
}
3.6.2、二维数组
二维数组是一维数组的扩展,二维数组是“元素为一维数组“的一维数组。一个m行n列(m大于0,n大于0)的二维数组,即可以看成由M个一维数组(行)所组成的线形表,也可以看成N个一维数组(列)所组成的线形表。
二维数组的顺序存储结构:
行主序 与 列主序计算公式,如下:
二维数组的创建:
//定义创建一个 2行5列的数组int[][] a = new int[2][5];
package com.wlw.arrays;
public class ArrausDemo05 {
public static void main(String[] args) {
//[4][2] 四行两列 四个一维数组,每个一维数组里有两个元素
/*
1,2
3,4
5,6
7,8
*/
int[][] array = {{1,2},{3,4},{5,6},{7,8}};
//输出数组元素
System.out.println(array[0]); //输出的是一个对象,第一个一维数组
System.out.println(array[0][1]);//2 ,第一个一维数组里的第二个元素
printArrays(array[0]); // 1,2
System.out.println("\n"+"===============");
//数组长度
System.out.println(array.length); //4
System.out.println(array[1].length); //2
System.out.println("\n"+"===============");
//遍历二维数组
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.println(array[i][j]);
}
}
}
//打印数组
public static void printArrays(int[] arrays){
for (int i = 0; i < arrays.length; i++) {
System.out.print(arrays[i] + " ");
}
}
}
/*输出结果
[I@4554617c
2
1 2
===============
4
2
===============
1
2
3
4
5
6
7
8
*/
3.7、广义表
广义表是一种复杂的数据结构,它是线性表的扩展,能够表示树结构和图结构。
广义表是n(n≥0)个元素a1,a2,…,ai,…,an的有限序列。
其中:
①ai–或者是原子或者是一个广义表。
②广义表通常记作:
Ls=( a1,a2,…,ai,…,an)。
③Ls是广义表的名字,n为它的长度。
④若ai是广义表,则称它为Ls的子表。
注意:
①广义表通常用圆括号括起来,用逗号分隔其中的元素。
②为了区分原子和广义表,书写时用大写字母表示广义表,用小写字母表示原子。
③若广义表Ls非空(n≥1),则a1是Ls的表头,其余元素组成的表(a2,a3,…,an)称为Ls的表尾。
④广义表是递归定义的
GList = (a0, a1,…, an-1)
中国(北京, 上海, 江苏(南京, 苏州), 浙江(杭州), 广东(广州))
L=(a, b)
//线性表,长度为2,深度为1T=(c, L)=(c, (a,b))
//L为T的子表,T的长度为2,深度为2G=(d, L, T)=(d, (a, b), (c, (a, b)))
//L、T为G的子表,G的长度为3,深度为3S=()
//空表,长度为0,深度为1S1=(S)=(())
//元素是一个空表,长度为1,深度为2Z=(e, Z)=(e, (e, (e, (…))))
//递归表,Z的长度为2,深度无穷
3.7.1、广义表的存储结构
广义表的双链存储结构:广义表结点(data数据域,child子表地址域,next后继结点地址域)
3.8、树
树是数据元素(结点)之间具有层次关系的非线性结构。在树结构中,根结点没有前驱结点。除根以外的结点只有一个前驱结点,有0至多个后继结点。
定义:树(tree)是由n(n≥0)个结点组成的有限集合。n=0的树称为空树;n>0的树T由以下两个条件约定构成:
- 根(root)结点,它没有前驱结点,只有后继结点。
- 其他结点分为m(0 <= m < n )棵互不相交的子树。
数是递归定义的。结点是树的基本单位,若干个结点组成一棵子树,若干棵互不相交的子树组成一棵树。树中每个结点都是该树中某一棵子树的根,因此树是由结点组成的,结点之间具有层次关系的非线性结构。
3.8.1、树的术语
- 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;
- 兄弟节点:具有相同父节点的节点互称为兄弟节点;
- 节点的度:一个节点含有的子树的个数称为该节点的度;(A的度为3)
- 树的度:一棵树中,最大的节点度称为树的度;
- 叶节点或终端节点:度为零的节点;(上图中的E)
- 非终端节点或分支节点:度不为零的节点;
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
- 深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
- 高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
- 堂兄弟节点:父节点在同一层的节点互为堂兄弟;
- 节点的祖先:从根到该节点所经分支上的所有节点;
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
- 森林:由m(m>=0)棵互不相交的树的集合称为森林;
3.8.2、树的种类
- 无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树;
- 有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树;
- 二叉树:每个节点最多含有两个子树的树称为二叉树;
- 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
- 满二叉树:所有叶节点都在最底层的完全二叉树;
- 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
- 排序二叉树(二叉查找树(英语:Binary Search Tree)):也称二叉搜索树、有序二叉树;
- 霍夫曼树:带权路径最短的二叉树称为哈夫曼树或最优二叉树;
- B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。
3.8.3、二叉树
二叉树(binary tree)是n(n>=0)个结点组成的有限集合;n=0时称为空二叉树;n>0的二叉树由一个根结点和两棵互不相交、分别称为左子树和右子树的子二叉树构成的。二叉树也是递归定义的。
二叉树有5种基本形态,如下图所示。
(a)0个结点,空二叉树
(b)1个结点,根节点
(c)由根节点和左子树组成,根的右子树为空
(d)由根节点和右子树组成,根的左子树为空
(e)由根节点、左子树和右子树组成
3.8.3.1、二叉树性质
(1)性质1:若根结点的层次为1,则二叉树第i层最多有2^(i-1)(i≥1)个结点。
(2)性质2:在高度为h的二叉树中,最多有2^(h)-1个结点(h≥0)。
(3)性质3:设一棵二叉树的叶子结点数为n0,2度结点数为n2,则n0=n2+1。
(4)性质4:一棵高度为h的满二叉树是具有2^(h)-1(h≥0)个结点的二叉树。满二叉树中每一层的结点数目都达到最大值。
(5)性质5:一棵具有n个结点高度为h的二叉树,如果它的每个结点都与高度为h的满二叉树中序号为0~n-1的结点一一对应,则称这棵二叉树为完全二叉树。
满二叉树是完全二叉树,完全二叉树不一定是满二叉树。完全二叉树的第1~h-1层是满二叉树,第h层不满,并且该层所有节点都必须集中在该层左边的若干位置上。如下图就不是完全二叉树。
(6)性质6:一棵具有n个结点的完全二叉树,其高度为
(7)性质7:一棵具有n个结点的完全二叉树,对序号为i(0≤i<n)的结点,有:
- 若i=0,则i为根结点,无父母结点;若i>0,则i的父母结点序号为(i-1)/2
- 若2i+1<n,则i的左孩子结点在2i+1;否则i无左孩子。
- 若2i+2<n,则i的右孩子结点在2i+2;否则i无右孩子。
3.8.3.1、二叉树的遍历规则
- 孩子优先的遍历规则
- 先根次序:访问根结点,遍历左子树,遍历右子树。
- 中根次序:遍历左子树,访问根结点,遍历右子树。
- 后根次序:遍历左子树,遍历右子树,访问根结点。
示例一:
先根遍历序列:**A** B D G C E F H
中根遍历序列:D G B **A** E C H F
后根遍历序列:G D B E H F C **A**
-
兄弟优先的遍历规则
- 二叉树的层次遍历是按层次次序进行的,遍历过程从根结点开始,逐层深入,从左至右依次访问完当前层的所有结点,再访问下一层。 由上面示例一中的图(a)所示二叉树的层次遍历序列为ABCDEFGH。二叉树层次遍历的特点是兄弟优先。对于任意一个结点(如B),其兄弟结点©在其孩子结点(D)之前访问。
-
二叉树遍历框架,典型的非线性递归遍历结构:
/* 基本的二叉树节点 */
class TreeNode {
int val;
TreeNode left, right;
}
void traverse(TreeNode root) {
traverse(root.left)
traverse(root.right)
}
- 二叉树框架可以扩展为 N 叉树的遍历框架:
/* 基本的 N 叉树节点 */
class TreeNode {
int val;
TreeNode[] children;
}
void traverse(TreeNode root) {
for (TreeNode child : root.children) {
traverse(child);
}
}
3.9、图【待定】
定义:
G=(V,E)
V= {Vi|vi∈某个数据元素集合}
E={(Vi,Vj)| vi,vj∈V} 或 E={<Vi,Vj>| vi,vj∈V且Path}
3.9.1、图的表示
ADT Graph<T> //图抽象数据类型
{ int vertexCount() //顶点数
T getVertex(int i) //顶点vi元素
void setVertex(int i, T x) //设置vi顶点为x
int insertVertex(T x); //插入顶点
void removeVertex(int v) //删除顶点
int next(int i, int j); //后继邻接顶点
void insertEdge(int i, int j, int weight) //插入边
void removeEdge(int i, int j) //删除边
int weight(int i, int j) //边的权值
}
3.9.2、图的遍历
3.9.2.1、图的深度优先(DFS)搜索遍历
public void DFSTraverse(int i) //非连通图的一次深度优先搜索遍历,从顶点vi出发
{
boolean[] visited=new boolean[this.vertexCount()]; //访问标记数组,元素初值为false,表示未被访问
int j=i;
do
{ if (!visited[j]) //若顶点vj未被访问。若i越界,Java将抛出数组下标序号越界异常
{
System.out.print("{ ");
this.depthfs(j, visited); //从顶点vj出发的一次深度优先搜索
System.out.print("} ");
}
j = (j+1) % this.vertexCount(); //在其他连通分量中寻找未被访问顶点
} while (j!=i);
System.out.println();
}
//从顶点vi出发的一次深度优先搜索,遍历一个连通分量;visited指定访问标记数组。递归算法
private void depthfs(int i, boolean[] visited)
{
System.out.print(this.getVertex(i)+" "); //访问顶点vi
visited[i] = true; //设置访问标记
for (int j=this.next(i,-1); j!=-1; j=this.next(i,j))//j依次获得vi的所有邻接顶点序号
if(!visited[j]) //若邻接顶点vj未被访问
depthfs(j, visited); //从vj出发的深度优先搜索遍历,递归调用
}
3.9.2.2、图的广度优先(BFS)搜索遍历
public void BFSTraverse(int i) //非连通图的一次广度优先搜索遍历,从顶点vi出发
{
boolean[] visited = new boolean[this.vertexCount()]; //访问标记数组
int j=i;
do
{ if (!visited[j]) //若顶点vj未被访问
{
System.out.print("{ ");
breadthfs(j, visited); //从vj出发的一次广度优先搜索
System.out.print("} ");
}
j = (j+1) % this.vertexCount(); //在其他连通分量中寻找未被访问顶点
} while (j!=i);
System.out.println();
}
//从顶点vi出发的一次广度优先搜索,遍历一个连通分量,使用队列
private void breadthfs(int i, boolean[] visited)
{
System.out.print(this.getVertex(i)+" "); //访问顶点vi
visited[i] = true; //设置访问标记
// SeqQueue<Integer> que = new SeqQueue<Integer>(this.vertexCount()); //创建顺序队列
LinkedQueue<Integer> que = new LinkedQueue<Integer>(); //创建链式队列
que.add(i); //访问过的顶点vi序号入队,自动转换成Integer(i))
while (!que.isEmpty()) //当队列不空时循环
{
i = que.poll(); //出队,自动转换成int;
for (int j=next(i,-1); j!=-1; j=next(i,j)) //j依次获得vi的所有邻接顶点
if (!visited[j]) //若顶点vj未访问过
{
System.out.print(this.getVertex(j)+" ");//访问顶点vj
visited[j] = true;
que.add(j); //访问过的顶点vj序号入队
// System.out.println("顶点队列:"+que.toString());
}
}
}
四、查找(搜索)
对于查找:(提高查找效率的措施:)(基本原则是缩小查找范围)
- 数据元素排序:以事先准备时间换取查找时间:排序线性表 「 ASL成功 = ASL不成功 = (n+1)/2 」
- 建立索引:以空间换取时间:字典采用顺序存储结构,事先按字母排序并建立多级索引。
- 散列存储
- 建立二叉排序树
对于所有的查找算法,一定要确定 搜索区间 是否覆盖了所有的情况!!!
平均查找长度(ASL) :在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。(pi是元素的查找概率,ci是查找相应元素需要进行的关键字比较次数)
4.1、二分法查找【用于查找】
看:查找之二分搜索
4.2、散列表
看:查找之散列表
4.3、二叉排序树
一个数据元素集合,如果既要排序、又要支持高效的查找、插入、删除操作,将其组织成什么样的数据结构能够满足要求?前述若干数据结构的排序、查找、插入、删除等操作性能分析如下:
- 排序顺序表,采用二分法查找,时间复杂度为O(log2N);插入和删除操作的时间复杂度为O(n),数据移动量大,效率较低。
- 排序单链表,顺序查找的时间复杂度为O(n),不能采用二分法查找;插入和删除操作的时间复杂度为O(n),虽然没有数据移动但因查找效率低,使得插入和删除的效率也较低。
- 散列表,虽然查找、插入和删除操作的效率较高,但是散列表不具有排序特性。
上述几种数据结构都不能满足该问题的要求。能够满足要求的是二叉排序树。
4.3.1、二叉排序树定义
二叉排序树(Binary Sort Tree)或者是一棵空树 ;或者是具有下列性质的二叉树:
- 元素以关键字为依据,每个结点都可比较相等和大小,各元素关键字互不相同。
- 对于每个结点,其左子树(不空)上所有结点元素均小于该结点,并且右子树(不空)上所有结点元素均大于该结点。【这也就是二叉排序树,可以排序的原因】
- 每个结点的左、右子树也分别为二叉排序树。
一棵二叉排序树如下图所示,按中根次序遍历该二叉排序树,得到按关键字升序排列的数据元素序列{6,12,18,36,54,57,66,76,81,99}。
二叉排序数的增删操作都依赖于查找操作,而二叉排序数的查找效率与二叉树的高度有关,高度越低,查找效率越高,因此,提高二叉排序树查找效率的办法是尽量降低二叉排序树的高度
4.4、平衡二叉树
为了降低二叉排序树的高度,提高查找效率,两位前苏联数学家G.M. Adelsen-Velskii和E.M.Landis于1962年提出一种高度平衡的二叉排序树,称为平衡二叉树(又称为AVL树)。
4.4.1、平衡二叉树定义
平衡二叉树(Balanced Binary Tree或Height-Balanced Tree)或者是一棵空二叉树 ,或者是具有下列性质的二叉排序树:
-
它的左子树和右子树都是平衡二叉树;
-
左子树与右子树的高度之差的绝对值不超过1。
结点的平衡因子(Balance Factor)定义为其左子树与右子树的高度之差:结点的平衡因子=左子树的高度 - 右子树的高度
平衡二叉树中任何一个结点的平衡因子只能是-1、0或1。下图(a)所示的二叉排序树是不平衡的,图(b)是一棵平衡二叉树,图中结点旁的数字是该结点的平衡因子。
在平衡二叉树中插入或删除一个节点,可能破坏二叉树度平衡性,因此,在插入和删除时,都要调整二叉树,使之始终保持平衡状态。
怎么来调整呢?调整最小不平衡子树(分为LL、LR、RL、RR)后续。
五、排序算法
如何写算法程序:
- 先简单再复杂
- 验证一步走一步
- 多打印中间结果
- 先局部再整体
- 没思路先细分
- 先粗糙再精细
- 变量改名(一看就明白代表什么意思)
- 语句合并
- 边界处理
如何计算时间复杂度:
- 计算主要代码会执行多少次(数学知识啊!),最重要的就看循环中那部分执行了多少次
- 忽略常数项,忽略低次项
空间复杂度:计算的是算法需要用到的额外空间
- 不用考虑临时变量
- 看需不需要其他的一个数组把数据挪过来挪过来,没有就是1。
什么是不稳定:
- 两个相等的数据在排完序之后,他们的相对顺序会发生改变。
- 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
怎么来验证算法的正确性:(对数器)
package book;
import java.util.Arrays;import java.util.Random;
/**
* 对数器
*/
public class DataChecker {
public static void main(String[] args) {
check();
}
static void check() {
int[] arr1 = generateRandomArray();
int[] arr2 = new int[arr1.length];
System.arraycopy(arr1, 0, arr2, 0, arr1.length);
Arrays.sort(arr1);
SelectionSort.selectionSort1(arr2);
boolean same = true;
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
same = false;
}
}
System.out.println(same == true ? "right" : "waong");
}
static int[] generateRandomArray() {
Random r = new Random();
int[] arr = new int[10000];
for (int i = 0; i < arr.length; i++) {
//一万以内的随机数
arr[i] = r.nextInt(10000);
}
return arr;
}
}
5.1、插入排序(直接插入排序、二分插入排序和希尔排序)
5.2、交换排序(冒泡排序和快速排序)
5.3、选择排序(直接选择排序和堆排序)
5.4、归并排序
看:归并排序算法
5.5、计数排序、基数排序、桶排序
5.6、总结