数据结构与算法
基础查找算法
二分查找
二分查找也称折半查找,是在一组有序(升序/降序)的数据中查找一个元素,它是一种效率较高的查找方法。
/**
* 二分查找法:
* @param arr 数组,(升序/降序)
* @param target 要查询到值
* @return int 查询到会返回(该值的下标),没有查询到返回(-1)
*/
public static int binarySearchB(int[] arr, int target) {
int i = 0;
int j = arr.length;
while (i < j) {
// 中间值 (向下取整)
int m = (j + i) / 2;
if (target < arr[m]) {
// 左边位置
j = m;
} else if (arr[m] < target) {
// 右边位置
i = m + 1;
} else {
// 当前位置
return m;
}
}
return -1;
}
顺序查找
顺序查找,时间复杂度是O(n)。
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i;
}
}
时间复杂度
时间频度:一个算法花费的时间与算法中语句执行次数成正比,它算法执行语句次数多,它就花费时间长;T(n)
时间复杂度按n越大来排序:常数阶O(1)、对数阶O(logn)、线性阶O(n)、线性对数阶O(nlogn)、平方阶O(n²)、立方阶O(n³)、…、指数阶O(2的n次方)。
例子:
// O(N)的时间复杂度
for(i=1; i<=n; ++i){
j = i;
j++;
}
// O(logN)的时间复杂度
int i = 1;
while(i<n){
i = i * 2;
}
// O(n²) 的时间复杂度
for(x=1; i<=n; x++){
for(i=1; i<=n; i++) {
j = i;
j++;
}
}
数组
数组是由一组元素组成的数据结构,每个元素至少有一个索引来标识。数组的起始位置BaseAddress,公式:BaseAddress + i * size(数据类型,int数组就是4,double就是8),可以计算出i元素的地址。
int[] arr = new int[]{1,2,3};
数组的结构:
- 8个字节markword
- 4个字节class指针(压缩class指针)
- 4个节的数组大小
- 数组元素,如果大小不是8字节的倍数,会自动补齐
数组的随机访问,时间复杂度是O(1)。
案例:
/**
* 自定义-数组-结构:[1 2 3 4]
*/
public class T implements Iterable<Integer> {
private int size = 0; // 数组的个数
private int num = 10; // 数组的容量
private int[] arr = new int[num]; // 初始化数组
/**
* 添加对象
*/
public void add(int element) {
add(element, size);
}
// 扩容
private void kr() {
if (size >= num) {
num += num >> 1;
int[] newArr = new int[num];
System.arraycopy(arr, 0, newArr, 0, size);
arr = newArr;
}
}
/**
* 添加对象 (指定下标的位置 进行创建对象)
* @return
*/
public void add(int element, int index) {
// 例子:
// int[] arr = new int[]{1,2,3}
// int[] arr = new int[]{1,[5],2,3}
// index = 1 新数组从 1+1=2,开始,拷贝个数:3-1=2,拷贝2个(2,3)
if (index >= 0 && index < size) {
kr();
// 参数1:原数组、参数2:原数组的下标、参数3:新的数据、参数4:要拷贝的位置、参数5:拷贝的个数
System.arraycopy(arr, index, arr, index + 1, size - index);
}
arr[index] = element;
size++;
}
/**
* 获取单条
*/
public int get(int index) {
return arr[index];
}
// 遍历方式1
public void foreach() {
for (int i = 0; i < size; i++) {
System.out.println(arr[i]);
}
}
// 遍历方式2
public void foreach2(Consumer<Integer> consumer) {
for (int i = 0; i < size; i++) {
consumer.accept(arr[i]);
}
}
// 遍历方式3
public IntStream foreach3() {
int[] ints = Arrays.copyOf(arr, size);
return IntStream.of(ints);
}
// 遍历方式4
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
int i = 0;
@Override
public boolean hasNext() {
return i < size; // 判断是否有下一个元素
}
@Override
public Integer next() {
return arr[i++]; // 获取当前元素,并移动下一个元素
}
};
}
// 删除 - 根据下标进行删除元素
private int remove(int index) {
int element = get(index);
if (index < size - 1) {
System.arraycopy(arr, index + 1, arr, index, size - index - 1);
}
size--;
return element;
}
}
二维数组:
// 二维数组
int[][] arr = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
};
// 遍历二维数据
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.println(arr[i][j]);
}
}
链表
链表是数据元素的线性结构,其每个元素都指向下一个元素,元素存储上并不是连续的;链表的头节点是哨兵,它不储存元素,用来边界判断。
分为:单向链表(每个元素只知道下一个元素)、双向链表(每个元素都知道上一个元素和下一个元素)、循环链表(链表尾节点指向头节点)
性能:随机访问,时间复杂度O(n),插入是O(i),插入结束位置是O(n),而插入头节点是O(1),中间位置是index+O(1)。
案例:
/**
* 自定义-链表-结构:1 2 3 4
*/
public class T implements Iterable<Integer> {
private static class Node {
private int value; //值
private Node next; //下一个节点
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
}
private Node head; // 头节点
// 添加头节点
public void addFirst(int element) {
head = new Node(element, head);
}
// b 遍历 (有1种方式)
public void loop1() {
Node n;
for (n = head; n != null; n = n.next) {
System.out.println(n.value);
}
}
// b 遍历 (有2种方式)
public void loop2(Consumer<Integer> consumer) {
Node n;
for (n = head; n != null; n = n.next) {
consumer.accept(n.value);
}
}
// b 遍历 (有3种方式)
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
Node n = head;
@Override
public boolean hasNext() {
return n != null; // 判断是否有下一个元素
}
@Override
public Integer next() {
int value = n.value;
n = n.next;
return value; // 返回当前元素,并移动下一个元素
}
};
}
// 添加尾节点
public void addLast(int element) {
Node lastNode = getLastNode();
if (lastNode == null) {
addFirst(element);
return;
}
lastNode.next = new Node(element, null);
}
// 找到最后一个节点
private Node getLastNode() {
if (head == null) {
return null;
}
Node n;
for (n = head; n.next != null; n = n.next) {
}
return n;
}
// 根据下标获取单条数据
public int getIndex(int index) {
Node node = getNode(index);
if (node == null) {
System.out.println("下标越界");
return -1;
}
return node.value;
}
// 根据下标获取元素
private Node getNode(int index) {
Node n;
int i = 0;
for (n = head; n != null; n = n.next) {
if (i == index) {
return n;
}
i++;
}
return null; // 没找到
}
// 根据索引添加数据
public void addIndex(int index, int element) {
if (index == 0) {
addFirst(element);
return;
}
Node prev = getNode(index - 1);
if (prev == null) {
System.out.println("下标越界");
return;
}
prev.next = new Node(element, prev.next);
}
// 删除头元素
public void removeFirst() {
if (head == null) {
System.out.println("下标越界");
return;
}
head = head.next;
}
// 删除指定元素
public void remove(int index) {
if (index == 0) {
removeFirst();
return;
}
Node prev = getNode(index - 1);
if (prev == null) {
System.out.println("下标越界");
return;
}
Node next = prev.next; // 被删除的节点
if (next == null) {
System.out.println("下标越界");
return;
}
prev.next = next.next;
}
}
链表:有带哨兵的链表、双向链表、环形链表。
链表相关案例:链表反转、有序链表去重、判断链表是否存在环、合并链表、判断链表是否是回文链表、删除倒数节点、根据值删除链表中节点等,可在码云上链表仓库查看。
递归
是一个函数在其定义中直接或间接调用自身的一种方法。直接或间接地调用自身的算法称为递归算法。
案例:
// 1. 阶乘 - 求5的阶乘 1 * 2 * 3 * 4 * 5
public static int f1(int n) {
if (n == 1) {
return 1;
}
return n * f1(n - 1);
}
斐波那契数列
// 斐波那契数列,也是 多路递归 是:0 1 1 2 3 5 8 13 21.。。(前一个数 + 后一个数 = 下一个值)
public static int f1(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
return f1(n - 1) + f1(n - 2);
}
// 运行
System.out.println(f1(8));
// 优化上面代码,可使用记忆法
排序
冒泡排序
// 冒泡排序 - 1 数组:5 4 3 2 1
public static void test1(int[] arr, int j) {
if (j == 0) {
return;
}
for (int i = 0; i < j; i++) {
if (arr[i] > arr[i + 1]) {
int t = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = t;
}
}
test1(arr, j - 1);
}
// 运行
int[] arr = {5, 4, 3, 2, 1};
test1(arr, arr.length - 1);
插入排序
// 插入排序 - 1 数组 4 3 2 1 / 3 4 2 1
public static void test1(int[] arr, int low) {
if (low == arr.length) {
return;
}
// 已排序的 i 、 未排序的下标(low)
int t = arr[low]; // 未排序当前对象
int i = low - 1;
while (i >= 0 && arr[i] > t) {
arr[i + 1] = arr[i];
i--;
}
arr[i + 1] = t;
test1(arr, low + 1);
}
// 运行
int[] arr = {5, 4, 3, 2, 1};
test1(arr, 1); // 默认未排序下标是1,已排序下标是0