【问题标题】:Compute the minimal number of swaps to order a sequence计算对序列进行排序的最小交换次数
【发布时间】:2013-02-15 15:14:10
【问题描述】:

我正在对没有相同数字的整数序列(不失一般性,假设该序列是1,2,...,n 的排列)进行排序,使其自然递增顺序(即1,2,...,n)。我正在考虑以最少的交换次数直接交换元素(无论元素的位置如何;换句话说,交换对任何两个元素都有效)(以下可能是一个可行的解决方案):

交换两个元素,其中一个或两个元素应交换到正确的位置。直到每个元素都放在正确的位置。

但我不知道如何在数学上证明上述解决方案是否最优。有人可以帮忙吗?

【问题讨论】:

标签: algorithm sorting sequence graph-theory


【解决方案1】:

我能够用 证明这一点。可能想在 :) 中添加该标签

n 顶点创建一个图。如果位置i 中的元素应该以正确的顺序位于位置j,则创建从节点n_in_j 的边。您现在将拥有一个由几个非相交循环组成的图形。我认为正确排序图表所需的最小交换次数是

M = sum (c in cycles) size(c) - 1

花点时间说服自己……如果两个项目在一个循环中,一次交换就可以解决它们。如果三个项目在一个循环中,您可以交换一对以将一个放在正确的位置,然后剩下两个循环,等等。如果n 项目在一个循环中,您需要n-1 交换。 (即使您不与直接邻居交换,这始终是正确的。)

鉴于此,您现在或许可以了解为什么您的算法是最优的。如果您进行交换并且至少有一个项目位于正确的位置,那么它将始终将 M 的值减少 1。对于长度为 n 的任何循环,请考虑将元素交换到正确的位置,由它的邻居。您现在有一个正确排序的元素,以及一个长度为 n-1 的循环。

由于M 是交换的最小数量,并且您的算法总是将M 每次交换减少1,因此它必须是最优的。

【讨论】:

  • 这个时间复杂度是多少?
  • 时间复杂度:O(n*logn) 空间复杂度:O(n) @puneet
  • 但那是如何证明最小性的呢? “我认为交换的最小数量......”,“花点时间说服自己......”对不起,“争论”和“说服自己”是不够的。您必须实际证明上述M 是最小的。
  • @AnT,我同意。具体来说,我可以设想一种涉及交换的算法,其中两个项目都没有结束它的预期位置,但实现了相同数量的移动。具体来说,可以进行交换以将任何循环减少到两个循环的数量(如果n 是奇数,则可能以单个循环结束),然后将所有两个循环交换到正确的位置。这也涉及n-1 移动。虽然这并不比提供的算法快,但至少表明提供的算法的最优性远非显而易见。
  • 为什么复杂度会是 n*log(n) ?任何人都可以在这里抛出一些直观的光吗?
【解决方案2】:

供您参考,这是我编写的一种算法,用于生成对数组进行排序所需的最小交换次数。它找到@Andrew Mao 描述的循环。

/**
 * Finds the minimum number of swaps to sort given array in increasing order.
 * @param ar array of <strong>non-negative distinct</strong> integers. 
 *           input array will be overwritten during the call!
 * @return min no of swaps
 */
public int findMinSwapsToSort(int[] ar) {
    int n = ar.length;
    Map<Integer, Integer> m = new HashMap<>();
    for (int i = 0; i < n; i++) {
        m.put(ar[i], i);
    }
    Arrays.sort(ar);
    for (int i = 0; i < n; i++) {
        ar[i] = m.get(ar[i]);
    }
    m = null;
    int swaps = 0;
    for (int i = 0; i < n; i++) {
        int val = ar[i];
        if (val < 0) continue;
        while (val != i) {
            int new_val = ar[val];
            ar[val] = -1;
            val = new_val;
            swaps++;
        }
        ar[i] = -1;
    }
    return swaps;
}

【讨论】:

  • 你能解释一下最后一个while循环发生了什么
  • 谁能帮助理解代码?我似乎无法理解正在发生的事情背后的逻辑
  • @GURMEETSINGH 你找到算法了吗?
  • @Spindoctor 是的,我想通了
  • @Spindoctor 在第一个 for 循环中将实际值作为键,将原始数组中的位置作为值。然后使用 Collections.sort() 对数组进行排序。在第二个 for 循环中,我们在排序之前获取数组的索引。在最后一个 for 循环中,我们将循环元素设为 -1
【解决方案3】:

@bekce 做得很好的解决方案。如果使用C#,设置修改数组ar的初始代码可以简洁地表示为:

var origIndexes = Enumerable.Range(0, n).ToArray();
Array.Sort(ar, origIndexes);

然后在其余代码中使用origIndexes 而不是ar

【讨论】:

    【解决方案4】:

    这是 C++ 中的示例代码,它找到对(1,2,3,4,5,.......n-2,n-1,n) 序列的排列进行排序的最小交换次数

    #include<bits/stdc++.h>
    using namespace std;
    
    
    int main()
    {
        int n,i,j,k,num = 0;
        cin >> n;
        int arr[n+1];
        for(i = 1;i <= n;++i)cin >> arr[i];
        for(i = 1;i <= n;++i)
        {
            if(i != arr[i])// condition to check if an element is in a cycle r nt
            {
                j = arr[i];
                arr[i] = 0;
                while(j != 0)// Here i am traversing a cycle as mentioned in 
                {             // first answer
                    k = arr[j];
                    arr[j] = j;
                    j = k;
                    num++;// reducing cycle by one node each time
                }
                num--;
            }
        }
        for(i = 1;i <= n;++i)cout << arr[i] << " ";cout << endl;
        cout << num << endl;
        return 0;
    }
    

    【讨论】:

      【解决方案5】:

      Swift 4 版本:

      func minimumSwaps(arr: [Int]) -> Int {
      
            struct Pair {
               let index: Int
               let value: Int
            }
      
            var positions = arr.enumerated().map { Pair(index: $0, value: $1) }
            positions.sort { $0.value < $1.value }
            var indexes = positions.map { $0.index }
      
            var swaps = 0
            for i in 0 ..< indexes.count {
               var val = indexes[i]
               if val < 0 {
                  continue // Already visited.
               }
               while val != i {
                  let new_val = indexes[val]
                  indexes[val] = -1
                  val = new_val
                  swaps += 1
               }
               indexes[i] = -1
            }
            return swaps
      }
      

      【讨论】:

        【解决方案6】:

        // 假设我们只处理从零开始的序列

        function minimumSwaps(arr) {
            var len = arr.length
            var visitedarr = []
            var i, start, j, swap = 0
            for (i = 0; i < len; i++) {
                if (!visitedarr[i]) {
                    start = j = i
                    var cycleNode = 1
                    while (arr[j] != start) {
                        j = arr[j]
                        visitedarr[j] = true
                        cycleNode++
                    }
                    swap += cycleNode - 1
                }
            }
            return swap
        }
        

        【讨论】:

          【解决方案7】:

          所有的周期盘点都很难记住。有一种方法更容易记忆。

          首先,让我们手动浏览一个示例案例。

          • 序列:[7, 1, 3, 2, 4, 5, 6]
          • 枚举它:[(0, 7), (1, 1), (2, 3), (3, 2), (4, 4), (5, 5), (6, 6 )]
          • 按值对枚举进行排序:[(1, 1), (3, 2), (2, 3), (4, 4), (5, 5), (6, 6), ( 0, 7)]
          • 从头开始。当索引与枚举索引不同时,继续交换索引和枚举索引定义的元素。请记住:swap(0,2);swap(0,3)swap(2,3);swap(0,2) 相同
            • swap(0, 1) => [(3, 2), (1, 1), (2, 3), (4, 4), (5, 5), (6, 6), (0, 7)]
            • swap(0, 3) => [(4, 4), (1, 1), (2, 3), (3, 2), (5, 5), (6, 6), (0, 7)]
            • swap(0, 4) => [(5, 5), (1, 1), (2, 3), (3, 2), (4, 4), (6, 6), (0, 7)]
            • swap(0, 5) => [(6, 6), (1, 1), (2, 3), (3, 2), (4, 4), (5, 5 ), (0, 7)]
            • swap(0, 6) => [(0, 7), (1, 1), (2, 3), (3, 2), (4, 4), (5, 5), (6, 6)]

          即从语义上讲,您对元素进行排序,然后通过交换最左边的不合适的项目来弄清楚如何将它们置于初始状态。

          Python 算法就这么简单:

          def swap(arr, i, j):
              arr[i], arr[j] = arr[j], arr[i]
          
          
          def minimum_swaps(arr):
              annotated = [*enumerate(arr)]
              annotated.sort(key = lambda it: it[1])
          
              count = 0
          
              i = 0
              while i < len(arr):
                  if annotated[i][0] == i:
                      i += 1
                      continue
                  swap(annotated, i, annotated[i][0])
                  count += 1
          
              return count
          

          因此,您无需记住访问过的节点或计算一些循环长度。

          【讨论】:

          • 这似乎没有返回具有重复值的数组的最小数量:[8, 8, 7, 9, 9, 9, 8, 9, 7] => 6, 应该是 4
          • 已检查。前段时间写的。是的。不适用于重复项。但。我的解决方案完全符合问题规范:“我正在对没有相同数字的整数序列进行排序”。它并非永远适用于具有重复项的列表。因此将驳回您的评论@RyanWood
          • 只是补充@Archibald的解释:这种方法有效,因为从枚举+有序数组到原始数组的排序与相反的交换次数相同。我发现额外的排序有点不必要。实际上,您可以通过将 while 循环更改为类似这样(在 JS 中)来获得相同的结果:``` while (i
          【解决方案8】:

          Java(和测试)中具有原始类型的整数的实现。

          import java.util.Arrays;
          
          public class MinSwaps {
            public static int computate(int[] unordered) {
              int size = unordered.length;
              int[] ordered = order(unordered);
              int[] realPositions = realPositions(ordered, unordered);
              boolean[] touchs = new boolean[size];
              Arrays.fill(touchs, false);
              int i;
              int landing;
              int swaps = 0;
          
              for(i = 0; i < size; i++) {
                if(!touchs[i]) {
                  landing = realPositions[i];
          
                  while(!touchs[landing]) {
                    touchs[landing] = true;
                    landing = realPositions[landing];
          
                    if(!touchs[landing]) { swaps++; }
                  }
                }
              }
          
              return swaps;
            }
          
            private static int[] realPositions(int[] ordered, int[] unordered) {
              int i;
              int[] positions = new int[unordered.length];
          
              for(i = 0; i < unordered.length; i++) {
                positions[i] = position(ordered, unordered[i]);
              }
          
              return positions;
            }
          
            private static int position(int[] ordered, int value) {
              int i;
          
              for(i = 0; i < ordered.length; i++) {
                if(ordered[i] == value) {
                  return i;
                }
              }
          
              return -1;
            }
          
            private static int[] order(int[] unordered) {
              int[] ordered = unordered.clone();
              Arrays.sort(ordered);
          
              return ordered;
            }
          }
          

          测试

          import org.junit.Test;
          
          import static org.junit.Assert.assertEquals;
          
          public class MinimumSwapsSpec {
            @Test
            public void example() {
              // setup
              int[] unordered = new int[] { 40, 23, 1, 7, 52, 31 };
          
              // run
              int minSwaps = MinSwaps.computate(unordered);
          
              // verify
              assertEquals(5, minSwaps);
            }
          
            @Test
            public void example2() {
              // setup
              int[] unordered = new int[] { 4, 3, 2, 1 };
          
              // run
              int minSwaps = MinSwaps.computate(unordered);
          
              // verify
              assertEquals(2, minSwaps);
            }
          
            @Test
            public void example3() {
              // setup
              int[] unordered = new int[] {1, 5, 4, 3, 2};
          
              // run
              int minSwaps = MinSwaps.computate(unordered);
          
              // verify
              assertEquals(2, minSwaps);
            }
          }
          

          【讨论】:

            【解决方案9】:

            斯威夫特 4.2:

            func minimumSwaps(arr: [Int]) -> Int {
                let sortedValueIdx = arr.sorted().enumerated()
                    .reduce(into: [Int: Int](), { $0[$1.element] = $1.offset })
            
                var checked = Array(repeating: false, count: arr.count)
                var swaps = 0
            
                for idx in 0 ..< arr.count {
                    if checked[idx] { continue }
            
                    var edges = 1
                    var cursorIdx = idx
                    while true {
                        let cursorEl = arr[cursorIdx]
                        let targetIdx = sortedValueIdx[cursorEl]!
                        if targetIdx == idx {
                            break
                        } else {
                            cursorIdx = targetIdx
                            edges += 1
                        }
                        checked[targetIdx] = true
                    }
                    swaps += edges - 1
                }
            
                return swaps
            }
            

            【讨论】:

              【解决方案10】:

              我们不需要交换实际的元素,只需找出有多少元素不在正确的索引中(循环)。 最小掉期为 Cycle - 1; 这是代码...

              static int minimumSwaps(int[] arr) {
                      int swap=0;
                      boolean visited[]=new boolean[arr.length];
              
                      for(int i=0;i<arr.length;i++){
                          int j=i,cycle=0;
              
                          while(!visited[j]){
                              visited[j]=true;
                              j=arr[j]-1;
                              cycle++;
                          }
              
                          if(cycle!=0)
                              swap+=cycle-1;
                      }
                      return swap;
              
              
                  }
              

              【讨论】:

              • 我无法说明 while 循环如何工作以查找循环数。具体来说,while 循环中的第二条语句。 j=arr[j]-1; 为什么 j 的值是通过减去 1 得出的,而我们在开始时将它设置为 i。
              • 最优化的解决方案,其他的不需要交换元素,因为要求只是找到最小的交换次数
              • 我在想j=arr[j]-1;@AshishSantikari 的原因可以通过使用已经排序的数组运行代码来查看。在这种情况下,填写visited 数组,按顺序填写,0 是第一个索引,因此是-1。在这种情况下,while 循环每次在 1 个循环后终止。如果出现乱序,则数组将是临时稀疏的,循环计数以正确顺序“看到”它需要多长时间,如果您为基于 0 的索引减去 1,则这等于交换次数。很酷。
              【解决方案11】:

              Python 代码

              A = [4,3,2,1]
              count = 0
              for i in range (len(A)):
                  min_idx = i
                  for j in range (i+1,len(A)):
                      if A[min_idx] > A[j]:
                          min_idx = j
                  if min_idx > i:
                      A[i],A[min_idx] = A[min_idx],A[i]
                      count = count + 1
              print "Swap required : %d" %count
              

              【讨论】:

                【解决方案12】:

                在 Javascript 中

                如果数组的计数从1开始

                function minimumSwaps(arr) {
                   var len = arr.length
                    var visitedarr = []
                    var i, start, j, swap = 0
                    for (i = 0; i < len; i++) {
                        if (!visitedarr[i]) {
                            start = j = i
                            var cycleNode = 1
                            while (arr[j] != start + 1) {
                                j = arr[j] - 1
                                visitedarr[j] = true
                                cycleNode++
                            }
                            swap += cycleNode - 1
                        }
                    }
                    return swap
                }
                

                else 以 0 开头的输入

                function minimumSwaps(arr) {
                    var len = arr.length
                    var visitedarr = []
                    var i, start, j, swap = 0
                    for (i = 0; i < len; i++) {
                        if (!visitedarr[i]) {
                            start = j = i
                            var cycleNode = 1
                            while (arr[j] != start) {
                                j = arr[j]
                                visitedarr[j] = true
                                cycleNode++
                            }
                            swap += cycleNode - 1
                        }
                    }
                    return swap
                }
                

                只是为当前的 HackerEarth 输入扩展 Darshan Puttaswamy 代码

                【讨论】:

                  【解决方案13】:

                  @Archibald,我喜欢你的解决方案,这是我最初的假设,即对数组进行排序是最简单的解决方案,但我认为不需要像我所说的那样进行反向遍历它,即枚举然后排序数组,然后计算枚举的交换。

                  我发现从数组中的每个元素中减去 1,然后计算对该列表进行排序所需的交换数会更简单

                  这是我的调整/解决方案:

                  def swap(arr, i, j):
                      tmp = arr[i]
                      arr[i] = arr[j]
                      arr[j] = tmp
                  
                  def minimum_swaps(arr):
                  
                      a = [x - 1 for x in arr]
                  
                      swaps = 0
                      i = 0
                      while i < len(a):
                          if a[i] == i:
                              i += 1
                              continue
                          swap(a, i, a[i])
                          swaps += 1
                  
                      return swaps
                  

                  至于证明最优性,我认为@arax 有一个很好的观点。

                  【讨论】:

                    【解决方案14】:

                    这是@Archibald 已经解释过的Java 解决方案。

                        static int minimumSwaps(int[] arr){
                            int swaps = 0;
                            int[] arrCopy = arr.clone();
                            HashMap<Integer, Integer> originalPositionMap
                                    = new HashMap<>();
                            for(int i = 0 ; i < arr.length ; i++){
                                originalPositionMap.put(arr[i], i);
                            }
                    
                            Arrays.sort(arr);
                            for(int i = 0 ; i < arr.length ; i++){
                                while(arr[i] != arrCopy[i]){
                                    //swap
                                    int temp = arr[i];
                                    arr[i] = arr[originalPositionMap.get(temp)];
                                    arr[originalPositionMap.get(temp)] = temp;
                                    swaps += 1;
                                }
                            }
                            return swaps;
                        }
                    

                    【讨论】:

                      【解决方案15】:
                      def swap_sort(arr)
                        changes = 0
                        loop do
                          # Find a number that is out-of-place
                          _, i = arr.each_with_index.find { |val, index| val != (index + 1) }
                          if i != nil
                            # If such a number is found, then `j` is the position that the out-of-place number points to.
                            j = arr[i] - 1
                      
                            # Swap the out-of-place number with number from position `j`.
                            arr[i], arr[j] = arr[j], arr[i]
                      
                            # Increase swap counter.
                            changes += 1
                          else
                            # If there are no out-of-place number, it means the array is sorted, and we're done.
                            return changes
                          end
                        end
                      end                                                                                                
                      

                      【讨论】:

                        【解决方案16】:

                        我真的很喜欢@Ieuan Uys 在 Python 中的解决方案。

                        我对他的解决方案的改进;

                        • 当循环少了一次迭代以提高速度时; while i &lt; len(a) - 1
                        • Swap 函数被解封装成一个单一的函数。
                        • 添加了大量代码 cmets 以提高可读性。

                        我在 python 中的代码。

                        def minimumSwaps(arr):
                            #make array values starting from zero to match index values. 
                            a = [x - 1 for x in arr] 
                        
                            #initialize number of swaps and iterator.
                            swaps = 0
                            i = 0
                        
                            while i < len(a)-1:
                                if a[i] == i:
                                    i += 1
                                    continue
                        
                                #swap.
                                tmp = a[i] #create temp variable assign it to a[i]
                                a[i] = a[tmp] #assign value of a[i] with a[tmp]
                                a[tmp] = tmp #assign value of a[tmp] with tmp (or initial a[i])
                        
                                #calculate number of swaps.
                                swaps += 1
                        
                            return swaps
                        

                        详细解释代码对大小为 n 的数组的作用;

                        我们逐一检查数组中除最后一个(n-1 次迭代)之外的每个值。如果该值与数组索引不匹配,那么我们将该值发送到其索引值等于其值的位置。例如,如果 a[0] = 3。那么这个值应该与 a[3] 交换。 a[0] 和 a[3] 交换。值 3 将位于它应该在的 a[3] 处。一个值被发送到它的位置。我们还有 n-2 次迭代。我对现在的 a[0] 不感兴趣。如果在该位置不为 0,则稍后将其交换为另一个值。因为另一个值也存在于错误的地方,所以后面的while循环会识别出来。

                        真实例子

                        a[4, 2, 1, 0, 3]
                        #iteration 0, check a[0]. 4 should be located at a[4] where the value is 3. Swap them. 
                        a[3, 2, 1, 0, 4] #we sent 4 to the right location now.  
                        #iteration 1, check a[1]. 2 should be located at a[2] where the value is 1. Swap them. 
                        a[3, 1, 2, 0, 4] #we sent 2 to the right location now.  
                        #iteration 2, check a[2]. 2 is already located at a[2]. Don't do anything, continue. 
                        a[3, 1, 2, 0, 4] 
                        #iteration 3, check a[3]. 0 should be located at a[0] where the value is 3. Swap them. 
                        a[0, 1, 2, 3, 4] #we sent 0 to the right location now.  
                        # There is no need to check final value of array. Since all swaps are done. 
                        

                        【讨论】:

                          【解决方案17】:

                          使用 Javascript 的解决方案。

                          首先,我使用当前索引设置所有需要排序的元素,然后遍历地图以仅对需要交换的元素进行排序。

                          function minimumSwaps(arr) {
                            const mapUnorderedPositions = new Map()
                            for (let i = 0; i < arr.length; i++) {
                              if (arr[i] !== i+1) {
                                mapUnorderedPositions.set(arr[i], i)
                              }
                            }
                          
                            let minSwaps = 0
                            while (mapUnorderedPositions.size > 1) {
                              const currentElement = mapUnorderedPositions.entries().next().value
                              const x = currentElement[0]
                              const y = currentElement[1]
                          
                              // Skip element in map if its already ordered
                              if (x-1 !== y) {
                                // Update unordered position index of swapped element
                                mapUnorderedPositions.set(arr[x-1], y)
                          
                                // swap in array
                                arr[y] = arr[x-1]
                                arr[x-1] = x
                          
                                // Increment swaps
                                minSwaps++
                              }
                          
                              mapUnorderedPositions.delete(x)
                            }
                          
                          
                            return minSwaps
                          }
                          

                          如果你有一个像 7 2 4 3 5 6 1 这样的输入,调试会这样:

                          Map { 7 => 0, 4 => 2, 3 => 3, 1 => 6 }
                          currentElement [ 7, 0 ]
                          swapping 1 with 7
                          [ 1, 2, 4, 3, 5, 6, 7 ]
                          
                          currentElement [ 4, 2 ]
                          swapping 3 with 4
                          [ 1, 2, 3, 4, 5, 6, 7 ]
                          
                          currentElement [ 3, 2 ]
                          skipped
                          
                          minSwaps = 2
                          

                          【讨论】:

                            【解决方案18】:

                            Apple Swift 5.2.4 版

                            func minimumSwaps(arr: [Int]) -> Int {
                                var swapCount = 0
                                var arrayPositionValue = [(Int, Int)]()
                                var visitedDictionary = [Int: Bool]()
                                
                                for (index, number) in arr.enumerated() {
                                    arrayPositionValue.append((index, number))
                                    visitedDictionary[index] = false
                                }
                                
                                arrayPositionValue = arrayPositionValue.sorted{ $0.1 < $1.1 }
                            
                                for i in 0..<arr.count {
                                    var cycleSize = 0
                                    var visitedIndex = i
                            
                                    while !visitedDictionary[visitedIndex]! {
                                        visitedDictionary[visitedIndex] = true
                                        visitedIndex = arrayPositionValue[visitedIndex].0
                                        cycleSize += 1
                                    }
                            
                                    if cycleSize > 0 {
                                        swapCount += cycleSize - 1
                                    }
                                }
                            
                                return swapCount
                            }
                            
                            

                            【讨论】:

                              【解决方案19】:

                              找出排列 1..N 所需的最小交换次数。

                              我们可以使用我们知道的排序结果:1..N,这意味着我们实际上不必进行交换,只需计算它们即可。

                              1..N的改组称为置换,由不相交的循环置换组成,例如1..6的这个置换:

                              1 2 3 4 5 6
                              6 4 2 3 5 1
                              

                              由循环排列 (1,6)(2,4,3)(5) 组成

                              1->6(->1) cycle: 1 swap
                              2->4->3(->2) cycle: 2 swaps
                              5(->5) cycle: 0 swaps
                              

                              因此,k 个元素的循环需要 k-1 次交换才能整理好。

                              由于我们知道每个元素“属于”的位置(即值 k 属于位置 k-1),我们可以轻松地遍历循环。从 0 开始,我们得到 6,它属于 5, 在那里我们找到了 1,它属于 0,我们又回到了开始的地方。

                              为避免以后重新计算一个周期,我们会跟踪访问了哪些元素 - 或者您可以执行交换,以便以后访问元素时它们位于正确的位置。

                              生成的代码:

                              def minimumSwaps(arr):
                                  visited = [False] * len(arr)
                                  numswaps = 0
                                  for i in range(len(arr)):
                                      if not visited[i]:
                                          visited[i] = True
                                          j = arr[i]-1
                                          while not visited[j]:
                                              numswaps += 1
                                              visited[j] = True
                                              j = arr[j]-1
                                  return numswaps
                              

                              【讨论】:

                                猜你喜欢
                                • 2019-08-08
                                • 2015-02-18
                                • 1970-01-01
                                • 2015-01-06
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2016-04-04
                                相关资源
                                最近更新 更多