网图,侵权请联系删除
大O表达法,用来大概表示需要进行的时间
常见时间复杂度:
多项式时间复杂度
int i = 1;
int j = 2;
int sum = i + j;
i = 1;
while(i <= n){
i = i * 2;
}
2^x = n,则 x = log2(n),忽略底数O(logn)
由多个数据规模来决定时间复杂度:不能确定m、n的值,则为 O(m + n)
int cal(int m, int n) {
int sum_1 = 0;
int i = 1;
for (; i < m; ++i) {
sum_1 = sum_1 + i;
}
int sum_2 = 0;
int j = 1;
for (; j < n; ++j) {
sum_2 = sum_2 + j;
}
return sum_1 + sum_2;
}
常见为O(1)、O(n)、O(n^2)
在内存中顺序存储(占用一片连续的内存地址);每个元素有着自己的下标,可以通过下标查找元素;
因为数组在内存中顺序存储,所以可以直接通过下标读取到对应的数组元素 ,这种读取元素的方式叫做随机读取
int[] array = new int[]{3,1,2,5,4,9,7,2};
// 输出数组中下标为3的元素
System.out.println(array[3]);
直接通过下标赋值
int[] array = new int[]{3,1,2,5,4,9,7,2};
// 给数组下标为5的元素赋值
array[5] = 10;
// 输出数组中下标为5的元素
System.out.println(array[5])
存在三种情况
- 尾部插入
- 中间插入
- 超范围插入
尾部插入
直接插入到尾部空闲位置,等同于更新元素
中间插入
先把插入元素以及后面的元素向后移动,再将要插入的元素放到对应的数据位置
超范围插入
需要进行数组扩容:创建一个新的数组,再将旧数组的元素复制过去
将元素逐个向左移位
链表是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成,在内存中的存储方式是随机存储;
单向链表的每一节点又包含两部分,一部分存放数据data,一部分是指向下一个节点的指针next;第一个节点成为头节点,最后一个节点称为尾节点
双向列表不仅拥有data、next部分,还存放指向前置节点的prev指针
只能根据头节点开始向后一个一个节点逐一查找,时间复杂度:最坏的情况是O(n)
如果不考虑查找节点的过程,链表更新直接替换新数据即可,时间复杂度O(n)
无 | 查找 | 更新 | 插入 | 删除 |
---|---|---|---|---|
数组 | O(1) | O(1) | O(n) | O(n) |
链表 | O(n) | O(1) | O(1) | O(1) |
例如在多线程中,争夺公平锁的等待队列,就是按照访问顺序来决定线程在队列中的
次序的。
- 有且仅有一个特定的称为根的节点。
- 当n>1时,其余节点可分为m(m>0)个互不相交的有限集,每一个集合本身又是一 个树,并称为根的子树
满二叉树和完全二叉树的区别:满二叉树要求所有分支都是满的;而完全 二叉树只需保证最后一个节点之前的节点都齐全即可
链表实现
数组实现
当子孩子没有数据时数组相应的位置会空出来,可以方便计算节点位置
- 如果左子树不为空,则左子树上所有节点的值均小于根节点的值
- 如果右子树不为空,则右子树上所有节点的值均大于根节点的值
- 左、右子树也都是二叉查找树
自平衡:
特殊情况下,会导致“失衡”,解决方法:自平衡(红黑树、AVL树、树堆)
/**
* 按前序遍历的顺序构建二叉树
* @param inputList
* @return
*/
public static TreeNode createBinaryTree(LinkedList<Integer> inputList){
if (inputList == null || inputList.isEmpty()) return null;
TreeNode node = null;
Integer data = inputList.removeFirst();
if (data != null){
node = new TreeNode(data);
node.leftNode = createBinaryTree(inputList);
node.rightNode = createBinaryTree(inputList);
}
return node;
}
/**
* 二叉树的前序遍历
* @param node
*/
public static void preOrderTraveral(TreeNode node){
if (node == null) return;
System.out.print(node.data);
preOrderTraveral(node.leftNode);
preOrderTraveral(node.rightNode);
}
/**
* 二叉树的中序遍历
* @param node
*/
public static void inOrderTraveral(TreeNode node){
if (node == null) return;
inOrderTraveral(node.leftNode);
System.out.print(node.data);
inOrderTraveral(node.rightNode);
}
/**
* 二叉树的后序遍历
* @param node
*/
public static void postOrderTraveral(TreeNode node){
if (node == null) return;
postOrderTraveral(node.leftNode);
postOrderTraveral(node.rightNode);
System.out.print(node.data);
}
本质上是一种完全二叉树,有两种类型:1. 最大堆 2.最小堆
最大堆:任何一个父节点的值,都大于或等于它左、右孩子节点 的值。
最小堆:的任何一个父节点的值,都小于或等于它左、右孩子节点的值。
两类操作:“上浮”和下沉
操作:
- 删除:是单一节点的下沉,时间复杂度O(logn)
- 插入:是单一节点的上浮,时间复杂度O(logn)
- 构建:需要所有非叶子节点依次下沉,时间复杂度O(n)
应用:
- 实现优先队列
- 堆排序
/**
* 堆的上浮操作
* @param array 插入新数据后未调整的堆
*/
public static void upAdjust(int[] array){
int childIndex = array.length - 1;
int parentIndex = (childIndex - 1)/2; // 找到父节点
int temp = array[childIndex]; // temp 保存插入的叶子节点值,用于最后的赋值
while (childIndex > 0 && temp < array[parentIndex]){
array[childIndex] = array[parentIndex];
childIndex = parentIndex;
parentIndex = (childIndex - 1)/2;
}
array[childIndex] = temp;
}
/**
* 堆的下沉操作
* @param array 待调整的堆
* @param parentIndex 要“下沉”的父节点
* @param length 堆的有效长度
*/
public static void downAdjust(int[] array,int parentIndex,int length){
int temp = array[parentIndex];
int childIndex = 2 * parentIndex + 1; // 找到左孩子
while (childIndex < length){
// 如果存在右孩子,且右孩子比左孩子小,将指针指向右孩子
if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]){
childIndex++;
}
// 如果父节点小于两个子孩子的值,则跳出
if (temp < array[childIndex]) break;
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = 2 * parentIndex + 1;
}
array[parentIndex] = temp;
}
public static void buildHeap(int[] array){
// 从最后一个非叶子节点开始,依次做“下沉”调整
for (int i = (array.length - 2)/2;i >= 0;i--){
downAdjust(array,i,array.length);
}
}
队列遵循先进先出(FIFO)原则,优先队列不再遵循先进先出的原则,而是分为两种情况:
- 最大优先队列,无论入队顺序如何,都是当前最大的元素优先出队
- 最小优先队列,无论入队顺序如何,都是当前最小的元素优先出
特性:
入队:在数组末插入新节点,让新节点“上浮”到合适的位置,时间复杂度:O(logn)
出队:将堆顶的元素出栈,再将最后一个元素移到对顶,再进行“下沉”操作,时间复杂度:O(logn)
private int[] array;
private int size; // 当前队列大小
public PriorityQueue() {
// 初始长度为 32
array = new int[32];
}
/**
* 入队
* @param val
*/
public void enqueue(int val){
if (size > array.length) resize();
array[size++] = val;
HeapHelper.upAdjust(array,size); // 上浮调整,传入有效长度
}
/**
* 出队
* @return
* @throws Exception
*/
public int dequeue() throws Exception {
if (size <= 0) throw new Exception("no more data");
int head = array[0];
array[0] = array[--size];
HeapHelper.downAdjust(array,0,size);// 0:要下沉的节点,这里是第一个,size:有效长度
return head;
}
这里直接搬书里的
- 什么是树
树是n个节点的有限集,有且仅有一个特定的称为根的节点。当n>1时,其余节点可 分为m个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树。- 什么是二叉树
二叉树是树的一种特殊形式,每一个节点最多有两个孩子节点。二叉树包含完全二叉 树和满二叉树两种特殊形式。- 二叉树的遍历方式有几种
根据遍历节点之间的关系,可以分为前序遍历、中序遍历、后序遍历、层序遍历这4 种方式;从更宏观的角度划分,可以划分为深度优先遍历和广度优先遍历两大类。- 什么是二叉堆
二叉堆是一种特殊的完全二叉树,分为最大堆和最小堆。
在最大堆中,任何一个父节点的值,都大于或等于它左、右孩子节点的值。
在最小堆中,任何一个父节点的值,都小于或等于它左、右孩子节点的值。- 什么是优先队列
优先队列分为最大优先队列和最小优先队列。
在最大优先队列中,无论入队顺序如何,当前最大的元素都会优先出队,这是基于最 大堆实现的。
在最小优先队列中,无论入队顺序如何,当前最小的元素都会优先出队,这是基于最 小堆实现的。