数据结构
- 1.时间复杂度
- 1.2渐进符号
- 1.3递归的时间复杂度
- 2.线性结构(逻辑)
- 2.1线性表的顺序存储
- 2.2线性表的链式存储
- 2.3循环链表
- 2.4双链表
- 3.栈
- 3.1栈的顺序存储
- 3.2栈的链式存储(链栈)
- 4.队列
- 4.1队列的顺序存储
- 4.2队列的链式存储
- 5.串
- 5.1串的匹配模式
- 6.数组
- 7.矩阵
- 7.1对称矩阵 (aij = aji)
- 7.2三对角矩阵
- 7.3稀疏矩阵
- 8.树(非线性)
- 8.1性质
- 8.2满二叉树与完全二叉树
- 8.3二叉树的存储
- 8.4二叉树的遍历
- 8.5平衡二叉树
- 8.6二叉排序树(二叉查找树)
- 8.7最优二叉树(哈夫曼树)
- 8.8线索二叉树
- 9.图(线性结构)
- 9.1图的存储结构
- 9.2图的遍历
- 9.3拓扑排序
1.时间复杂度
保留最高阶项,系数化为1
O(1)<O(log₂n)<O(n)<O(2ⁿ)<O(n!)<O(nⁿ)
1.2渐进符号
O:渐进上界 >=
Ω:渐进下界 <=
Θ:渐进紧致界 =
1.3递归的时间复杂度
1.每次递归的时间复杂度不变的情况:
递归的次数 * 每次递归的时间复杂度
2.递归式主方法:
T(n) = aT(n/b) + f(n)
一个算法比另一个快,时间复杂度小
2.线性结构(逻辑)
通常采用顺序存储,链式存储
基本操作:插入,删除,查找
1.定义:线性表是n个元素的有限序列
2.非空线性表特点:
除第一个元素外,序列中的每个元素均只有一个直接前驱
除最后一个元素外,序列中的每个元素均只有一个直接后继
2.1线性表的顺序存储
一组地址连续的存储单元,依次存储线性表中的数据元素
优点:可以随机存取表中的数据元素
缺点:插入和删除需要移动元素
插入一个元素(共有n+1个插入位置)的期望值:n/2
删除一个元素(共有n个可删除元素)的期望值:
(n-1)/2
线性表顺序存储
class SequenceList {
int N = 10; // 容量
int[] arr;
int n; // 表长(表中元素的个数)
void init() {
arr = new int[N];
for (int i = 0; i < N / 2; i++) {
//给一半的元素
arr[i] = i + 1;
}
n = N / 2;
for (int i = 0; i < n; i++) {
//打印输出所赋值的元素
System.out.print(arr[i] + " ");
}
}
}
插入
时间复杂度:最好O(1),最坏和平均O(n)
void insert(int k, int x) {
// k位置,x要插入的数字
if (k < 1 || k > n + 1) {//越界
return ;
}
for (int i = n; i >= k; i--) {
//从最后一个数开始往后移
arr[i] = arr[i - 1];
}
arr[k - 1] = x;//数组从0开始,所以k-1
n++;//插入之后表长要+1
for (int i = 0; i < n; i++) {
// 打印输出
System.out.print(arr[i] + " ");
}
System.out.println();
}
删除
时间复杂度:最好O(1),最坏和平均O(n)
void delete(int k) { // k要删除的位置
if (k < 1 || k > n) { // 越界
return false;
}
for (int i = k; i < n; i++) {
// 从k开始往前移
arr[i - 1] = arr[i];
}
n--; // 插入之后表长要-1
for (int i = 0; i < n; i++) {
// 打印输出
System.out.print(arr[i] + " ");
}
System.out.println();
}
查找(特别快)
时间复杂度:O(1)
int getElement(int k) {
if (k < 1 || k > n) {
return -1;
}
return arr[k - 1];
}
2.2线性表的链式存储
数据域和指针域
头指针:指向链表的第一个结点
头结点:链表的第0个结点
插入,删除,查找的时间复杂度:
最好O(1),最坏和平均O(n)
class Node {
int data;
Node next; // 省略了new Node();相当于指针
public Node(int data) {
this.data = data;
}
public Node() {
}
}
不带头结点
class LinkList {
Node list;
void init() {
// 调用有参构造方法
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
list = n1; // list指向n1
n1.next = n2; // 把n2赋值给n1的指针
n2.next = n3;
}
void printList() {
Node p = list; // 可以构成循环,没有的话只会执行一次
while (p != null) {
System.out.print(p.data + " ");
p = p.next; // p的下一个结点地址赋给p
}
System.out.println();
}
}
带头结点
class HeadLinkList {
Node head;
void init() {
head = new Node(); // 调用无参构造方法,因为头结点不存储数据
Node n1 = new Node(3);
Node n2 = new Node(2);
Node n3 = new Node(1);
head.next = n1; // 头结点的下一个结点指向n1
n1.next = n2; // 把n2赋值给n1的指针
n2.next = n3;
}
void printList() {
Node p = head.next; // 头结点不存储数据,所以要指向头结点的下一个结点
while (p != null) {
System.out.print(p.data + " ");
p = p.next; // p的下一个结点地址赋给p
}
System.out.println();
}
}
带头结点插入
boolean insert(int k, Node node) {//k是位置
if (k < 1) {
return false;
}
int i = 0;//i从0开始是头结点
Node p = head; // 指针指向头结点
while (i < k - 1 && p != null) {
//i,p往后移
i++;
p = p.next;
}
if (p == null) {
return false;
}
node.next = p.next;
p.next = node;
return true;
}
node.next = p.next;
p.next = node;
不带头结点的插入
boolean insert(int k, Node node) { // k是位置
if (k < 1) {
return false;
}
if (k == 1) {
node.next = list;
list = node;
return true;
}
int i = 1;
Node p = list;
while (i < k - 1 && p != null) {
// i,p往后移
i++;
p = p.next;
}
if (p == null) {
return false;
}
node.next = p.next;
p.next = node;
return true;
}
带头结点删除
boolean delete(int k) {
if (k < 1 || k > head.data) {
return false;
}
int i = 0;
Node p = head;
while (i < k - 1) {
i++;
p = p.next;//p往后移
}
Node s = p.next;
p.next = s.next;
//p指向下一个结点的下一个结点
head.data--;
return true;
}
不带头结点删除
boolean delete(int k) {
if (k < 1 || k > length) {
return false;
}
if(k == 1){
list = list.next;
return true;
}
int i = 1;
Node p = list;
while (i < k - 1) {
i++;
p = p.next; // p往后移
}
Node s = p.next;
p.next = s.next;
// p指向下一个结点的下一个结点
length--;
return true;
}
查找
Node getData(int k) {
if (k < 1 || k > head.data) {
return null;
}
int i = 1; // 找第k个所以从1开始
Node p = head.next;
while (i < k) {
i++;
p = p.next;
}
return p;
}
2.3循环链表
插入尾指针:直接插
删除:要循环得到前一个结点
2.4双链表
插入
Node.next = p.next;
if(p.next != null){
p.next.prev = Node;
}
p.next = Node;
Node.next.prev = p;
删除
Node s = p.next;
p.next = s.next;
if(s.next != null){
s.next.prev = p;
}
3.栈
先进后出,后进先出
3.1栈的顺序存储
初始化空栈
int[] a = new int[5];
top = 0;
判栈空
if(top == 0) return true;
return false;
入栈
a[top] = x;
top++;
出栈
int pop(){
if(isEmpty()) return -1;
top--;
return a[top];
}
读栈顶元素
int Top(){
if(isEmpty()) return -1;
return a[top -1];
}
3.2栈的链式存储(链栈)
初始化空栈
top = null;
判栈空
if(top == null) return true;
return false;
入栈
Node.next = top;
top = Node;
出栈
int pop(){
if(isEmpty()) return -1;
top = top.next;
}
读栈顶元素
int Top(){
if(isEmpty()) return -1;
return a[top];
}
共享栈:top0 == top1 - 1
4.队列
先进先出,后进后出
队尾插入,队头删除
入队和出队不会遍历元素
4.1队列的顺序存储
int N = 6;
int[] q = null;
int front,rear;//头指针,尾指针
int size;
初始化
int[] q = new int[N];
front = rear = 0;
size = 0;
判空
if(front == rear) return true;
return false;
入队
q[rear] = x;
rear++;//尾指针往后移
size++;
出队
if(isEmpty()) return -1;
front++;//头指针往后移
size--;
读队头元素
if(isEmpty()) return -1;
return q[front];
循环队列
入队和出队不需要移动元素
(rear + 1) % N;
(front + 1) % N;
//判空
size == 0;//空
size == N;//满
4.2队列的链式存储
Node head = null,tail = null;//头结点,尾结点
初始化
head = tail = new Node();
head.next = null;
判空
if(head == tail)
入队
node.next = tail.next;
tail.next = node;
tail = node;
出队
if(head == tail) return -1;
if(head.next = tail){
//队列只有一个元素
Node s = head.next;
head.next = s.next;
tail = head;//把队尾指针修改为队头指针
return s;
}
Node s = head.next;
head.next = s.next;//头插法
return s;
获取对头元素
if(head == tail) return -1;
return head.next;
5.串
由字符构成的有限序列,是一种特殊的线性表
空串:长度为0
空格串:由一个或多个空格构成
子串:由串中任意长度的连续字符构成
主串:含有子串的串
空串是任意串的子串
5.1串的匹配模式
1.朴数
int Index(String n, String m) {
int k = 1, i = 1, j = 1;
while (i <= n.length && j <= m.length) {
char a = n.chatAt(i - 1);
char b = m.chatAt(j - 1);
if (a == b) {
i++;
j++;
} else {
k++;
i = k;
j = 1;
}
}
if (j > m.length) {
return k;
} else {
return -1;
}
}
时间复杂度 | 时间 | 次数 |
---|---|---|
最好 | O(m) | m次 |
最坏 | O(n*m) | (n-m+1)*m |
平均 | O(n+m) | (n+m)/2 |
2.KMP算法
串的前缀:包含第一个字符且不包含最后一个字符的子串
串的后缀:包含最后一个字符且不包含第一个字符的子串
第i个字符的next值:1~i-1串中,最长相等前后缀长度+1
next[1] = 0
next[2] = 1
时间复杂度:O(n+m)
6.数组
1.一维数组
Loc:首地址
L:元素大小
2.二维数组
a(N,M)
行优先
列优先
7.矩阵
7.1对称矩阵 (aij = aji)
按行优先:存储下三角区+主对角线
7.2三对角矩阵
7.3稀疏矩阵
稀疏矩阵的顺序存储结构称为三元组顺序表,常见的是十字链表
8.树(非线性)
度:子树的个数为该结点的度
分支结点:度不为0的结点
树的高度(深度):一棵树的最大层树
8.1性质
1.树的性质
1.树中的结点总数=树中所有结点的度数之和+1
2.度为m的数中第i层上至多有mⁱ⁻¹个结点
3.高度为h度为m的数至多有(mʰ-1)/(m-1)
4.具有n个结点,度为m的数的最小高度是
2.二叉树的性质
二叉树中的结点区分左右子树且最大度为2
1.二叉树第i层上至多有2ⁱ⁻¹个结点
2.高度为h的二叉树至多有2ʰ-1个结点
3.度为0的结点=度为2的结点数+1
4.具有n个结点的完全二叉树的高度为
[log₂n]向下取整+1或[log₂(n+1)]向上取整
8.2满二叉树与完全二叉树
满二叉树:高度为k的二叉树有2ᵏ-1个结点
完全二叉树:除了最后一层外,其余层是满的,在最后一层上,结点必须从左往右,不可以留空
具有n个结点的二叉树有几种
8.3二叉树的存储
1.顺序存储
总结点为n,编号为i的结点
i = 1,为根结点,i > 1,该结点的双亲结点为
i / 2向下取整
2i < n,2i为该结点的左孩子,反之没有左孩子
2i + 1 < n,2i+1为该结点的右孩子,反之没有
最坏情况:一个深度为k且只有k个结点的二叉树,需要2ᴷ-1个存储单元
2.链式存储
二叉链表:n个结点有2n个指针域,n-1个有效指针域,n+1个空指针域
三叉链表:n个结点有n+2个空指针域
完全二叉树采用顺序存储
一般二叉树采用链式存储
8.4二叉树的遍历
先序遍历:根左右
中序遍历:左根右
后序遍历:左右根
层次遍历:从上到下,从左到右
8.5平衡二叉树
二叉树中任意一个结点的左右子树高度之差的绝对值不超过1
叶子结点满足平衡二叉树的要求
完全二叉树一定是平衡二叉树
平衡二叉树不一定是完全二叉树
8.6二叉排序树(二叉查找树)
关键字:根结点
>左子树的所有结点
<右子树的所有结点
左右子树也是二叉排序树
中序遍历的遍历得到的序列是有序序列
8.7最优二叉树(哈夫曼树)
是带权路径长度最短的树
WPL=叶子结点的带权路径长度之和
只有度为0和度为2的结点,没有度为1的结点
总结点:2n-1个
构造哈夫曼树:
从前往后找两个权值最小的
左小右大
加入末尾
权值相同,从前往后
哈夫曼编码:左0右1
等长编码:2x次>=表格中的位数,每个频率*x
压缩比:(等长编码-哈夫曼编码)/等长编码
8.8线索二叉树
有孩子的赋为1,没有孩子赋为0
9.图(线性结构)
任意两个结点之间都可能有直接关系
前驱和后继的数目没有限制
G(V,E)
G是图
V是图中顶点的非空有限集合
E是图中边的有限集合
1.有向图:图中的每条边都是有方向的<>
2.无向图:图中的每条边都是无方向的()
3.完全图:每个顶点与其它n-1个顶点之间有边
无向完全图:[n(n-1)]/2条边
有向完全图:n(n-1)条边
度(D):与该顶点相关的边的数目
总度数=2e//e指边的个数
连通图(无向):任意两个顶点都是连通的
最少:n-1条边,最多:[n(n-1)]/2条边
强连通图(有向)
最少:n条边,最多:n(n-1)条边
9.1图的存储结构
1.邻接矩阵表示法
图(0,1表示),网(♾️,权值表示)
无向图的邻接矩阵是对称的,有向图不一定
无向图:顶点的度是第i行中不为0的元素个数
有向图:i行是出度,j列是入度
非0元素个数:有向图e个,无向图2e个
2.邻接链表表示法
稠密图用邻接矩阵,稀疏图用邻接链表
9.2图的遍历
1.深度优先(DFS):访问邻接点(一个),回溯
2.广度优先(BFS):访问邻接点(全部),回溯
时间复杂度:
邻接矩阵:O(n²)
邻接链表:O(n+e)
9.3拓扑排序
1.AVO网不可以构成环路
2.排序:
①选择一个入度为0的结点,输出
②从网中删除该顶点及与该顶点有关的所有弧
③重复上述2步,不存在入度为0的结点
614325
在有向无环图的拓扑排序中,顶点i在j之前
可能存在弧<i,j>,一定不存在弧<j,i>
可能存在i到j的路径,一定不存在j到i的路径