A data structure for finding dad: and check the collection of detailed explanations, attach the python template and sample questions (full version and compressed version are available for finding dad)

1. Concept

Union query set is a tree-type data structure used to deal with the merging and query problems of some disjoint sets (the so-called union and query). For example, we can use union search to determine how many trees are in a forest, whether a node belongs to a tree, and so on.

Main composition:
Union search set is mainly composed of an integer array pre[] and two functions find() and join( ).
The array pre[] records who the predecessor node of each point is (generally understood as who is the father of the point), the function find(x) is used to find which set the specified node x belongs to (it can be understood as finding the ancestor), the function join (x,y) is used to merge two nodes x and y (usually consider x as the father of y).

Function:
The main function of the union search set is to find the number of connected components (if all points in a graph are reachable (directly or indirectly connected), then the number of connected components of the graph is 1; if the graph has two subgraphs If all of them are reachable, the number of connected components in this graph is 2...).

Example:
For example, a continent has four sects : Heaven, Earth, Xuan, and Huang, and each sect has several disciples of different levels. The pre[] array records the name of each disciple’s boss, and the find() method is to find the disciple’s boss The boss of the boss... is the head of the sect, the join(A, B) method is to set the disciple A as the boss of disciple B.

Second, python implements the full version of the code

The data structure of the full version has initialization, find, merge and other methods, which are mainly to facilitate understanding. Generally, the full version is not used to solve the problem, because the amount of code redundancy increases the time and space complexity.

class UnionFind:
    def __init__(self):
        self.father = {
    
    }    ##记录每个节点的父节点
 
    def find(self, x):
        root = x
        while self.father[root] != root:    #寻找根节点
            root = self.father[root]
        while root != x:                    #路径压缩
            preFather = self.father[x]
            self.father[x] = root
            x = preFather
        return root
 
    def merge(self, x, y):              #合并节点
        root_x, root_y = self.find(x), self.find(y)
        if root_x != root_y:
            self.father[root_x] = root_y
 
    def is_connected(self, x, y):       #判断联通性
        return self.find(x) == self.find(y)
 
    def add(self, x):                   #增加节点
        if x not in self.father:
            self.father[x] = x

3. Collect and check the compressed version of python code (the most commonly used way of solving problems)

When solving problems, in order to ensure the simplicity of the code and reduce the time and space complexity, generally only one father array and one find() method are defined.

Note: The find method has the function of continuously updating the father , and is generally implemented in a recursive manner.

dus ={
    
    。。。。。。}  # 先定义爸爸数组的形式及初始元素值

# 定义查找祖值方法(含更新祖值作用)
def find(i):
    if dus[i] != i:
        dus[i] = find(dus[i])
    return dus[i]

Four, and collect typical examples

(1) Likou Question Bank: 947, the most removed peers or stones in the same row

Apply compressed version and check template

n stones are placed on some integer coordinate points in a two-dimensional plane. There can be at most one stone on each coordinate point.

If there are other stones in the same row or in the same row of a stone, then the stone can be removed.

Give you an array of stones of length n, where stones[i] = [xi, yi] represents the position of the i-th stone, and returns the maximum number of stones that can be removed.

Example 1:

Input: stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
Output: 5
Explanation: A removal The method of 5 stones is as follows:

  1. Remove stone [2,2] because it goes with [2,1].
  2. Remove stone [2,1] because it is in the same column as [0,1].
  3. Remove stone [1,2] because it goes with [1,0].
  4. Remove the stone [1,0] because it is in the same column as [0,0].
  5. Remove the stone [0,1] because it is the same as [0,0].

The stone [0,0] cannot be removed because it is not in line/column with another stone.

Example 2:

Input: stones = [[0,0],[0,2],[1,1],[2,0],[2,2]]
Output: 3
Explanation: A method to remove 3 stones is as follows Shown:

  1. Remove the stone [2,2] because it is the same as [2,0].
  2. Remove stone [2,0] because it is in the same column as [0,0].
  3. Remove the stone [0,2] because it is the same as [0,0].

The stones [0,0] and [1,1] cannot be removed because they are not in line/column with another stone.

Example 3:

Input: stones = [[0,0]]
Output: 0
Explanation: [0,0] is the only stone on the plane, so it cannot be removed.

prompt:

1 <= stones.length <= 1000
0 <= xi, yi <= 104
There will be no two stones placed on the same coordinate point

Problem-solving ideas:

After reading the topic, the first feeling is to find out how many sets of connected stones.
And each group of connected stones needs to keep at least one stone, so the answer is the number of stones-the number of groups of connected stones.

After reading the official problem solution, I understood a trick. Add 10000 (the maximum value of the coordinates) to the ordinates of all coordinates. We can regard the coordinates as edges.
For example, [[0,0],[1,1],[1,2]] => [[0,10000],[1,10001],[1,10002].
Here 0, 10000, 1, 10001, 10002 represent 5 nodes.
[0,10000] means that node 0 and node 10000 are connected by an edge.
[1,10001] indicates that node 1 and node 10001 have an edge connection.
[1,10002] means that node 1 and node 10002 have an edge connection.
This turns into a familiar graph and finds the problem, traverse all edges, you can find all connected groups.

The time complexity is O(nlogm), where n is the number of edges, and m is the number of nodes.
The space complexity is O(m), and the dictionary is used here for implementation and search. Of course, the list is also OK.

In addition, pay attention to call find on all nodes at the end to update the connected group to which they belong.

Problem solution code:

class Solution:
    def removeStones(self, stones: List[List[int]]) -> int:
    	# 以字典形式定义初始化的parent集
        dus ={
    
    s+i*10000:s+i*10000 for stone in stones for i, s in enumerate(stone)}
        print(dus)
		# 定义查找祖值方法(含更新祖值作用)
        def find(i):
            if dus[i] != i:
                dus[i] = find(dus[i])
            return dus[i]

		# 遍历全部石头,按顺序更新祖值
        for i, (s1, s2) in enumerate(stones):
            if s1 in dus and find(s1) != find(s2+10000):
                # union
                dus[find(s1)] = find(s2+10000)
                print(i, dus)
        # 防止按顺序更新的祖集中有重复未更新,再次更新
        for k in dus:
            print(k)
            find(k)
        # 最后用总数减去不重复的祖值数即可
        return len(stones) - len(set(dus.values()))

(2) Likou Question Bank: 1202, exchange elements in the string

Apply the full version and check the template

Give you a string s, and some "index pairs" array pairs in the string, where pairs[i] = [a, b] represent two indexes in the string (numbering starts from 0).

You can swap the characters at any pair of indexes in pairs as many times as you want.

Return the smallest string lexicographically that s can become after several exchanges.

Example 1:

Input: s = "dcab", pairs = [[0,3],[1,2]]
Output: "bacd"
Explanation:
exchange s[0] and s[3], s = "bcad"
exchange s[1 ] And s[2], s = "bacd"

Example 2:

Input: s = "dcab", pairs = [[0,3],[1,2],[0,2]]
Output: "abcd"
Explanation:
Exchange s[0] and s[3], s = " "bcad"
exchanges s[0] and s[2], s = "acbd"
exchanges s[1] and s[2], s = "abcd"

Example 3:

Input: s = "cba", pairs = [[0,1],[1,2]]
Output: "abc"
Explanation:
exchange s[0] and s[1], s = "bca"
exchange s[1 ] And s[2], s = "bac"
exchange s[0] and s[1], s = "abc"

prompt:

1 <= s.length <= 10^5
0 <= pairs.length <= 10^5
0 <= pairs[i][0], pairs[i][1] <s.length
s contains only lowercase English letter

Problem solution ideas:

The main thing is to investigate and check the idea of ​​the collection, and after the collection is used, find out the nodes contained in each collection separately, reorder them, and re-assign them.

Problem solution python code (detailed comments):

# 定义一个并查集类,包含初始化祖先、查找祖先、合并祖先三种方法
class DSU:
    def __init__(self, nodecount):
        self.parent=[-1]*nodecount#初始化,每个节点的祖先都是自己,记住-1,这里node_count为节点总数
    def findboss(self, node):# 首先,是找到自己所在集合的最上面那一层的祖先,若值不为-1,说明当前自己的祖先并不是最终祖先,循环进行再去找他的祖先,直到找到最终祖先
        temp=node
        while self.parent[node]!=-1:
            node=self.parent[node]
        if temp!=node:#路径压缩,使得所有节点的祖先都是最终的祖先
            self.parent[temp]=node
        return node 
    def mergeboss(self, node1, node2):#查询相互连通的两个人的祖先是不是同一个人
        node1boss=self.findboss(node1)
        node2boss=self.findboss(node2)
        if node1boss!=node2boss:#如果不是,那就合并两个集合,从两人中选举一个新祖先
            self.parent[node1boss]=node2boss

class Solution:
    def smallestStringWithSwaps(self, s: str, pairs: List[List[int]]) -> str:
        n=len(s)
        if n<2:
            return s
        dsu=DSU(n)#n个节点数,初始化并查集
        for node1,node2 in pairs:#先用并查集遍历一遍,使得每个节点都找到自己的祖先
            dsu.mergeboss(node1,node2)
        h={
    
    }
        for i in range(n):#再将所有公共祖先的子节点划分到一起,公共祖先自己也在该集合里
            if dsu.findboss(i) not in h:
                h[dsu.findboss(i)]=[i]
            else:
                h[dsu.findboss(i)].append(i)
        res=list(s)
        #print(dsu.parent)
        #print(h)
        for nodes in h.values():
            indices=sorted(nodes)#这里的每个节点都是相互连通的,即可以随意互相置换,直接按题意排序即可
            string=sorted(res[node] for node in nodes)#按最小字典序排列即从小到大
            # print(indices)
            # print(string)
            for index,letter in zip(indices,string):#按排好位置后,放回字母
                res[index]=letter
        return "".join(res)

Guess you like

Origin blog.csdn.net/weixin_44414948/article/details/114002727