有趣的排序问题

时间:2010-06-22 11:54:55

标签: algorithm sorting

按特定顺序存在一个,零和'U'。 (例如“1001UU0011”)1和0的数量是相同的,并且总是有两个'U'彼此相邻。您可以将这对'U'与任何一对相邻数字交换。这是一个示例移动:

      __
     /  \
1100UU0011 --> 11001100UU

任务是将所有零置于之前。

以下是一个示例解决方案:

First step:
  __
 /  \
1100UU0011

Second step:
  ____
 /    \
UU00110011

000011UU11  --> DONE

创建强力算法非常容易。但是,就像我的例子一样,需要数百甚至数千个动作来解决一个简单的动作。所以我正在寻找更“聪明”的算法。


这不是功课;这是比赛中的一项任务。比赛已经结束,但我无法找到解决方案。

编辑:这里的任务是创建一个算法,可以对那些0和1进行排序 - 而不仅仅是输出N 0和N 1和2 Us。你必须以某种方式显示步骤,就像在我的例子中一样。

编辑2 :任务没有以最少的动作或类似的方式询问结果。但我个人希望看到一个算法,提供:)

8 个答案:

答案 0 :(得分:4)

我认为这应该有效:

  • 迭代一次找到的位置 美国。如果他们不占用最后一个 两个点,移动它们 与最后两个交换。
  • 创建一个 变量来跟踪当前 已排序的元素,最初设置为 array.length - 1,意思是什么 在分类之后。
  • 迭代 向后。每次遇到一个 1:
    • 将之前的那个及其元素与U的交换。
    • 将U的交换回当前已排序的元素跟踪器-1,更新变量
  • 继续直到数组的开头。

答案 1 :(得分:3)

这是一个非常有趣的问题 - 让我们试着解决它。我将首先对问题进行精确分析,看看能找到什么。我将在接下来的几天逐个添加这个答案。欢迎任何帮助。

大小为n的问题恰好是n个零,n个和U个问题,因此它包含2n+2符号。

(2n)!
-----
(n!)²

完全n零和n个的不同序列。然后有2n+1个可能的位置来插入两个U,因此有

(2n)!         (2n+1)!
-----(2n+1) = -------
(n!)²          (n!)²

大小为n的问题实例。

接下来,我正在寻找一种方法,为每个问题实例分配一个分数,以及该分数在所有可能的动作下如何变化,希望找出最小数量的所需动作。

大小一的实例已经排序

--01   0--1   01--

(我想我会使用连字符代替U,因为它们更容易识别)或无法排序。

--10  ==only valid move==>  10--
-10-  no valid move
10--  ==only valid move==>  --10

因此我将假设n >= 2

我正在考虑反问题 - 从有序序列开始可以到达什么无序序列。有序序列被确定为两个连字符的位置 - 所以下一个问题是是否有可能从每个其他顺序序列到达每个有序序列。因为可以向前和向后执行一系列移动,所以足以表明一个特定的有序序列可以从所有其他序列到达。我选择(0|n)(1|n)--。 ((0|x)完全代表x个零。如果x不是n-m形式,则假定为零或更多。可能还有其他约束,如a+b+2=n未明确陈述。^^表示交换位置.0 / 1边界显然在最后一个零和第一个之间。)

// n >= 2, at least two zeros between -- and the 0/1 border
(0|a)--(0|b)00(1|n) => (0|n)--(1|n-2)11 => (0|n)(1|n)--
            ^^                       ^^
// n >= 3, one zero between -- and 0/1 boarder
(0|n-1)--01(1|n-1) => (0|n)1--(1|n-3)11 => (0|n)(1|n)--
         ^^                          ^^
// n >= 2, -- after last zero but at least two ones after --          
(0|n)(1|a)--(1|b)11 => (0|n)(1|n)--
                 ^^
// n >= 3, exactly one one after --
(0|n)(1|n-3)11--1 => (0|n)(1|n-3)--111 => (0|n)(1|n)--
            ^^                      ^^
// n >= 0, nothing to move
(0|n)(1|n)--

对于剩下的两个大小为2的问题 - 0--011001--1 - 似乎无法达到0011--。因此,对于n >= 3,可以在最多四次移动中从每个其他有序序列到达每个有序序列(在所有情况下可能更少,因为我认为选择(0|n)--(1|n)会更好但是我留下这个为了明天。)。初步目标是找出可以创建(并因此删除)010101的速率和条件,因为它们似乎是其他人已经提到过的难题。

答案 2 :(得分:2)

如果你使用WIDTH优先蛮力,它仍然是暴力,但至少你可以保证提出最短的动作序列,如果有答案的话。这是一个使用宽度优先搜索的快速Python解决方案。

from time import time

def generate(c):
    sep = "UU"
    c1, c2 = c.split(sep)
    for a in range(len(c1)-1):
        yield c1[0:a]+sep+c1[(a+2):]+c1[a:(a+2)]+c2
    for a in range(len(c2)-1):
        yield c1+c2[a:(a+2)]+c2[0:a]+sep+c2[(a+2):]

def solve(moves,used):
    solved = [cl for cl in moves if cl[-1].rindex('0') < cl[-1].index('1')]
    if len(solved) > 0: return solved[0]
    return solve([cl+[d] for cl in moves for d in generate(cl[-1]) if d not in used and not used.add(d)],used)

code = raw_input('enter the code:')

a = time()
print solve([[code]],set())
print "elapsed time:",(time()-a),"seconds"

答案 3 :(得分:1)

嗯,我首先想到的是自上而下的动态编程方法。它有点容易理解但可能会占用大量内存。虽然我正在尝试应用自下而上的方法,但您可以试试这个:

想法很简单 - 缓存蛮力搜索的所有结果。它会变成这样:

function findBestStep(currentArray, cache) {
    if (!cache.contains(currentArray)) {
        for (all possible moves) {
            find best move recursively
        }
        cache.set(currentArray, bestMove);
    } 

    return cache.get(currentArray);
}

这种方法的复杂性将是...... O(2 ^ n)令人毛骨悚然。但是,我认为没有任何合理的方法,因为任何移动都是允许的。

如果找到一种应用自下而上算法的方法,它可能会快一点(它不需要缓存),但它仍然具有O(2 ^ n)复杂度。

<强>加了: 好的,我已经用Java实现了这个功能。代码很长,因为它总是在Java中,所以不要害怕它的大小。主算法非常简单,可以在底部找到。我认为没有比这更快的方法(如果它更快,这更像是一个数学问题)。它占用了大量的内存,但仍然可以很快地计算出来。 这个0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,2计算在1秒内,吃掉~60mb的记忆,导致7步排序。

public class Main {

    public static final int UU_CODE = 2;

    public static void main(String[] args) {
        new Main();
    }

    private static class NumberSet {
        private final int uuPosition;
        private final int[] numberSet;
        private final NumberSet parent;

        public NumberSet(int[] numberSet) {
            this(numberSet, null, findUUPosition(numberSet));
        }

        public NumberSet(int[] numberSet, NumberSet parent, int uuPosition) {
            this.numberSet = numberSet;
            this.parent = parent;
            this.uuPosition = uuPosition;
        }

        public static int findUUPosition(int[] numberSet) {
            for (int i=0;i<numberSet.length;i++) {
                if (numberSet[i] == UU_CODE) {
                    return i;
                }
            }
            return -1;
        }

        protected NumberSet getNextNumberSet(int uuMovePos) {
            final int[] nextNumberSet = new int[numberSet.length];
            System.arraycopy(numberSet, 0, nextNumberSet, 0, numberSet.length);
            System.arraycopy(this.getNumberSet(), uuMovePos, nextNumberSet, uuPosition, 2);
            System.arraycopy(this.getNumberSet(), uuPosition, nextNumberSet, uuMovePos, 2);
            return new NumberSet(nextNumberSet, this, uuMovePos);
        }

        public Collection<NumberSet> getNextPositionalSteps() {
            final Collection<NumberSet> result = new LinkedList<NumberSet>();

            for (int i=0;i<=numberSet.length;i++) {
                final int[] nextNumberSet = new int[numberSet.length+2];
                System.arraycopy(numberSet, 0, nextNumberSet, 0, i);
                Arrays.fill(nextNumberSet, i, i+2, UU_CODE);
                System.arraycopy(numberSet, i, nextNumberSet, i+2, numberSet.length-i);
                result.add(new NumberSet(nextNumberSet, this, i));
            }
            return result;
        }

        public Collection<NumberSet> getNextSteps() {
            final Collection<NumberSet> result = new LinkedList<NumberSet>();

            for (int i=0;i<=uuPosition-2;i++) {
                result.add(getNextNumberSet(i));
            }

            for (int i=uuPosition+2;i<numberSet.length-1;i++) {
                result.add(getNextNumberSet(i));
            }

            return result;
        }

        public boolean isFinished() {
            boolean ones = false;
            for (int i=0;i<numberSet.length;i++) {
                if (numberSet[i] == 1)
                    ones = true;
                else if (numberSet[i] == 0 && ones)
                    return false;
            }
            return true;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final NumberSet other = (NumberSet) obj;
            if (!Arrays.equals(this.numberSet, other.numberSet)) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 83 * hash + Arrays.hashCode(this.numberSet);
            return hash;
        }

        public int[] getNumberSet() {
            return this.numberSet;
        }

        public NumberSet getParent() {
            return parent;
        }

        public int getUUPosition() {
            return uuPosition;
        }
    }

    void precacheNumberMap(Map<NumberSet, NumberSet> setMap, int length, NumberSet endSet) {
        int[] startArray = new int[length*2];
        for (int i=0;i<length;i++) startArray[i]=0;
        for (int i=length;i<length*2;i++) startArray[i]=1;
        NumberSet currentSet = new NumberSet(startArray);

        Collection<NumberSet> nextSteps = currentSet.getNextPositionalSteps();
        List<NumberSet> nextNextSteps = new LinkedList<NumberSet>();
        int depth = 1;

        while (nextSteps.size() > 0) {
            for (NumberSet nextSet : nextSteps) {
                if (!setMap.containsKey(nextSet)) {
                    setMap.put(nextSet, nextSet);
                    nextNextSteps.addAll(nextSet.getNextSteps());
                    if (nextSet.equals(endSet)) {
                        return;
                    }
                }
            }
            nextSteps = nextNextSteps;
            nextNextSteps = new LinkedList<NumberSet>();
            depth++;
        }
    }

    public Main() {
        final Map<NumberSet, NumberSet> cache = new HashMap<NumberSet, NumberSet>();
        final NumberSet startSet = new NumberSet(new int[] {0,1,0,1,0,1,0,1,0,1,0,1,0,1,2,2});

        precacheNumberMap(cache, (startSet.getNumberSet().length-2)/2, startSet);

        if (cache.containsKey(startSet) == false) {
            System.out.println("No solutions");
        } else {
            NumberSet cachedSet = cache.get(startSet).getParent();
            while (cachedSet != null && cachedSet.parent != null) {
                System.out.println(cachedSet.getUUPosition());
                cachedSet = cachedSet.getParent();
            }
        }
    }
}

答案 4 :(得分:0)

这是一个尝试:

Start:
  let c1 = the total number of 1s
  let c0 = the total number of 0s
  if the UU is at the right end of the string, goto StartFromLeft
StartFromRight
  starting at the right end of the string, move left, counting 1s, 
  until you reach a 0 or the UU.  
  If you've reached the UU, goto StartFromLeft.
  If the count of 1s equals c1, you are done.  
  Else, swap UU with the 0 and its left neighbor if possible.  
  If not, goto StartFromLeft.
StartFromLeft
  starting at the left end of the string, move right, counting 0s,
  until you reach a 1 or the UU.
  If you've reached the UU, goto StartFromRight.
  If the count of 0s equals c0, you are done.
  Else, swap UU with the 1 and its right neighbor, if possible.  
  If not, goto StartFromRight
  Then goto StartFromRight.

所以,对于原来的1100UU0011:

1100UU0011 - original
110000UU11 - start from right, swap UU with 00
UU00001111 - start from left, swap UU with 11

对于棘手的0101UU01

0101UU01 - original
0UU11001 - start from right, can't swap UU with U0, so start from left and swap UU with 10
00011UU1 - start from right, swap UU with 00

然而,这不会解决像01UU0这样的问题...但是这可以通过一个标志来修复 - 如果你已经完成整个算法一次,没有交换并且它没有被解决...做某事

答案 5 :(得分:0)

关于这个问题......它从未要求最佳解决方案,而且这些类型的问题不希望如此。您需要编写通用算法来处理此问题,并且对于长度可能为兆字节的字符串,查找最佳解决方案的强力搜索是不可行的。另外我注意到,保证有相同数量的0和1,但我认为使用可能有不同数量的0和1的一般情况更有意思。如果输入字符串的长度小于7,实际上并不能保证在每种情况下都是一个解决方案,即使在你有2 0和2 1的情况下也是如此。

尺寸3:只有一位数,因此按定义排序(UU0 UU1 0UU 1UU)

尺寸4:无法改变订单。如果UU在中间,则没有移动,如果它在一端,则只与两个数字交换(1UU0无移动,UU10-> 10UU-> UU10等)

尺寸5:中间的UU只能移动到远端而不会改变0和1的顺序(1UU10-> 110UU)。一端的UU可以移动到中间而不是改变顺序,但是只能移回到同一端,因此没有用它(UU110-> 11UU0-> UU110)。更改数字的唯一方法是UU是否结束并与对端交换。 (UUABC-> BCAUU或ABCUU-> UUCAB)。这意味着如果UU位于0或2位置,它可以解决0是否在中间(UU101-> 011UU或UU100-> 001UU)并且如果UU位于1或3位置,它可以解决1是否在中(010UU-> UU001或110UU-> UU011)。其他任何事情已经解决或无法解决。如果我们需要处理这种情况,我会说硬编码。如果已排序,则返回结果(无移动)。如果UU位于某处,请将其移至最后。从结尾切换到另一端,这是唯一可能的交换,无论它现在是否已经排序。

大小6:现在我们得到一个位置,我们可以根据我们可以进行移动的规则指定一个字符串,但是没有解决方案。这是任何算法的问题点,因为我认为任何解决方案的条件应该是它会让你知道它是否无法解决。例如,无论在何处放置UU,都可以解决0010,0100,1000,1011,1100,1101和1110,并且最坏的情况需要4个移动来解决。只有当UU处于奇数位置时才能解决0101和1010。只有当UU处于偶数位置(结束或中间)时,才能解决0110和1001。

我认为最好的方法将是以下内容,但我尚未编写。首先,确保在列表末尾放置一个“1”。如果结束当前为0,则将UU移动到最后,然后将其移动到最后的'1'位置 - 1.之后,您不断地将UU移动到第一个'1',然后移动到新UU之后的第一个'0'。这会将所有0移动到列表的开头。我从另一个方面看到了类似的答案,但没有考虑到两端的最终角色。这可能会遇到小值仍然存在的问题(即001UU01,无法移动到第1个,移到结束00101UU让我们移动到开始但在00UU110结束时离开0)。

我的猜测是你可以硬编码这样的特殊情况。我想可能有更好的算法。例如,您可以使用前两个字符作为“临时交换变量”。你会把UU放在那里,然后对其他人进行操作组合,以便在开始时让UY回来​​。例如,UUABCDE可以将AB与CD或DE或BC与DE交换(BCAUUDE-> BCADEUU-> UUADEBC)。

另一种可能的方法是将字符视为两个base-3位的两个块 0101UU0101将显示为11C11或3593.也许类似于硬编码交换的组合。例如,如果您看到11UU,请将UU向左移动2.如果您看到UU00,请将UU向右移动两个。如果您看到UU100或UU101,请将UU右移2以获得001UU或011UU。

也许另一种可能性是某些算法将0左侧的中心和1的右侧移动(如果给出的是0和1的相同数量。

也许最好只使用一个只包含0和1且UU位置的结构。

也许更好地查看结果条件,允许UU在字符串中的任何位置,必须满足这些条件: 长度/ 2后没有0 之前没有1(长度/ 2-1)

也许有更多的一般规则,比如在这种情况下将UU与10交换'10111UU0'非常好,因为'0'现在在UU之后,这会让你将新的00移回10的位置( 10111UU0-&GT; UU111100-&GT; 001111UU)

无论如何,这是C#中的暴力代码。输入是一个字符串和一个空字典。它将字典填入每个可能的结果字符串作为键和最短步骤列表作为值:

呼叫:

m_Steps = new Dictionary<string, List<string>>();
DoSort("UU1010011101", new List<string>);

它包括DoTests(),它为具有给定位数(不包括UU)的每个可能字符串调用DoSort:

Dictionary<string, List<string>> m_Steps = new Dictionary<string, List<string>>();

public void DoStep(string state, List<string> moves) {
 if (m_Steps.ContainsKey(state) && m_Steps[state].Count <= moves.Count + 1) // have better already
  return;

 // we have a better (or new) solution to get to this state, so set it to the moves we used to get here
 List<string> newMoves = new List<string>(moves);
 newMoves.Add(state);
 m_Steps[state] = newMoves;

 // if the state is a valid solution, stop here
 if (state.IndexOf('1') > state.LastIndexOf('0'))
  return;

 // try all moves
 int upos = state.IndexOf('U');
 for (int i = 0; i < state.Length - 1; i++) {
  // need to be at least 2 before or 2 after the UU position (00UU11 upos is 2, so can only move to 0 or 4)
  if (i > upos - 2 && i < upos + 2)
   continue;

  char[] chars = state.ToCharArray();
  chars[upos] = chars[i];
  chars[upos + 1] = chars[i + 1];
  chars[i] = chars[i + 1] = 'U';
  DoStep(new String(chars), newMoves);
 }
}

public void DoTests(int digits) { // try all combinations
 char[] chars = new char[digits + 2];
 for (int value = 0; value < (2 << digits); value++) {
  for (int uupos = 0; uupos < chars.Length - 1; uupos++) {
   for (int i = 0; i < chars.Length; i++) {
    if (i < uupos)
     chars[i] = ((value >> i) & 0x01) > 0 ? '1' : '0';
    else if (i > uupos + 1)
     chars[i] = ((value >> (i - 2)) & 0x01) > 0 ? '1' : '0';
    else
     chars[i] = 'U';
   }
   m_Steps = new Dictionary<string, List<string>>();
   DoSort(new string(chars), new List<string>);
   foreach (string key in m_Steps.AllKeys))
    if (key.IndexOf('1') > key.LastIndexOf('0')) { // winner
     foreach (string step in m_Steps[key])
      Console.Write("{0}\t", step);
     Console.WriteLine();
    }
  }
 }
}

答案 6 :(得分:-2)

Counting sort

如果A是0的数量,A也是1的数量,U是我们的数量:

for(int i=0; i<A; i++)
   data[i] = '0';
for(int i=0; i<A; i++)
   data[A+i] = '1';
for(int i=0; i<U; i++)
   data[A+A+i] = 'U';

答案 7 :(得分:-2)

只有2个我们?

为什么不计算0的数量并存储我们的位置:

numberOfZeros = 0
uPosition = []
for i, value in enumerate(sample):
    if value = 0:
        numberOfZeros += 1
    if value = U
       uPosition.append(i)

result = []
for i in range(len(sample)):
    if i = uPosition[0]
       result.append('U')
       uPosition.pop(0)
       continue
    if numberOfZeros > 0:
       result.append('0')
       numberOfZeros -= 1
       continue
    result.append('1')

会导致O(n)

的运行时间

甚至更好:

result = []
numberOfZeros = (len(sample)-2)/2
for i, value in enumerate(sample):
    if value = U
       result.append('U')
       continue
    if numberOfZeros > 0:
       result.append(0)
       numberOfZeros -= 1
       continue
    result.append(1)