Commonly used data structures and algorithms java version 01 (binary, sorting, and search)

Various algorithm competitions are about to start again. The main purpose of writing this blog is to review and consolidate the algorithms that have been learned, rather than learning new algorithms from scratch.
Therefore, for those who will not elaborate and explain the algorithm content too much, but focus on code display, reading requires a certain algorithm foundation.

two points

Binary search (binary search), also known as binary search, is a search algorithm that is applicable to:

  • There is an interval and a judgment condition , and they satisfy such a relationship: there is a cut-off point in this interval , the values ​​on the left side of the cut-off point do not meet the judgment condition, and the values ​​on the right side of the cut-off point all meet the judgment condition, dichotomous Algorithms are used to find this cut-off point .

Example 1
Find the first number greater than or equal to x from an ascending array arr with length n, and return its subscript, or -1 if it does not exist.

In this example, the interval is the ascending array arr, which is a discrete interval. The judgment condition is greater than or equal to x. The left side of the boundary point does not meet this condition, and the right side of the boundary point meets this condition. Then the code is as follows:

public class Main {
    
    
	static int binarySearch(int[] arr, int n, int x) {
    
    
		int left = 0, right = n - 1;
		while(left < right) {
    
    
			int middle = (left + right) / 2;
			if(arr[middle] >= x) {
    
     // 如果满足判定条件,那么答案一定在 <= middle那边
				right = middle;
			}
			else {
    
     // 如果不满足判定条件,那么答案一定在 > middle那边
				left = middle + 1;
			}
		}
		if(arr[left] >= x) {
    
    
			return left;
		}
		return -1;
	}
	public static void main(String[] args) {
    
    
		int[] arr = new int[] {
    
    1, 2, 3, 4, 5};
		System.out.println(binarySearch(arr, 5, 0));
		System.out.println(binarySearch(arr, 5, 3));
		System.out.println(binarySearch(arr, 5, 6));
	}
}

Example 2
Solve the equation x^5 + x^3 + 1 = n, the value of x is accurate to three decimal places, where -10^9 <= n <= 10^9

In this example, the interval is the value range of x, which is a continuous interval, which is not directly given and needs to be set by ourselves. For the setting of the interval, it can be larger than the actual value range, but not smaller than the actual value range, then we can directly set the interval to -10^9 to 10^9.
The judgment condition is not directly given, we need to set it ourselves, we set the judgment condition as x^5 + x^3 + 1 > n, so that the values ​​on the left side of the dividing point do not meet this condition, and the values ​​on the right side all meet this condition condition, the value of the cutoff point is the solution of x. code show as below:

public class Main {
    
    
	static double funcY(double x) {
    
    
		return Math.pow(x, 5) + Math.pow(x, 3) + 1;
	}
	static double binarySearch(double n) {
    
    
		double right = Math.pow(10, 9);
		double left = right * -1;
		while(right - left > Math.pow(10, -10)) {
    
    
			double middle = (left + right) / 2;
			if(funcY(middle) > n) {
    
    
				right = middle;
			}
			else {
    
    
				left = middle;
			}
		}
		
		return left;
	}
	public static void main(String[] args) {
    
    
		System.out.printf("%.3f\n", binarySearch(1));
		System.out.printf("%.3f\n", binarySearch(2));
		System.out.printf("%.3f\n", binarySearch(0));
		System.out.printf("%.3f\n", binarySearch(100000000));
	}
}

quick sort

In fact, it is not very useful. It is absolutely not used in competitions. It is occasionally used in interviews, and it is more difficult to write, especially the boundary value subscripts are easy to make mistakes. If it is not handled properly, infinite recursion often occurs. But after all, it is a very famous sorting method, so let's review it.
Quick sort is actually a recursive divide-and-conquer process, and the approximate steps are as follows:

  • Randomly select a value x, and exchange the positions of the elements in the array, so that the left half of the array is all smaller than x, the right half is all greater than x (this refers to ascending order, and vice versa for descending order), and the middle part is all equal to x. Note that the lengths of the left half and the right half do not have to be equal, and can even be zero.
  • Treat the left half as a sub-array, and perform the first step recursively.
  • Treat the right half as a sub-array, and perform the first step recursively.
  • Terminates the operation if the subarray length is zero.

The code is as follows:
(When exchanging the positions of array elements, there is a very strange method, that is, to use double pointers to directly exchange array elements, without additional array space, and only write a for loop to complete the exchange of arrays , the time spent is about half of the writing below. But I don’t think it’s necessary, the complexity of the constant level is generally negligible, and it’s better to use an easier-to-understand writing method.)

public class Main {
    
    
	static int[] lxArr = new int[100010]; // 存放小于x的数
	static int[] rxArr = new int[100010]; // 存放大于x的数
	static void quickSortAsc(int[] arr, int l, int r) {
    
    
		if(l >= r) {
    
    
			return;
		}
		int x = arr[l]; // x随便取一个值
		int lxNum = 0; // 小于x的数量
		int rxNum = 0; // 大于x的数量
		// 把原数组里的元素分成两部分,存放到其他两个数组里
		for(int i = l; i <= r; i++) {
    
    
			if(arr[i] < x) {
    
    
				lxArr[lxNum++] = arr[i];
			}
			else if(arr[i] > x) {
    
    
				rxArr[rxNum++] = arr[i];
			}
		}
		// 把其他两个数组里的元素,重新存放到原数组中
		for(int i = l; i <= r; i++) {
    
    
			if(i < l + lxNum) {
    
     // 把lxArr里的元素存入原数组
				arr[i] = lxArr[i - l];
			}
			else if(i <= r - rxNum) {
    
     //把x存入原数组
				arr[i] = x;
			}
			else {
    
    // 把rxArr里的元素存入原数组
				arr[i] = rxArr[rxNum + i - r - 1];
			}
		}
		// 有可能lxNum = 0,这样l就大于r了
		quickSortAsc(arr, l, l + lxNum - 1);
		quickSortAsc(arr, r - rxNum + 1, r);
	}
	public static void main(String[] args) {
    
    
		int[] a = {
    
    10, 4, 1, 9, 100};
		quickSortAsc(a, 0, 4);
		for (int i = 0; i < a.length; i++) {
    
    
			System.out.printf("%d ", a[i]);
		}
	}
}

heap sort

Compared with quick sort, heap sort is much easier to write.
But heap sorting involves a lot of knowledge. First of all, what is a heap? A heap is a complete binary tree . The binary tree and all its subtrees satisfy the condition that the value of the root node is less than or equal to the value of all nodes of the left and right subtrees . We call this kind of binary tree a small root heap. (Conversely, if the root node is greater than or equal to the subtree node, then it is a large root heap.)
How to insert an element into a heap? Taking the small root heap as an example, insert the element directly into the last layer of the binary tree. For example, for the binary tree in the figure below, we take the new element as the right child of node 3. In order to make the new tree still a heap, that is to say, still meet the conditions described above, we compare the value of the inserted element with the parent element, and if the parent element is larger, then exchange the values ​​of the two elements. The operation is then recursed upwards, and if the parent element is larger than the child element, then the values ​​of the two elements are swapped until the recursion reaches the vertices of the tree.
insert image description here
How to build a heap? First of all, a tree with only one element must be a heap, so if you want to build a heap based on n elements, I only need to use the first element as the initial heap, and then insert the subsequent elements into the heap in turn. .
How to remove elements from the heap vertex? Take out the value of the vertex element, assign the value of the last element of the binary tree to the vertex element, and then delete the last element. Next, compare the size of the vertex element and the left and right children. If the condition of the heap is not met, the vertex element is exchanged with the smaller child. This operation is performed recursively until the last layer of the binary tree.

Use the small root heap to sort the array in ascending order, the code is as follows:

import java.util.ArrayList;
// 堆的节点
class HeapNode{
    
    
	public int value;
	public HeapNode(int value) {
    
    
		this.value = value;
	}
}
// 小根堆
class SmallHeap{
    
    
	// 因为是完全二叉树,所以使用一维数组的形式存储二叉树,根据下标来确定节点之间的父子关系
	// 对于下标index,它的父亲是(index - 1) / 2,左儿子是index * 2 + 1,右儿子是index * 2 + 2
	public ArrayList<HeapNode> treeNodes = new ArrayList<HeapNode>();
	public void addNode(HeapNode node) {
    
    
		treeNodes.add(node);
		pushUp(treeNodes.size() - 1);
	}
	// 取出树根的元素
	public int takeOutRoot() {
    
    
		int rootValue = treeNodes.get(0).value;
		treeNodes.get(0).value = treeNodes.get(treeNodes.size() - 1).value;
		treeNodes.remove(treeNodes.size() - 1);
		pushDown(0);
		return rootValue;
	}
	public void swap(int index1, int index2) {
    
    
		int temp = treeNodes.get(index1).value;
		treeNodes.get(index1).value = treeNodes.get(index2).value;
		treeNodes.get(index2).value = temp;
	}
	// 把index下标的元素向下递归操作
	public void pushDown(int index) {
    
    
		if(index * 2 + 1 >= treeNodes.size()) {
    
    
			return ;
		}
		// 如果只有左孩子没有右孩子
		if(index * 2 + 1 == treeNodes.size() - 1) {
    
    
			if(treeNodes.get(index * 2 + 1).value < treeNodes.get(index).value) {
    
    
				swap(index, index * 2 + 1);
				pushDown(index * 2 + 1);
			}
			return;
		}
		// 符合条件,则与左孩子进行交换
		if(treeNodes.get(index * 2 + 1).value < treeNodes.get(index).value
				&& treeNodes.get(index * 2 + 1).value <= treeNodes.get(index * 2 + 2).value) {
    
    
			swap(index, index * 2 + 1);
			pushDown(index * 2 + 1);
			return;
		}
		// 符合条件,则与右孩子进行交换
		if(treeNodes.get(index * 2 + 2).value < treeNodes.get(index).value
				&& treeNodes.get(index * 2 + 2).value <= treeNodes.get(index * 2 + 1).value) {
    
    
			swap(index, index * 2 + 2);
			pushDown(index * 2 + 2);
			return;
		}
	}
	// 把index下标的元素向上递归操作
	public void pushUp(int index) {
    
    
		if(index <= 0) {
    
    
			return;
		}
		// 与父元素值进行交换
		if(treeNodes.get(index).value < treeNodes.get((index - 1) / 2).value) {
    
    
			swap(index, (index - 1) / 2);
			pushUp((index - 1) / 2);
		}
	}
}
public class Main {
    
    
	static SmallHeap smallHeap = new SmallHeap();
	public static void main(String[] args) {
    
    
		int n = 5;
		int[] arr = {
    
    9, 3, 2, 10 , 4};
		for (int i = 0; i < n; i++) {
    
    
			smallHeap.addNode(new HeapNode(arr[i]));
		}
		for (int i = 0; i < n; i++) {
    
    
			System.out.print(smallHeap.takeOutRoot() + " ");
		}
	}
}

And lookup

Union search is a tree-shaped data structure, which can judge and change the set and merge set of elements with a complexity close to O(1).
The core idea is that each set corresponds to a tree, and the root element of the tree represents the vertex element of the set.

  • When you want to merge collections, just add the root element of one tree to the root element of another tree
  • When you want to judge whether two elements are in the same set, you can judge whether the root element values ​​of the tree where the two elements are located are equal
  • When you want to change the set to which an element belongs, remove the element from the original tree and add it under another tree root element

It is not easy to give an example of searching and collecting, so I directly found a sample question that I have done before. The question is shown in the figure below, and the code is behind the question:

insert image description here

class Node{
    
    
	public Node fatherNode;
	public int value;
	public Node(int value) {
    
    
		this.value = value;
	}
	public void setFatherNode(Node fatherNode) {
    
    
		this.fatherNode = fatherNode;
	}
	public Node getRootNode() {
    
    
		// 如果当前节点没有父节点,那么该节点就是根节点
		if(this.fatherNode == null) {
    
    
			return this;
		}
		// 如果当前节点有父节点,那么递归调用父节点的函数,并且把父节点更新为根节点,以减少再次查询时的递归次数
		this.fatherNode = this.fatherNode.getRootNode();
		return this.fatherNode;
	}
}
public class Main {
    
    
	static Node[] nodes = new Node[100010];
	static int n, m;
	static Scanner sc = new Scanner(System.in);
	public static void main(String[] args) {
    
    
		n = sc.nextInt();
		m = sc.nextInt();
		for(int i = 1; i <= n; i++) {
    
    
			nodes[i] = new Node(i);
		}
		for(int i = 0; i < m; i++) {
    
    
			String type = sc.next();
			int valA = sc.nextInt();
			int valB = sc.nextInt();
			Node rootOfA = nodes[valA].getRootNode();
			Node rootOfB = nodes[valB].getRootNode();
			// 注意java判断字符串是否相等不能用 == ,而是要用equals()函数
			if(type.equals("M")) {
    
    
				if(rootOfA != rootOfB) {
    
    
					// 合并集合
					rootOfA.fatherNode = rootOfB;
				}
			}
			else if(type.equals("Q")) {
    
    
				// 判断是否位于同一个集合
				if(rootOfA != rootOfB) {
    
    
					System.out.println("No");
				}
				else {
    
    
					System.out.println("Yes");
				}
			}
		}
	}
}

Guess you like

Origin blog.csdn.net/m0_52744273/article/details/128918014