程序的核心:数据结构与算法
数据结构:通过合理组织与存储数据,优化数据的访问、插入、删除等操作效率。
算法:解决特定问题的有限步骤描述,明确输入、输出及执行逻辑。
时间复杂度:算法时间效率度量,假设要处理的数据总量x,x足够大,算法为了达成某个目消耗的计算次数y。
例:
“O符号表示渐近上界,忽略低阶项和常数系数,仅关注最高阶项。”
y=ax+b a是系数,b是常数。 y=x O(n)
y=ax^2+bx+c y=x^2 O(n^2)
y=a y=1 O(1)
a^y=x y=logax O(logn)
时间复杂度具体例子:
1.长度是x的无序数组,找一个值num。 [5,7,4,3,2,0,1,9]
存在可能第一个数据是要查找的数据num,需要遍历一次。存在可能最后一个数据是要查找的num,需要遍历x次。最坏情况下需遍历全部元素(O(n)),平均情况为O(n/2),但常数系数忽略,仍记为O(n)。
2.计算 1+2+3+4+5+6+7+......+x 求和
int sum = 0;
for(i=1;i<=x;i++){
sum = sum +i;
}
运算x次(循环x次),时间复杂度是O(n)
sum=(1+x)*x/2(相当于等差数列,高斯求和公式)
运算一次(直接计算),时间复杂度是O(1)
3.冒泡排序 : [5,7,4,3,2,0,1,9]
比较相邻的两个元素,如果前一个比后一个大,就交换它们。每一轮排序 都会让当前未排序部分的最大元素“冒泡”到正确的位置。重复这个过程,直到整个数组有序。
第一轮运算次数:x-1次,1个数据到位。
第二轮运算次数:x-2次,1个数据到位。
第三轮运算次数:x-3次,1个数据到位。
......
第x-1轮运算次数:x-(x-1)=1次,2个数据到位。
y=x-1+x-2+x-3+x-4+......+1=x^2
时间复杂度是O(n^2)。
public class BubbleSort {
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) { // 外层循环控制轮数
for (int j = 0; j < n - i - 1; j++) { // 内层循环控制比较次数
if (arr[j] > arr[j + 1]) { // 如果前一个比后一个大,交换
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
public static void main(String[] args) {
int[] arr = {5, 7, 4, 3, 2, 0, 1, 9};
bubbleSort(arr);
System.out.print("排序后的数组:");
for (int num : arr) {
System.out.print(num + " ");
}
}
}
4.二分查找法: [1,4,5,7,9,16,19,21] 前提有序数组。
初始化:
left = 0(数组起始索引)
right = arr.length - 1(数组末尾索引)
循环条件:
当 left <= right 时,继续查找。
中间位置计算:mid = left + (right - left) / 2(防止整数溢出)
比较与调整边界:
若 arr[mid] == num,返回 mid(找到目标)。
若 arr[mid] < num,说明目标在右侧,更新 left = mid + 1。
若 arr[mid] > num,说明目标在左侧,更新 right = mid - 1。
若 arr[mid] > num,说明目标在左侧,更新 right = mid - 1。
当目标值 num 不存在 时,算法会通过以下步骤终止:
最终状态:在最后一次循环中,left 和 right 会 交叉(即 left > right)。
此时,left 指向第一个 大于 num 的元素,right 指向最后一个 小于 num 的元素。
时间复杂度 第一轮 x x/2^0 1
第二轮 x/2 x/2^1 1
第三轮 x/2/2 x/2^2 1
......
第k轮 1==x/2^(k-1) 1
k=log2x O(logn)
直接问题规模下手,时间复杂度O(n)。
循环减半.时间复杂度O(logn)。
k层关于n的循环,O(n^k)
如何设计算法以高效处理无序数据,从而降低时间复杂度?
1,把数据放进无序数组,再查找数据。
把数据放进有序数组,时间复杂度 O(n),有序数组时间复杂度低,二分查找法O(logn)。但是数据是无序变有序, 需要先排序,再 二分查找,那么时间复杂度是冒泡排序O(n^2)*二分查找法O(logn),整体看时间复杂度会更高。
2,哈希算法:
数组通过下标操作,时间复杂度是O(1)。需要知道整个数组的下标,需要知道数据存放在哪里,下标是多少,通过算法放数据,取数据也根据算法取。
数据num对整个数组取余:num%art.length 来设置数据存放的位置
假设用长度是10的数组存放数据,存放数据19,19除10余9,存放在九号位置。数据10对10取余得0,10存放在0号位置,数据13对10取余3,放在三号位置。
时间复杂度是O(1)。
哈希冲突:当出现10和20两个数据同时出现时,两个不同的数据计算出来在同一个位置,新的数据会覆盖掉旧的数据,
为解决哈希冲突,拉链法,在发生冲突的位置加入链表,数组在内存中申请一块连续的空间,物理上也是连续的,数据存储的位置是随机的,为了能确实找到数据,每个位置除了存储的数据本身,还会存储下一个节点的地址。
数组:链表不长时,遍历链表时间可以忽略,时间复杂度是O(1)。链表很长的时候,遍历链表时间不可忽略,时间复杂度是O(n)。数据量大的时候整体时间复杂度仍然很高。
单向链表:只可以向后查找
双向链表:可以往前查找,也可以向后查找。
3,有序二叉树:
数据存放规则满足左边节点的值小,右边节点值大。
[10,15,20,5,1,7,11]
存放的数据量是 2^0+2^1+2^2+……+2(k-1)=x
2^0+2^0+2^1+2^2+……+2(k-1)-2^0=x
2(2^0)+2^1+2^2+……+2(k-1)-2^0=x
2^1+2^1+2^2+……+2(k-1)-2^0=x
……
2^k-2^0=x
存放的数据量是:2^k-1=x 树的层数 k=log2x
理想情况(树是对称的),时间复杂度是O(logn)
非理想状况的有序二叉树:
有序二叉树时间复杂度不稳定,不是总是O(logn),在极端情况下(如单支树)退化为O(n),需平衡优化。
4,平衡二叉树:
数据存放满足左边节点的值小,右边节点值大,左右两棵子树高度差不超过1。
利用数组[5,7,4,2,0,3,1,6]构建平衡二叉树,从插入位置向上检测 ,从发现的第一个不平衡节点,处开始旋转。平衡二叉树,不管存放什么数据,树的节点总是较为理想状态。
5,红黑树(最优二叉树)
让时间复杂度稳定在O(logn)
2-3-4树 value有序
value存储数值,next可以存储下一个数据节点的地址
2-3-4树从下往上构建,超过四节点所能容纳数据量,中间值往上一层,原四节点的三个值分为一个三节点和一个二节点。继续填数据,层层向上构建。每层内是几节点就相应分出几个叉。
红黑树
红黑树的五大规则
一,每个节点是红色或黑色。没有其他颜色,只能是红或黑。
二,根节点必须是黑色。如果根节点是红色,必须调整为黑色。
三,所有叶子节点(NIL 节点)是黑色。红黑树中的叶子节点是空指针(NIL),它们被视为黑色。
四,红色节点的两个子节点必须是黑色(即不能有两个连续的红色节点)。这条规则确保没有两个相邻的红色节点,防止路径过长。
五,从任意节点到其所有叶子节点的路径上,黑色节点的数量必须相同(称为“黑高”相同)。这保证了树的平衡性,避免某些路径过长。
规则四、五确保树高度平衡,最长路径≤2×最短路径,保证操作稳定在O(log n)。(因为不能有两个连续的红色节点,最短路径全黑,(黑+黑+黑+叶子)最长路径红黑交替(黑+红+黑+红+黑+红+叶子))。