数据结构与算法(Java版)数据结构知识点

数据结构:

  1. 数组:一种线性数据结构,可以存储一组相同类型的元素。Java中数组是固定长度的,可以使用下标访问数组中的元素。

  2. 链表:一种线性数据结构,由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。Java中可以使用LinkedList实现单向链表,或者使用自定义类实现双向链表。

  3. 栈:一种后进先出(LIFO)的数据结构,可以使用数组或链表实现。Java中可以使用Stack类实现栈。

  4. 队列:一种先进先出(FIFO)的数据结构,可以使用数组或链表实现。Java中可以使用Queue接口实现队列,或者使用LinkedList实现双端队列。

  5. 树:一种非线性数据结构,由一些节点和它们之间的连接组成。Java中可以使用TreeNode类实现二叉树,或者使用自定义类实现其他类型的树。

  6. 堆:一种特殊的树形数据结构,满足堆属性(大根堆或小根堆)。Java中可以使用PriorityQueue实现堆。

  7. 图:一种非线性数据结构,由一组节点和它们之间的连接组成。Java中可以使用自定义类实现图。

目录

数组

链表

队列


数组

下面是数组的常见知识点:

  1. 定义数组:定义一个数组需要指定数组类型、数组名称和数组长度。例如,定义一个整型数组,长度为10,可以使用以下代码:

    int[] arr = new int[10];

  2. 初始化数组:可以在定义数组时对其进行初始化,或者在之后的程序中给数组元素赋值。例如,定义一个字符串数组并初始化如下:

    String[] names = {"Alice", "Bob", "Charlie"};

  3. 数组元素的访问:可以通过下标访问数组元素。下标从0开始,到数组长度减1结束。例如,访问上面定义的names数组中的第一个元素:

    String name = names[0];

  4. 多维数组:在Java中,可以定义多维数组,例如二维数组、三维数组等。定义一个二维数组需要指定数组类型、数组名称、行数和列数。例如,定义一个3行4列的二维整型数组:

    int[][] arr = new int[3][4];

  5. 数组的长度:可以通过数组的length属性获取数组的长度。例如,获取上面定义的names数组的长度:

    int len = names.length;

  6. 数组的遍历:可以使用for循环或者foreach循环遍历数组中的元素。例如,使用foreach循环遍历上面定义的names数组中的所有元素:

    for (String name : names) { System.out.println(name); }
  7. 数组的排序:可以使用Arrays类的sort方法对数组进行排序。例如,对一个整型数组进行排序:

    int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3}; Arrays.sort(arr);
  8. 数组的复制:可以使用Arrays类的copyOf方法对数组进行复制。例如,将一个整型数组复制到一个新的数组中:

    int[] arr = {1, 2, 3, 4, 5}; int[] newArr = Arrays.copyOf(arr, arr.length);

    链表

链表是一种基本的数据结构,它可以用来实现队列、栈和其他高级数据结构。在Java中,链表通常使用节点(Node)对象来表示。每个节点包含一个数据元素和指向下一个节点的引用。下面是数据结构与算法(Java版)中链表的一些知识点:

1. 链表的基本结构:链表由一个个节点组成,每个节点包含一个数据元素和一个指向下一个节点的引用。如下所示:

class Node {
    int data;       // 数据元素
    Node next;      // 指向下一个节点的引用
}

链表的定义:链表由节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。

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

2. 链表的遍历:链表的遍历是指依次访问链表中的每个节点。可以使用循环或递归的方式来遍历链表。如下所示:

// 使用循环方式遍历链表
Node current = head;
while (current != null) {
    // 访问当前节点的数据元素
    System.out.print(current.data + " ");
    // 将当前节点移动到下一个节点
    current = current.next;
}

// 使用递归方式遍历链表
public void traverse(Node node) {
    if (node != null) {
        // 访问当前节点的数据元素
        System.out.print(node.data + " ");
        // 递归访问下一个节点
        traverse(node.next);
    }
}

 链表遍历:从链表的头节点开始,依次访问每个节点,直到尾节点为止。

ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
ListNode cur = head;
while (cur != null) {
    System.out.print(cur.val + " ");
    cur = cur.next;
}
// 输出:1 2 3

3. 链表的插入操作:链表的插入操作是指将一个新节点插入到链表中。插入操作可以分为头插法和尾插法。如下所示:

// 头插法:将新节点插入到链表的头部
Node newNode = new Node(5);
newNode.next = head;
head = newNode;

// 尾插法:将新节点插入到链表的尾部
Node newNode = new Node(5);
if (head == null) {
    head = newNode;
} else {
    Node current = head;
    while (current.next != null) {
        current = current.next;
    }
    current.next = newNode;
}

4. 链表的删除操作:链表的删除操作是指删除链表中的一个节点。删除操作通常需要知道要删除节点的前驱节点。如下所示:

// 删除指定节点
Node current = head;
Node prev = null;
while (current != null) {
    if (current.data == key) {
        if (prev == null) {
            // 要删除的节点是头节点
            head = current.next;
        } else {
            // 删除中间节点或尾节点
            prev.next = current.next;
        }
        return true;
    }
    prev = current;
    current = current.next;
}
return false;

删除头结点:

head = head.next;

 删除尾节点:

ListNode cur = head;
while (cur.next.next != null) {
    cur = cur.next;
}
cur.next = null;

 删除指定节点:

ListNode prev = null;
ListNode cur = head;
while (cur != null) {
    if (cur.val == target) {
        if (prev == null) {
            head = head.next;
        } else {
            prev.next = cur.next;
        }
        break;
    }
    prev = cur;
    cur = cur.next;
}

5. 链表的反转操作:链表的反转操作是指将链表中的节点反转。反转操作通常需要使用三个指针:prev、current和next。如下所示:

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode current = head;
    ListNode next = null;

    while (current != null) {
        // 保存当前节点的下一个节点
        next = current.next;
        // 反转当前节点的指针,指向前一个节点
        current.next = prev;
        // 将prev指针指向当前节点
        prev = current;
        // 将current指针指向next节点,继续遍历链表
        current = next;
    }

    // prev指向的节点即为反转后的头节点
    return prev;
}

在这个算法中,我们首先定义了三个指针:prev、current和next。prev指向前一个节点,current指向当前节点,next指向当前节点的下一个节点。在循环中,我们不断地将当前节点的next指针指向prev节点,然后将prev指针指向当前节点,将current指针指向next节点,继续遍历链表,直到遍历到链表的尾部。最终,prev指向的节点即为反转后的头节点。

需要注意的是,在反转操作中,我们需要提前保存当前节点的下一个节点,以防止反转操作之后无法遍历到链表的下一个节点。

6. 链表中间节点:查找链表的中间节点,可以使用快慢指针的方法。

ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
    fast = fast.next.next;
    slow = slow.next;
}
return slow;

7. 链表的合并:将两个有序链表合并为一个有序链表。

其中ListNode是链表的节点类,val是节点的值,next是指向下一个节点的指针:

public class ListNode {
    int val;
    ListNode next;
    ListNode(int val) {
        this.val = val;
    }
}

public class LinkedList {
    public ListNode merge(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        ListNode head = null;
        if (l1.val < l2.val) {
            head = l1;
            head.next = merge(l1.next, l2);
        } else {
            head = l2;
            head.next = merge(l1, l2.next);
        }
        return head;
    }
}

该方法首先判断l1和l2是否为null,如果其中一个为null,则直接返回另一个链表。接着,定义一个head节点作为合并后链表的头节点,并比较l1和l2的值,将较小的值作为head节点,并将其next指向递归合并后的链表。最后,返回head节点即可。

该算法的时间复杂度为O(n),其中n为两个链表的节点总数。

栈(Stack)是一种线性数据结构,具有“先进后出”的特点。栈的插入操作称为入栈(push),删除操作称为出栈(pop)。栈还有一个很重要的操作,即查看栈顶元素但不出栈,称为“栈顶元素”(peek)。

下面是一些栈的常用知识点:

  1. 栈的实现方式:栈可以通过数组或链表实现。

  2. 栈的操作:栈的主要操作包括入栈、出栈、判断栈是否为空、查看栈顶元素等。

  3. 栈的应用:栈的常用应用包括表达式求值、括号匹配、迷宫求解、递归函数的实现等。

下面是一些常见的栈操作的示例代码:

1. 入栈操作

public void push(int val) {
    stack.add(val);
}

2. 出栈操作

public int pop() {
    if (stack.isEmpty()) {
        throw new EmptyStackException();
    }
    return stack.remove(stack.size() - 1);
}

3.判断栈是否为空

public boolean isEmpty() {
    return stack.isEmpty();
}

4. 查看栈顶元素

public int peek() {
    if (stack.isEmpty()) {
        throw new EmptyStackException();
    }
    return stack.get(stack.size() - 1);
}

队列

队列(Queue)是一种线性数据结构,具有先进先出(First In First Out,FIFO)的特点。队列有两个基本操作:入队(enqueue)和出队(dequeue)。入队操作是将一个元素插入到队列的末尾,出队操作是将队列的第一个元素删除并返回。

Java中队列的常用实现类有以下几种:

1. LinkedList:LinkedList是Java标准库中的一个双向链表实现,也可以作为队列来使用。由于LinkedList实现了Deque接口,因此也可以当作双端队列来使用。

以下是使用LinkedList实现队列的示例代码:

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

public class MyQueue {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(1); // 入队操作
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue.peek()); // 获取队列头部元素
        System.out.println(queue.poll()); // 出队操作
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

2. ArrayDeque:ArrayDeque是一个基于数组实现的双端队列,可以当作队列和栈来使用。

以下是使用ArrayDeque实现队列的示例代码:

import java.util.ArrayDeque;
import java.util.Queue;

public class MyQueue {
    public static void main(String[] args) {
        Queue<Integer> queue = new ArrayDeque<>();
        queue.offer(1); // 入队操作
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue.peek()); // 获取队列头部元素
        System.out.println(queue.poll()); // 出队操作
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

3. PriorityQueue:PriorityQueue是一个基于优先级堆(最小堆)的队列实现,可以根据元素的优先级进行排序。

以下是使用PriorityQueue实现队列的示例代码:

import java.util.PriorityQueue;
import java.util.Queue;

public class MyQueue {
    public static void main(String[] args) {
        Queue<Integer> queue = new PriorityQueue<>();
        queue.offer(3); // 入队操作
        queue.offer(1);
        queue.offer(2);
        System.out.println(queue.peek()); // 获取队列头部元素
        System.out.println(queue.poll()); // 出队操作
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

除了以上三种实现方式,Java中还提供了BlockingQueue、ConcurrentLinkedQueue等其他队列实现类,具体可根据需要选择使用。

数据结构与算法(Java版)树所有知识点:

树是一种非常常见的数据结构,它是由n个节点组成的集合,其中每个节点都有零个或多个子节点,没有父节点的节点称为根节点,除了根节点之外,每个子节点都有一个父节点,它们之间通过边连接。树结构具有良好的层次性,通常用于存储有层次关系的数据,例如文件系统、HTML文档等。

树的基本概念:

  • 节点:树中的基本单元。
  • 根节点:树的顶端节点。
  • 父节点:一个节点的直接上级节点。
  • 子节点:一个节点的直接下级节点。
  • 叶节点:没有子节点的节点。
  • 兄弟节点:有相同父节点的节点。
  • 子树:一个节点及其所有子节点的集合。
  • 深度:从根节点到当前节点的路径长度。
  • 高度:从当前节点到叶节点的最长路径长度。

树的遍历:

树的遍历是指按照一定顺序访问树中所有节点的过程。常见的树的遍历方式有三种:

  1. 先序遍历:按照“根节点-左子树-右子树”的顺序访问每个节点。
  2. 中序遍历:按照“左子树-根节点-右子树”的顺序访问每个节点。
  3. 后序遍历:按照“左子树-右子树-根节点”的顺序访问每个节点。

树的遍历可以使用递归和迭代两种方式实现。

树的常见操作:

  1. 插入节点:向树中插入一个新节点。
  2. 删除节点:从树中删除一个节点。
  3. 查找节点:在树中查找一个指定的节点。
  4. 获取树的高度:计算树的高度。
  5. 获取树的节点数:计算树中的节点数量。

树的实现:

树可以使用链式存储和数组存储两种方式实现。

链式存储是指使用节点来表示树中的每个元素,每个节点包含三个部分:值、左子节点和右子节点。

数组存储是指使用数组来表示树中的每个元素,使用数组下标表示节点的编号,每个节点包含一个值。

常见的树结构有二叉树、二叉搜索树、平衡树、堆等。

以上是数据结构与算法(Java版)中树的相关知识点,涉及到树的基本概念、遍历、操作和实现等方面。

堆是一种完全二叉树,它有两种形式:最大堆和最小堆。在最大堆中,父节点的值总是大于或等于它的子节点的值。而在最小堆中,父节点的值总是小于或等于它的子节点的值。

Java中可以使用PriorityQueue类实现堆。PriorityQueue是一个优先级队列,它内部使用堆来实现。它的添加和删除操作的时间复杂度都是O(logn)。

常见的堆操作包括:

  1. 添加元素:将新元素添加到堆的最后,然后向上调整堆,使其满足堆的性质。

  2. 删除元素:将堆顶元素删除,然后将堆的最后一个元素移动到堆顶,再向下调整堆,使其满足堆的性质。

  3. 获取堆顶元素:返回堆顶元素,即堆中最大或最小的元素。

以下是一个使用PriorityQueue实现堆的示例代码:

import java.util.PriorityQueue;

public class HeapExample {
    public static void main(String[] args) {
        // 创建一个最小堆
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();

        // 添加元素
        minHeap.add(3);
        minHeap.add(1);
        minHeap.add(2);

        // 获取堆顶元素
        System.out.println(minHeap.peek()); // 输出1

        // 删除堆顶元素
        minHeap.poll();

        // 获取堆顶元素
        System.out.println(minHeap.peek()); // 输出2
    }
}

上面的代码创建了一个最小堆,并添加了三个元素。然后通过peek()方法获取堆顶元素,删除堆顶元素后再次获取堆顶元素。最终输出结果为1和2,符合堆的性质。

图是由节点(或顶点)和边组成的数据结构,可以用于表示许多现实世界的问题。Java中的图可以使用邻接矩阵或邻接表表示。

以下是Java中图的所有知识点:

1. 邻接矩阵表示法

邻接矩阵表示法使用二维数组表示图中的每个节点之间的关系。二维数组的行和列分别对应于节点,每个元素的值表示对应节点之间是否存在边。

示例代码:

public class Graph {
    private int[][] adjMatrix;
    private int vertexCount;

    public Graph(int vertexCount) {
        this.vertexCount = vertexCount;
        adjMatrix = new int[vertexCount][vertexCount];
    }

    public void addEdge(int i, int j, int weight) {
        adjMatrix[i][j] = weight;
        adjMatrix[j][i] = weight;
    }

    public void removeEdge(int i, int j) {
        adjMatrix[i][j] = 0;
        adjMatrix[j][i] = 0;
    }

    public int getEdge(int i, int j) {
        return adjMatrix[i][j];
    }

    public int getVertexCount() {
        return vertexCount;
    }
}

 2. 邻接表表示法

邻接表表示法使用一个数组来存储每个节点的邻居节点。数组中的每个元素对应于一个节点,每个元素都是一个链表,存储了该节点的邻居节点。

示例代码:

public class Graph {
    private LinkedList<Integer>[] adjList;
    private int vertexCount;

    public Graph(int vertexCount) {
        this.vertexCount = vertexCount;
        adjList = new LinkedList[vertexCount];
        for (int i = 0; i < vertexCount; i++) {
            adjList[i] = new LinkedList<>();
        }
    }

    public void addEdge(int i, int j) {
        adjList[i].add(j);
        adjList[j].add(i);
    }

    public void removeEdge(int i, int j) {
        adjList[i].remove(Integer.valueOf(j));
        adjList[j].remove(Integer.valueOf(i));
    }

    public boolean hasEdge(int i, int j) {
        return adjList[i].contains(j);
    }

    public LinkedList<Integer> getAdjacentVertices(int i) {
        return adjList[i];
    }

    public int getVertexCount() {
        return vertexCount;
    }
}

3. 深度优先遍历

深度优先遍历是从图中的一个节点开始,沿着一条路径一直到达叶子节点,然后返回到之前的节点,继续访问下一个子节点。深度优先遍历使用递归实现。

示例代码:

public void dfs(int start, boolean[] visited) {
    visited[start] = true;
    System.out.print(start + " ");
    for (int i : adjList[start]) {
        if (!visited[i]) {
            dfs(i, visited);
        }
    }
}

4. 广度优先遍历

广度优先遍历(BFS)是一种遍历图的算法,也称为“宽度优先搜索”或“横向优先搜索”,其基本思想是按照广度优先的顺序逐层遍历整个图,先访问与起始节点相邻的节点,然后依次访问与这些节点相邻的未被访问过的节点。

广度优先遍历是从图中的一个节点开始,先访问与该节点直接相邻的所有节点,然后逐层遍历下去,直到遍历完所有与该节点直接或间接相邻的节点。广度优先遍历通常需要借助队列来实现。

以下是使用Java实现广度优先遍历的示例代码:

import java.util.*;

public class Graph {
    private int V;              // 图的顶点数
    private LinkedList<Integer>[] adj;    // 邻接表表示图

    // 构造函数
    public Graph(int v) {
        V = v;
        adj = new LinkedList[v];
        for (int i = 0; i < v; i++) {
            adj[i] = new LinkedList<>();
        }
    }

    // 添加边
    public void addEdge(int v, int w) {
        adj[v].add(w);
    }

    // 广度优先遍历
    public void BFS(int s) {
        boolean[] visited = new boolean[V];
        LinkedList<Integer> queue = new LinkedList<>();
        visited[s] = true;
        queue.add(s);

        while (queue.size() != 0) {
            s = queue.poll();
            System.out.print(s + " ");

            Iterator<Integer> i = adj[s].listIterator();
            while (i.hasNext()) {
                int n = i.next();
                if (!visited[n]) {
                    visited[n] = true;
                    queue.add(n);
                }
            }
        }
    }

    // 测试代码
    public static void main(String[] args) {
        Graph g = new Graph(4);
        g.addEdge(0, 1);
        g.addEdge(0, 2);
        g.addEdge(1, 2);
        g.addEdge(2, 0);
        g.addEdge(2, 3);
        g.addEdge(3, 3);

        System.out.println("广度优先遍历 (从顶点 2 开始):");
        g.BFS(2);
    }
}

在上面的示例代码中,我们首先定义了一个Graph类,用于表示图。在该类中,我们使用邻接表来表示图。邻接表是一种表示图的常见数据结构,它将每个顶点的相邻顶点列表存储为一个链表。

在Graph类中,我们使用addEdge方法来添加边,使用BFS方法来实现广度优先遍历。在BFS方法中,我们使用一个boolean类型的数组visited来记录每个顶点是否已经被访问过,使用一个LinkedList类型的队列queue来存储待访问的顶点。我们首先将起始顶点加入队列中,然后依次遍历队列中的每个顶点,将与其相邻的未访问过的顶点加入队列中,并将其标记为已访问。直到队列为空为止,遍历结束。

最后,在测试代码中,我们创建了一个包含6个顶点的图,并调用BFS方法来实现广度优先遍历。运行该程序,输出结果为:

广度优先遍历 (从顶点 2 开始):
2 0 3 1

更具体:

下面是一个从顶点2开始的广度优先遍历的示例:

假设我们有以下图:

     2 —— 0
    / \
   1 —— 3

使用邻接表来表示这个图:

import java.util.ArrayList;

public class Graph {
    private int V; // 顶点数
    private ArrayList<ArrayList<Integer>> adj; // 邻接表

    public Graph(int v) {
        V = v;
        adj = new ArrayList<>();
        for (int i = 0; i < v; i++) {
            adj.add(new ArrayList<Integer>());
        }
    }

    public void addEdge(int v, int w) {
        adj.get(v).add(w);
        adj.get(w).add(v);
    }

    public ArrayList<Integer> getAdj(int v) {
        return adj.get(v);
    }
}

现在我们从顶点2开始进行广度优先遍历:

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

public class BFS {
    public static void main(String[] args) {
        Graph g = new Graph(4);
        g.addEdge(2, 0);
        g.addEdge(2, 1);
        g.addEdge(2, 3);

        bfs(g, 2);
    }

    public static void bfs(Graph g, int s) {
        boolean[] visited = new boolean[g.V];
        Queue<Integer> queue = new LinkedList<>();

        visited[s] = true;
        queue.add(s);

        while (!queue.isEmpty()) {
            int v = queue.poll();
            System.out.print(v + " ");

            for (int w : g.getAdj(v)) {
                if (!visited[w]) {
                    visited[w] = true;
                    queue.add(w);
                }
            }
        }
    }
}

输出结果为:2 0 1 3

这就是从顶点2开始的广度优先遍历的结果。

猜你喜欢

转载自blog.csdn.net/m0_62110645/article/details/129916908