数据结构与算法(数组、链表)

数据结构与算法

基础查找算法

二分查找

二分查找也称折半查找,是在一组有序(升序/降序)的数据中查找一个元素,它是一种效率较高的查找方法。

/**
  * 二分查找法:
  * @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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值