Data Structure and Algorithm (Java Edition) Data Structure Knowledge Points

data structure:

  1. Array: A linear data structure that can store a set of elements of the same type. Arrays in Java are of fixed length, and elements in the array can be accessed using subscripts.

  2. Linked List: A linear data structure consisting of a series of nodes, each node containing a data element and a pointer to the next node. In Java, LinkedList can be used to implement a singly linked list, or a custom class can be used to implement a doubly linked list.

  3. Stack: A last-in-first-out (LIFO) data structure that can be implemented using an array or a linked list. A stack can be implemented in Java using the Stack class.

  4. Queue: A first-in-first-out (FIFO) data structure that can be implemented using an array or a linked list. In Java, you can use the Queue interface to implement a queue, or use LinkedList to implement a double-ended queue.

  5. Tree: A non-linear data structure consisting of nodes and connections between them. In Java, you can use the TreeNode class to implement binary trees, or use custom classes to implement other types of trees.

  6. Heap: A special tree data structure that satisfies the heap property (big root heap or small root heap). Java can use PriorityQueue to implement the heap.

  7. Graph: A nonlinear data structure consisting of a set of nodes and the connections between them. Graphs can be implemented in Java using custom classes.

Table of contents

array

linked list

the stack

queue

Tree

heap

picture


array

The following are common knowledge points of arrays:

  1. Define an array: To define an array, you need to specify the array type, array name, and array length. For example, to define an integer array with a length of 10, you can use the following code:

    int[] arr = new int[10];

  2. Initialize an array: You can initialize an array when defining it, or assign values ​​to array elements later in the program. For example, define an array of strings and initialize it as follows:

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

  3. Access to array elements: Array elements can be accessed through subscripts. Subscripts start at 0 and end at the length of the array minus 1. For example, to access the first element in the names array defined above:

    String name = names[0];

  4. Multidimensional arrays: In Java, multidimensional arrays can be defined, such as two-dimensional arrays, three-dimensional arrays, etc. Defining a two-dimensional array requires specifying the array type, array name, number of rows, and number of columns. For example, define a two-dimensional integer array with 3 rows and 4 columns:

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

  5. The length of the array: The length of the array can be obtained through the length property of the array. For example, to get the length of the names array defined above:

    int len = names.length;

  6. Array traversal: You can use for loop or foreach loop to traverse the elements in the array. For example, use a foreach loop to iterate over all elements in the names array defined above:

    for (String name : names) { System.out.println(name); }
  7. Sorting of arrays: Arrays can be sorted using the sort method of the Arrays class. For example, to sort an array of integers:

    int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3}; Arrays.sort(arr);
  8. Array copying: Arrays can be copied using the copyOf method of the Arrays class. For example, to copy an array of integers into a new array:

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

    linked list

A linked list is a basic data structure that can be used to implement queues, stacks, and other advanced data structures. In Java, linked lists are usually represented by Node objects. Each node contains a data element and a reference to the next node. The following are some knowledge points of the linked list in Data Structure and Algorithm (Java Edition):

1. The basic structure of the linked list: the linked list is composed of nodes, each node contains a data element and a reference to the next node. As follows:

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

Definition of linked list: A linked list is composed of nodes, each node contains a data element and a pointer to the next node.

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

2. Traversal of the linked list: The traversal of the linked list refers to visiting each node in the linked list in turn. Linked lists can be traversed using loops or recursion. As follows:

// 使用循环方式遍历链表
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);
    }
}

 Linked list traversal: Starting from the head node of the linked list, visit each node in turn until the tail node.

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. The insertion operation of the linked list: the insertion operation of the linked list refers to inserting a new node into the linked list. The insertion operation can be divided into head insertion method and tail insertion method. As follows:

// 头插法:将新节点插入到链表的头部
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. Delete operation of the linked list: The delete operation of the linked list refers to deleting a node in the linked list. Deletion operations usually require knowledge of the predecessor nodes of the node being deleted. As follows:

// 删除指定节点
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;

Delete the head node:

head = head.next;

 Delete the tail node:

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

 Delete the specified node:

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. The inversion operation of the linked list: the inversion operation of the linked list refers to reversing the nodes in the linked list. The reverse operation usually requires the use of three pointers: prev, current, and next. As follows:

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;
}

In this algorithm, we first define three pointers: prev, current and next. prev points to the previous node, current points to the current node, and next points to the next node of the current node. In the loop, we continuously point the next pointer of the current node to the prev node, then point the prev pointer to the current node, and point the current pointer to the next node, and continue to traverse the linked list until it reaches the end of the linked list. Finally, the node pointed to by prev is the reversed head node.

It should be noted that in the reversal operation, we need to save the next node of the current node in advance to prevent the inability to traverse to the next node of the linked list after the reversal operation.

6. Intermediate node of the linked list: To find the intermediate node of the linked list, you can use the method of fast and slow pointers.

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

7. Merge of linked lists: Merge two sorted linked lists into one sorted linked list.

Among them, ListNode is the node class of the linked list, val is the value of the node, and next is the pointer to the next node:

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;
    }
}

This method first judges whether l1 and l2 are null, and if one of them is null, directly returns another linked list. Next, define a head node as the head node of the merged linked list, compare the values ​​of l1 and l2, use the smaller value as the head node, and point its next to the recursively merged linked list. Finally, return to the head node.

The time complexity of the algorithm is O(n), where n is the total number of nodes in the two linked lists.

the stack

Stack (Stack) is a linear data structure with the characteristics of "first in, last out". A stack insertion operation is called a push, and a deletion operation is called a pop. There is also a very important operation on the stack, which is to view the top element of the stack but not go out of the stack, which is called "the top element of the stack" (peek).

The following are some common knowledge points of the stack:

  1. Implementation of the stack: The stack can be implemented by an array or a linked list.

  2. Stack operation: The main operations of the stack include stacking, popping, judging whether the stack is empty, viewing the top element of the stack, etc.

  3. Application of stacks: Common applications of stacks include expression evaluation, bracket matching, maze solving, and implementation of recursive functions.

The following are sample codes for some common stack operations:

1. Push operation

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

2. Pop operation

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

3. Determine whether the stack is empty

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

4. View the top element of the stack

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

queue

Queue (Queue) is a linear data structure with the characteristics of First In First Out (FIFO). Queues have two basic operations: enqueue and dequeue. The enqueue operation is to insert an element at the end of the queue, and the dequeue operation is to delete and return the first element of the queue.

The commonly used implementation classes of queues in Java are as follows:

1. LinkedList: LinkedList is a doubly linked list implementation in the Java standard library, and it can also be used as a queue. Since LinkedList implements the Deque interface, it can also be used as a double-ended queue.

The following is a sample code to implement a queue using 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 is an array-based double-ended queue that can be used as a queue and a stack.

The following is a sample code to implement a queue using 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 is a queue implementation based on priority heap (minimum heap), which can be sorted according to the priority of elements.

The following is a sample code for implementing a queue using 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());
    }
}

In addition to the above three implementation methods, Java also provides other queue implementation classes such as BlockingQueue and ConcurrentLinkedQueue, which can be selected according to needs.

Tree

All knowledge points of data structure and algorithm (Java version) tree:

A tree is a very common data structure. It is a collection of n nodes, each of which has zero or more child nodes. A node without a parent node is called a root node. Except for the root node, each child node has a parent node, and they are connected by edges. The tree structure has a good hierarchy and is usually used to store data with hierarchical relationships, such as file systems, HTML documents, etc.

The basic concept of a tree:

  • Node: The basic unit in a tree.
  • Root Node: The top node of the tree.
  • Parent node: The immediate superior node of a node.
  • Child node: The direct subordinate node of a node.
  • Leaf node: A node with no child nodes.
  • Sibling nodes: Nodes that have the same parent node.
  • Subtree: A collection of a node and all its children.
  • Depth: The length of the path from the root node to the current node.
  • Height: The length of the longest path from the current node to the leaf node.

Tree traversal:

Tree traversal refers to the process of visiting all nodes in the tree in a certain order. There are three common tree traversal methods:

  1. Preorder traversal: Visit each node in the order of "root node - left subtree - right subtree".
  2. In-order traversal: visit each node in the order of "left subtree-root node-right subtree".
  3. Post-order traversal: Visit each node in the order of "left subtree-right subtree-root node".

Tree traversal can be implemented in two ways: recursion and iteration.

Common operations on trees:

  1. Insert Node: Inserts a new node into the tree.
  2. Delete Node: Deletes a node from the tree.
  3. Find Node: Find a specified node in the tree.
  4. Get the height of the tree: Calculate the height of the tree.
  5. Get the number of nodes in the tree: Calculate the number of nodes in the tree.

Implementation of the tree:

Trees can be implemented using chained storage and array storage.

Linked storage refers to the use of nodes to represent each element in the tree, and each node contains three parts: value, left child node and right child node.

Array storage refers to using an array to represent each element in the tree, using an array subscript to represent the node number, and each node contains a value.

Common tree structures include binary tree, binary search tree, balanced tree, heap, etc.

The above are the relevant knowledge points of the tree in Data Structure and Algorithm (Java Edition), involving the basic concept, traversal, operation and implementation of the tree.

heap

A heap is a complete binary tree, and it has two forms: max-heap and min-heap. In a max heap, the value of a parent node is always greater than or equal to the value of its children. In min-heap, however, the value of a parent node is always less than or equal to the value of its child nodes.

A heap can be implemented in Java using the PriorityQueue class. PriorityQueue is a priority queue, which is implemented internally using a heap. The time complexity of its addition and deletion operations is O(logn).

Common heap operations include:

  1. Add elements: Add new elements to the end of the heap, then adjust the heap upwards so that it satisfies the properties of the heap.

  2. Delete elements: delete the top element of the heap, then move the last element of the heap to the top of the heap, and then adjust the heap downward to meet the properties of the heap.

  3. Get the top element of the heap: Return the top element of the heap, that is, the largest or smallest element in the heap.

The following is a sample code that implements a heap using 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
    }
}

The above code creates a min-heap and adds three elements. Then use the peek() method to get the top element of the heap, delete the top element of the heap and get the top element of the heap again. The final output is 1 and 2, which conforms to the nature of the heap.

picture

A graph is a data structure composed of nodes (or vertices) and edges that can be used to represent many real-world problems. A graph in Java can be represented using an adjacency matrix or an adjacency list.

The following are all the knowledge points of graphs in Java:

1. Adjacency matrix notation

Adjacency matrix notation uses a two-dimensional array to represent the relationship between each node in the graph. The rows and columns of the two-dimensional array correspond to nodes, and the value of each element indicates whether there is an edge between the corresponding nodes.

Sample code:

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. Adjacency list notation

The adjacency list notation uses an array to store each node's neighbors. Each element in the array corresponds to a node, and each element is a linked list, which stores the neighbor nodes of the node.

Sample code:

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. Depth-first traversal

Depth-first traversal starts from a node in the graph, follows a path to the leaf node, and then returns to the previous node to continue to visit the next child node. Depth-first traversal is implemented using recursion.

Sample code:

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. Breadth-first traversal

Breadth-first traversal (BFS) is an algorithm for traversing a graph, also known as "breadth-first search" or "horizontal-first search".

Breadth-first traversal starts from a node in the graph, first visits all nodes directly adjacent to this node, and then traverses layer by layer until all nodes directly or indirectly adjacent to this node are traversed. Breadth-first traversal usually needs to be implemented with the help of queues.

The following is a sample code to implement breadth-first traversal in 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);
    }
}

In the sample code above, we first define a Graph class for representing graphs. In this class, we represent graphs using adjacency lists. An adjacency list is a common data structure for representing graphs that stores the list of adjacent vertices for each vertex as a linked list.

In the Graph class, we use the addEdge method to add edges and the BFS method to implement breadth-first traversal. In the BFS method, we use a boolean type array visited to record whether each vertex has been visited, and use a LinkedList type queue queue to store the vertices to be visited. We first add the starting vertex to the queue, then traverse each vertex in the queue in turn, add the adjacent unvisited vertices to the queue, and mark them as visited. The traversal ends until the queue is empty.

Finally, in the test code, we create a graph with 6 vertices and call the BFS method to implement breadth-first traversal. Running the program, the output is:

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

more detail:

Here is an example of a breadth-first traversal starting at vertex 2:

Suppose we have the following graph:

     2 —— 0
    / \
   1 —— 3

Represent this graph using an adjacency list:

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);
    }
}

Now we start breadth-first traversal from vertex 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);
                }
            }
        }
    }
}

The output result is: 2 0 1 3

This is the result of the breadth-first traversal starting from vertex 2.

Guess you like

Origin blog.csdn.net/m0_62110645/article/details/129916908