数据结构学习-线性结构(数组、链表、栈、队列)

数据结构学习-线性结构(数组、链表、栈、队列)

线性结构介绍:

线性结构是数据结构中的一种基本类型,它是一种数据元素之间存在一对一关系的数据结构。在线性结构中,每个元素(除了第一个和最后一个)都有一个直接前驱和一个直接后继。常见的线性结构有数组、链表、栈和队列等。

1.数组(Array)

描述:

数组是一种线性数据结构,用于存储固定大小的有序集合。数组中的元素可以通过索引直接访问,这意味着访问速度非常快。数组可以在静态分配或动态分配中创建,具体取决于编程语言和应用场景。

优点:
  • 快速访问:通过索引可以直接访问元素,时间复杂度为O(1)。
  • 缓存友好:由于数组元素在内存中是连续存储的,因此访问相邻元素时可以利用CPU缓存,提高性能。
缺点:
  • 固定大小:一旦创建了数组,其大小就不能改变,这限制了灵活性。
  • 插入和删除效率低:在数组中间插入或删除元素时,需要移动后续元素,时间复杂度为O(n)。
面试题:

题目:如何在已排序的数组中查找一个元素?

解答:可以使用二分查找算法,时间复杂度为O(log n)。

代码说明:

public class BinarySearch {
    // 二分查找方法
    public static int binarySearch(int[] array, int target) {
        int left = 0; // 左边界
        int right = array.length - 1; // 右边界

        while (left <= right) {
            int mid = left + (right - left) / 2; // 计算中间位置,防止溢出

            if (array[mid] == target) {
                return mid; // 找到目标元素,返回其索引
            } else if (array[mid] < target) {
                left = mid + 1; // 目标在右半部分,更新左边界
            } else {
                right = mid - 1; // 目标在左半部分,更新右边界
            }
        }

        return -1; // 没有找到目标元素
    }

    public static void main(String[] args) {
        int[] sortedArray = {1, 2, 3, 4, 5, 6, 7, 8, 9}; // 已排序的数组
        int target = 5; // 要查找的目标元素
        int index = binarySearch(sortedArray, target); // 调用二分查找方法

        if (index != -1) {
            System.out.println("Element found at index: " + index); // 找到目标元素,输出索引
        } else {
            System.out.println("Element not found in the array."); // 没有找到目标元素
        }
    }
}

题目:如何在数组中实现动态扩容?

解答:当数组空间不足时,可以创建一个新的更大的数组,将原数组中的元素复制到新数组中,然后释放旧数组的空间。

代码说明:

public class DynamicArray {
    private int[] array; // 存储数组
    private int size; // 当前数组中的元素数量
    private int capacity; // 数组的容量

    // 构造函数,初始化数组的初始容量
    public DynamicArray(int initialCapacity) {
        this.capacity = initialCapacity;
        this.array = new int[initialCapacity];
        this.size = 0;
    }

    // 添加元素到数组中
    public void add(int value) {
        if (size == capacity) {
            resize(); // 如果当前容量已满,进行扩容
        }
        array[size++] = value; // 将新元素添加到数组末尾,并增加元素数量
    }

    // 扩容方法
    private void resize() {
        int newCapacity = capacity * 2; // 新容量为当前容量的两倍
        int[] newArray = new int[newCapacity]; // 创建新的数组

        // 将原数组中的元素复制到新数组中
        for (int i = 0; i < size; i++) {
            newArray[i] = array[i];
        }

        array = newArray; // 更新引用,指向新数组
        capacity = newCapacity; // 更新容量
    }

    public static void main(String[] args) {
        DynamicArray dynamicArray = new DynamicArray(3); // 初始化一个容量为3的动态数组
        dynamicArray.add(1); // 添加元素
        dynamicArray.add(2);
        dynamicArray.add(3);
        dynamicArray.add(4); // 触发扩容

        // 输出数组中的所有元素
        for (int i = 0; i < dynamicArray.size; i++) {
            System.out.println("Element at index " + i + ": " + dynamicArray.array[i]);
        }
    }
}

2. 链表(Linked List)

描述:

链表是一种线性数据结构,其中的元素(节点)通过指针连接在一起。每个节点包含数据部分和指向下一个节点的指针。链表可以是单向链表、双向链表或循环链表。

优点:
  • 动态大小:链表可以在运行时动态增加或减少节点,灵活性高。
  • 高效的插入和删除:在链表中插入或删除节点只需要修改指针,时间复杂度为O(1)。
缺点:
  • 访问效率低:链表不能通过索引直接访问元素,需要从头节点开始逐个遍历,时间复杂度为O(n)。
  • 额外的内存开销:每个节点除了存储数据外,还需要存储指针,增加了内存开销。
面试题:

题目:如何反转一个单向链表?

解答:可以使用迭代或递归的方法来反转链表。迭代方法的时间复杂度为O(n),空间复杂度为O(1)。

代码说明:

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public class ReverseLinkedList {
    // 反转链表方法
    public ListNode reverseList(ListNode head) {
        ListNode prev = null; // 前一个节点
        ListNode current = head; // 当前节点
        while (current != null) {
            ListNode next = current.next; // 保存当前节点的下一个节点
            current.next = prev; // 将当前节点的 next 指向前一个节点
            prev = current; // 更新前一个节点为当前节点
            current = next; // 移动到下一个节点
        }
        return prev; // 返回新的头节点
    }

    public static void main(String[] args) {
        // 创建一个链表 1 -> 2 -> 3 -> 4
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);

        ReverseLinkedList solution = new ReverseLinkedList();
        ListNode reversedHead = solution.reverseList(head); // 调用反转方法

        // 输出反转后的链表
        while (reversedHead != null) {
            System.out.print(reversedHead.val + " ");
            reversedHead = reversedHead.next;
        }
    }
}

题目:如何检测链表中是否存在环?

解答:可以使用快慢指针法。快指针每次移动两步,慢指针每次移动一步。如果链表中存在环,快指针最终会追上慢指针。

代码说明:

public class DetectCycle {
    // 检测链表中是否存在环
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false; // 链表为空或只有一个节点,不可能有环
        }
        ListNode slow = head; // 慢指针,每次移动一步
        ListNode fast = head.next; // 快指针,每次移动两步

        while (fast != null && fast.next != null) {
            if (slow == fast) {
                return true; // 快指针追上慢指针,说明有环
            }
            slow = slow.next; // 慢指针移动一步
            fast = fast.next.next; // 快指针移动两步
        }
        return false; // 快指针到达链表末尾,没有环
    }

    public static void main(String[] args) {
        // 创建一个链表 1 -> 2 -> 3 -> 4 -> 2 (形成环)
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = head.next; // 创建环

        DetectCycle solution = new DetectCycle();
        boolean hasCycle = solution.hasCycle(head); // 调用检测环的方法
        System.out.println("Has cycle: " + hasCycle); // 输出是否有环
    }
}

3. 栈(Stack)

描述:

栈是一种特殊的线性表,只允许在一端进行插入或删除操作。栈遵循“后进先出”(LIFO)原则。

优点:
  • 操作简单:栈的基本操作(入栈、出栈、读取栈顶元素)都非常简单,容易实现。
  • 支持回溯:栈可以用于支持函数调用栈、表达式求值等需要回溯的场景。
缺点:
  • 容量限制:如果使用固定大小的数组实现栈,可能会遇到栈溢出的问题。
  • 功能单一:栈只能进行栈顶操作,不适合需要随机访问或中间操作的场景。
面试题:

题目:如何使用两个栈实现一个队列?

解答:可以使用两个栈stack1stack2来实现队列。入队时将元素压入stack1,出队时将stack1中的元素依次弹出并压入stack2,然后从stack2中弹出元素。

代码说明:

import java.util.Stack;

class MyQueue {
    private Stack<Integer> stack1; // 用于入队操作
    private Stack<Integer> stack2; // 用于出队操作

    // 构造函数,初始化两个栈
    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    // 入队操作,将元素压入 stack1
    public void push(int x) {
        stack1.push(x);
    }

    // 出队操作,将 stack1 中的元素依次移到 stack2,然后从 stack2 弹出元素
    public int pop() {
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }

    // 获取队头元素,类似于 pop 操作,但不移除元素
    public int peek() {
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.peek();
    }

    // 判断队列是否为空
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }

    public static void main(String[] args) {
        MyQueue queue = new MyQueue();
        queue.push(1); // 入队
        queue.push(2);
        queue.push(3);

        System.out.println("Front element: " + queue.peek()); // 输出队头元素
        System.out.println("Dequeued element: " + queue.pop()); // 出队
        System.out.println("Is queue empty? " + queue.empty()); // 判断队列是否为空
    }
}

4. 队列(Queue)

描述:

队列是一种特殊的线性表,允许在一端进行插入操作,在另一端进行删除操作。队列遵循“先进先出”(FIFO)原则。

优点:
  • 公平性:队列保证了元素的处理顺序,适用于需要按顺序处理任务的场景。
  • 简单直观:队列的操作简单直观,容易理解和实现。
缺点:
  • 固定大小:如果使用固定大小的数组实现队列,可能会遇到队列满的问题。
  • 插入和删除操作受限:队列只能在两端进行操作,不支持中间操作。
面试题:

题目:如何使用两个队列实现一个栈?

解答:可以使用两个队列queue1queue2来实现栈。入栈时将元素加入queue1,出栈时将queue1中的元素依次移到queue2,直到只剩下一个元素,然后将该元素出队并返回。最后交换queue1queue2的角色。

代码说明:

import java.util.LinkedList;
import java.util.Queue;

class MyStack {
    private Queue<Integer> queue1; // 主队列,用于存储元素
    private Queue<Integer> queue2; // 辅助队列,用于临时存储元素

    // 构造函数,初始化两个队列
    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }

    // 入栈操作,将元素加入 queue1
    public void push(int x) {
        queue1.offer(x);
    }

    // 出栈操作,将 queue1 中的元素依次移到 queue2,直到只剩下一个元素,然后从 queue1 弹出该元素
    public int pop() {
        while (queue1.size() > 1) {
            queue2.offer(queue1.poll());
        }
        int top = queue1.poll();
        swapQueues();
        return top;
    }

    // 获取栈顶元素,类似于 pop 操作,但不移除元素
    public int top() {
        while (queue1.size() > 1) {
            queue2.offer(queue1.poll());
        }
        int top = queue1.poll();
        queue2.offer(top); // 将栈顶元素重新加入 queue2
        swapQueues();
        return top;
    }

    // 判断栈是否为空
    public boolean empty() {
        return queue1.isEmpty();
    }

    // 交换两个队列的角色
    private void swapQueues() {
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }

    public static void main(String[] args) {
        MyStack stack = new MyStack();
        stack.push(1); // 入栈
        stack.push(2);
        stack.push(3);

        System.out.println("Top element: " + stack.top()); // 输出栈顶元素
        System.out.println("Popped element: " + stack.pop()); // 出栈
        System.out.println("Is stack empty? " + stack.empty()); // 判断栈是否为空
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coder_小岁月

创作不易,谢谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值