算法帖——用舞蹈鏈算法(Dancing Links)求解俄羅斯方塊覆蓋問題

問題的提出:以下圖,用13塊俄羅斯方塊覆蓋8*8的正方形。如何用計算機求解?html

 

254t

 

解決這類問題的方法不一而足,然而核心思想都是窮舉法,不一樣的方法僅僅是對窮舉法進行了優化算法

 

用13塊不一樣形狀的俄羅斯方塊(每一個方塊只能使用一次)覆蓋住棋盤,很容易就想到這是「精確覆蓋問題」(13個俄羅斯方塊徹底覆蓋住8*8的正方形)。而舞蹈鏈算法(Dancing Links)是比較好求解「精確覆蓋問題」的算法,由於該算法在窮舉的過程當中,再也不額外增長空間負擔,狀態的回溯也比較方便,能快捷的排除無效的窮舉過程。有關舞蹈鏈算法(Dancing Links),在這裏再也不贅述,詳情參看「跳躍的舞者,舞蹈鏈(Dancing Links)算法——求解精確覆蓋問題dom

 

用舞蹈鏈算法(Dancing Links)解決問題的核心是把問題轉換爲問題矩陣oop

 

很直觀的,這樣的矩陣一共有77列,其中第1-64列表示8*8正方形的每個單元格,第65-77列表明方塊的編號優化

這樣求解出來的解就是正方形的每個單元格都有方塊填充,每一個方塊都被使用了一次spa

 

以上圖爲例,我把左下角的深綠色的方塊定義爲方塊1,而這個深綠色方塊又佔用了第4九、5七、5八、5九、60單元格3d

那麼這個深綠色的方塊所構造的數據行就是以下表示htm

{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0}blog

 

爲了方便描述,咱們把上面的行矩陣記做{49,5七、5八、5九、60、65}接口

 

而咱們要作的就是,構造出全部的數據行

 

先把以下圖方塊1的全部能在的位置作成數據行

S1-1

則一共能有7行*5列=35種可能

 

同時,巧妙利用中心旋轉的算法,分別得出旋轉90度、180度、270度的位置可能

以下所示

S1-2

旋轉90度的圖

S1-3

旋轉180度的圖

S1-4

旋轉270度的圖

 

這樣一來,只須要遍歷最早圖的形狀位置便可,其他旋轉的形狀的能夠依次推導。

上面的形狀還有一個以下圖的,須要遍歷

S2-1

 

這樣一來,這個形狀1的全部位置就遍歷完成了。

 

依次遍歷13個形狀,這樣就生成了問題矩陣的全部行

代碼以下:

 
Public  Class  clsTetris
         Implements  I_Question

    Private _Shapes As  List( Of  clsTetrisShape)
    Private _Index() As  Integer

    Public  ReadOnly  Property Cols As  Integer  Implements  I_Question.Cols
        Get
            Return 77
        End  Get
    End  Property

    Public  Function ConvertFromDance(Answer() As  Integer) As  Object  Implements  I_Question.ConvertFromDance
        Debug.Print(Answer.Length)

        Dim tBmp As  New  Bitmap(320, 320)
        Dim tG As  Graphics = Graphics.FromImage(tBmp)

        tG.Clear( Color.White)


        Dim I As  Integer
        For I = 0 To Answer.Length - 1

            _Shapes(_Index(Answer(I) - 1)).DrawShape(tG)

        Next

        Return tBmp
    End  Function


    Public  ReadOnly  Property ExtraCols As  Integer  Implements  I_Question.ExtraCols
        Get
            Return 77
        End  Get
    End  Property

    Public  Sub ConvertToDance(Dance As  clsDancingLinksImproveNoRecursive) Implements  I_Question.ConvertToDance
        _Shapes = New  List( Of  clsTetrisShape)

        Dim I As  Integer, J As  Integer
        Dim tShape As  clsTetrisShape, tRotateShape As  clsTetrisShape
        Dim S As  Integer

        'Shape 1

        For I = 0 To 6
            For J = 0 To 4
                S = I * 8 + J
                tShape = New  clsTetrisShape(1, S, S + 1, S + 2, S + 3, S + 8)

                AppendAllShapes(Dance, tShape)

            Next
        Next


        For I = 0 To 6
            For J = 0 To 4
                S = I * 8 + J
                tShape = New  clsTetrisShape(1, S, S + 8, S + 9, S + 10, S + 11)

                AppendAllShapes(Dance, tShape)

            Next
        Next



        'Shape 2
        For I = 0 To 5
            For J = 0 To 5
                S = I * 8 + J
                tShape = New  clsTetrisShape(2, S, S + 1, S + 9, S + 10, S + 18)

                AppendAllShapes(Dance, tShape)

            Next
        Next



        'Shape3
        For I = 0 To 5
            For J = 0 To 5
                S = I * 8 + J
                tShape = New  clsTetrisShape(3, S, S + 1, S + 9, S + 10, S + 17)

                AppendAllShapes(Dance, tShape)

            Next
        Next

        For I = 0 To 5
            For J = 1 To 6
                S = I * 8 + J
                tShape = New  clsTetrisShape(3, S, S + 1, S + 7, S + 8, S + 16)

                AppendAllShapes(Dance, tShape)

            Next
        Next


        'Shape 4
        For I = 0 To 5
            For J = 0 To 5
                S = I * 8 + J
                tShape = New  clsTetrisShape(4, S, S + 1, S + 2, S + 8, S + 16)

                AppendAllShapes(Dance, tShape)

            Next
        Next



        'Shape5
        For I = 0 To 6
            For J = 0 To 4
                S = I * 8 + J
                tShape = New  clsTetrisShape(5, S, S + 1, S + 2, S + 10, S + 11)

                AppendAllShapes(Dance, tShape)

            Next
        Next

        For I = 0 To 6
            For J = 1 To 5
                S = I * 8 + J
                tShape = New  clsTetrisShape(5, S, S + 1, S + 2, S + 7, S + 8)

                AppendAllShapes(Dance, tShape)

            Next
        Next

        'Shape6
        For I = 0 To 5
            For J = 0 To 5
                S = I * 8 + J
                tShape = New  clsTetrisShape(6, S, S + 8, S + 9, S + 10, S + 18)

                _Shapes.Add(tShape)

                tRotateShape = tShape.Rotate90
                _Shapes.Add(tRotateShape)

            Next
        Next

        For I = 0 To 5
            For J = 2 To 7
                S = I * 8 + J
                tShape = New  clsTetrisShape(6, S, S + 6, S + 7, S + 8, S + 14)

             
                _Shapes.Add(tShape)

                tRotateShape = tShape.Rotate90
                _Shapes.Add(tRotateShape)

            Next
        Next

        'Shape 7


        For I = 0 To 5
            For J = 0 To 5
                S = I * 8 + J
                tShape = New  clsTetrisShape(7, S, S + 1, S + 2, S + 9, S + 17)

                AppendAllShapes(Dance, tShape)

            Next
        Next

        'Shape 8

        For I = 0 To 6
            For J = 0 To 5
                S = I * 8 + J
                tShape = New  clsTetrisShape(8, S, S + 1, S + 2, S + 8, S + 9)

                AppendAllShapes(Dance, tShape)

            Next
        Next

        For I = 0 To 6
            For J = 0 To 5
                S = I * 8 + J
                tShape = New  clsTetrisShape(8, S, S + 1, S + 2, S + 9, S + 10)

                AppendAllShapes(Dance, tShape)

            Next
        Next

        'Shape 9

        For I = 0 To 6
            For J = 0 To 4
                S = I * 8 + J
                tShape = New  clsTetrisShape(9, S, S + 1, S + 2, S + 3, S + 9)

                AppendAllShapes(Dance, tShape)

            Next
        Next

        For I = 0 To 6
            For J = 0 To 4
                S = I * 8 + J
                tShape = New  clsTetrisShape(9, S, S + 1, S + 2, S + 3, S + 10)

                AppendAllShapes(Dance, tShape)

            Next
        Next

        'Shape 10

        For I = 0 To 6
            For J = 0 To 6
                S = I * 8 + J
                tShape = New  clsTetrisShape(10, S, S + 1, S + 8, S + 9)

                _Shapes.Add(tShape)

            Next
        Next


        'Shape 11

        For I = 0 To 5
            For J = 1 To 6
                S = I * 8 + J
                tShape = New  clsTetrisShape(11, S, S + 7, S + 8, S + 9, S + 16)

                _Shapes.Add(tShape)

            Next
        Next

        'Shape12
        For I = 0 To 7
            For J = 0 To 3
                S = I * 8 + J
                tShape = New  clsTetrisShape(12, S, S + 1, S + 2, S + 3, S + 4)

                _Shapes.Add(tShape)

                tRotateShape = tShape.Rotate90
                _Shapes.Add(tRotateShape)

            Next
        Next



        'Shape 13

        For I = 0 To 6
            For J = 0 To 5
                S = I * 8 + J
                tShape = New  clsTetrisShape(13, S, S + 1, S + 2, S + 8, S + 10)

                AppendAllShapes(Dance, tShape)

            Next
        Next

        ReDim _Index(_Shapes.Count - 1)

        For I = 0 To _Shapes.Count - 1
            _Index(I) = I
        Next

        Dim R As  New  Random, tSwap As  Integer

        For I = _Shapes.Count - 1 To Int(_Shapes.Count / 3) Step -1
            J = R.Next(I)
            tSwap = _Index(J)
            _Index(J) = _Index(I)
            _Index(I) = tSwap
        Next

        For I = 0 To _Shapes.Count - 1
            Dance.AppendLine(_Shapes(_Index(I)).GetLineValue)
        Next

    End  Sub

    Private  Sub AppendAllShapes(Dance As  clsDancingLinksImproveNoRecursive, tShape As  clsTetrisShape)
        Dim tRotateShape As  clsTetrisShape

        _Shapes.Add(tShape)

        tRotateShape = tShape.Rotate90
        _Shapes.Add(tRotateShape)

        tRotateShape = tShape.Rotate180
        _Shapes.Add(tRotateShape)

        tRotateShape = tShape.Rotate270
        _Shapes.Add(tRotateShape)
    End  Sub

    Public  ReadOnly  Property IsRandomSolution As  Boolean  Implements  I_Question.IsRandomSolution
        Get
            Return  False
        End  Get
    End  Property
End  Class

 

 

上面這個類實現了I_Question接口,代碼以下:

 

 
Public  Interface  I_Question
    ReadOnly  Property Cols As  Integer
    ReadOnly  Property ExtraCols As  Integer
    ReadOnly  Property IsRandomSolution As  Boolean

    Sub ConvertToDance(Dance As  clsDancingLinksImproveNoRecursive)

    Function ConvertFromDance(Answer() As  Integer) As  Object

End  Interface

 

幾個參數解釋一下

Cols:問題矩陣的數據列數

ExtraCols:問題矩陣必須覆蓋的列數。大多數的狀況下,和Cols相等,也就是全部列徹底覆蓋

IsRandomSolution:一個開關,指示求解過程當中,是按照最少列優先求解(爲False的時候)仍是隨機選擇列求解(爲True的時候),在列數比較少的狀況下,能夠爲True,不然不建議使用True,爲True的時候,若是存在多個解,每次求解有可能得出不一樣的解。

ConvertToDance:將數據轉換爲問題矩陣,並輸入到指定的Dance類

ConvertFromDance:Dance類計算得出結果後,將結果返回給實現接口的類,讓該類對結果進行相應的處理。

 

 

類clsTetris還內置了clsTetrisShape類,定義每一個形狀的編號、位置、並最終將每一個形狀繪製到指定的圖上,以下:

 

  Public  Class  clsTetrisShape
    Private Poi() As  Integer
    Private ShapeType As  Integer



    Public  Sub  New(ShapeType As  Integer, ParamArray Poi() As  Integer)
        Me.ShapeType = ShapeType
        Dim I As  Integer
        ReDim  Me.Poi(Poi.Length - 1)

        For I = 0 To Poi.Length - 1
            Me.Poi(I) = Poi(I)
        Next
    End  Sub

    Public  Function GetLineValue() As  Integer()
        Dim Value(76) As  Integer
        Dim I As  Integer
        For I = 0 To 76
            Value(I) = 0
        Next

        For I = 0 To Poi.Length - 1
            Value(Poi(I)) = 1
        Next

        Value(63 + ShapeType) = 1

        Return Value
    End  Function

    Public  Function Rotate90() As  clsTetrisShape
        Dim NewPoi(Poi.Length - 1) As  Integer
        Dim I As  Integer, X As  Integer, Y As  Integer

        For I = 0 To Poi.Length - 1
            X = Int(Poi(I) / 8)
            Y = Poi(I) Mod 8
            NewPoi(I) = Y * 8 + 7 - X
        Next

        Return  New  clsTetrisShape(ShapeType, NewPoi)
    End  Function

    Public  Function Rotate180() As  clsTetrisShape
        Dim NewPoi(Poi.Length - 1) As  Integer
        Dim I As  Integer

        For I = 0 To Poi.Length - 1
            NewPoi(I) = 63 - Poi(I)
        Next

        Return  New  clsTetrisShape(ShapeType, NewPoi)
    End  Function

    Public  Function Rotate270() As  clsTetrisShape
        Dim NewPoi(Poi.Length - 1) As  Integer
        Dim I As  Integer, X As  Integer, Y As  Integer

        For I = 0 To Poi.Length - 1
            X = Int(Poi(I) / 8)
            Y = Poi(I) Mod 8
            NewPoi(I) = (7 - Y) * 8 + X
        Next

        Return  New  clsTetrisShape(ShapeType, NewPoi)
    End  Function

    Public  Sub DrawShape(G As  Graphics)
        Dim tBrush As  SolidBrush
        Select  Case ShapeType
            Case 1
                tBrush = New  SolidBrush( Color.FromArgb(84, 130, 53))
            Case 2
                tBrush = New  SolidBrush( Color.FromArgb(112, 48, 160))
            Case 3
                tBrush = New  SolidBrush( Color.FromArgb(166, 166, 166))
            Case 4
                tBrush = New  SolidBrush( Color.FromArgb(0, 176, 240))
            Case 5
                tBrush = New  SolidBrush( Color.FromArgb(0, 32, 96))
            Case 6
                tBrush = New  SolidBrush( Color.FromArgb(0, 0, 0))
            Case 7
                tBrush = New  SolidBrush( Color.FromArgb(192, 0, 0))
            Case 8
                tBrush = New  SolidBrush( Color.FromArgb(255, 217, 102))
            Case 9
                tBrush = New  SolidBrush( Color.FromArgb(0, 112, 192))
            Case 10
                tBrush = New  SolidBrush( Color.FromArgb(0, 176, 80))
            Case 11
                tBrush = New  SolidBrush( Color.FromArgb(255, 255, 0))
            Case 12
                tBrush = New  SolidBrush( Color.FromArgb(198, 89, 17))
            Case 13
                tBrush = New  SolidBrush( Color.FromArgb(146, 208, 80))
            Case Else
                tBrush = New  SolidBrush( Color.FromArgb(146, 208, 80))
        End  Select

        Dim I As  Integer, X As  Integer, Y As  Integer
        For I = 0 To Poi.Length - 1
            X = Int(Poi(I) / 8)
            Y = Poi(I) Mod 8

            G.FillRectangle(tBrush, New  Rectangle(Y * 40, X * 40, 40, 40))
        Next
    End  Sub
End  Class

 

 

 

而後是貼出求解類

 

  Public  Class  clsDancingCentre
    Public  Shared  Function Dancing(Question As  I_Question) As  Object
        Dim _Dance As  New  clsDancingLinksImproveNoRecursive(Question.Cols, Question.ExtraCols)

        Question.ConvertToDance(_Dance)

        Return Question.ConvertFromDance(_Dance.Dance(Question.IsRandomSolution))
    End  Function
End  Class

 

該類只有一個核心方法,定義一個舞蹈鏈算法(Dancing Links)類,並對該類和I_Question接口搭橋求解問題

 

在clsTetris類中,本來若是設置IsRandomSolution爲True的話,那麼求解過程很是緩慢(曾經1小時沒有求出一個解出來),但若是設置爲False的時候,每次求解是秒破,可是每次求解都是同一個結果。後來想到,交換問題矩陣的行,會影響求解的順序,但不影響求解的結果。若是求解的結果是惟一的,那麼矩陣的行交不交換都同樣,可是若是求解的問題不是惟一的,那麼改變問題矩陣的行,那麼每次求解出來的解就有可能不一樣。故在clsTetris中,在最後把數據添加到Dance類的時候,是改變了添加順序的,這樣每次求解都是秒破,而且得出的結果也不同。求解100個解,不到30秒。

 

最後貼出Dancing類,這纔是舞蹈鏈算法(Dancing Links)的核心

 

  Public  Class  clsDancingLinksImproveNoRecursive
    Private Left() As  Integer, Right() As  Integer, Up() As  Integer, Down() As  Integer
    Private Row() As  Integer, Col() As  Integer

    Private _Head As  Integer

    Private _Rows As  Integer, _Cols As  Integer, _NodeCount As  Integer
    Private Count() As  Integer

    Private Ans() As  Integer


    Public  Sub  New( ByVal Cols As  Integer)
        Me.New(Cols, Cols)
    End  Sub


    Public  Sub  New( ByVal Cols As  Integer, ExactCols As  Integer)
        ReDim Left(Cols), Right(Cols), Up(Cols), Down(Cols), Row(Cols), Col(Cols), Ans(Cols)
        ReDim Count(Cols)
        Dim I As  Integer

        Up(0) = 0
        Down(0) = 0
        Right(0) = 1
        Left(0) = Cols

        For I = 1 To Cols
            Up(I) = I
            Down(I) = I
            Left(I) = I - 1
            Right(I) = I + 1
            Col(I) = I
            Row(I) = 0

            Count(I) = 0
        Next

        Right(Cols) = 0

        _Rows = 0
        _Cols = Cols
        _NodeCount = Cols
        _Head = 0


        Dim N As  Integer = Right(ExactCols)

        Right(ExactCols) = _Head
        Left(_Head) = ExactCols

        Left(N) = _Cols
        Right(_Cols) = N

    End  Sub


    Public  Sub AppendLine( ByVal  ParamArray Value() As  Integer)
        Dim V As  New  List( Of  Integer)

        Dim I As  Integer
        For I = 0 To Value.Length - 1
            If Value(I) <> 0 Then V.Add(I + 1)
        Next

        AppendLineByIndex(V.ToArray)

    End  Sub

    Public  Sub AppendLine(Line As  String)
        Dim V As  New  List( Of  Integer)

        Dim I As  Integer
        For I = 0 To Line.Length - 1
            If Line.Substring(I, 1) <> "0"  Then V.Add(I + 1)
        Next

        AppendLineByIndex(V.ToArray)
    End  Sub

    Public  Sub AppendLineByIndex( ByVal  ParamArray Index() As  Integer)

        If Index.Length = 0 Then  Exit Sub
        _Rows += 1

        Dim I As  Integer, K As  Integer = 0

        ReDim  Preserve Left(_NodeCount + Index.Length)
        ReDim  Preserve Right(_NodeCount + Index.Length)
        ReDim  Preserve Up(_NodeCount + Index.Length)
        ReDim  Preserve Down(_NodeCount + Index.Length)
        ReDim  Preserve Row(_NodeCount + Index.Length)
        ReDim  Preserve Col(_NodeCount + Index.Length)

        ReDim  Preserve Ans(_Rows)

        For I = 0 To Index.Length - 1

            _NodeCount += 1

            If I = 0 Then
                Left(_NodeCount) = _NodeCount
                Right(_NodeCount) = _NodeCount
            Else
                Left(_NodeCount) = _NodeCount - 1
                Right(_NodeCount) = Right(_NodeCount - 1)
                Left(Right(_NodeCount - 1)) = _NodeCount
                Right(_NodeCount - 1) = _NodeCount
            End  If

            Down(_NodeCount) = Index(I)
            Up(_NodeCount) = Up(Index(I))
            Down(Up(Index(I))) = _NodeCount
            Up(Index(I)) = _NodeCount

            Row(_NodeCount) = _Rows
            Col(_NodeCount) = Index(I)

            Count(Index(I)) += 1
        Next

    End  Sub


    Public  Function Dance( Optional Random As  Boolean = False) As  Integer()
        Dim P As  Integer, C1 As  Integer
        Dim I As  Integer, J As  Integer

        Dim K As  Integer = 0
        Dim R As  New  Random



        Do
            If (Right(_Head) = _Head) Then
                ReDim  Preserve Ans(K - 1)
                For I = 0 To Ans.Length - 1
                    Ans(I) = Row(Ans(I))
                Next
                Return Ans
            End  If

            P = Right(_Head)
            C1 = P


            If Random = False  Then
                Do  While P <> _Head
                    If Count(P) < Count(C1) Then C1 = P
                    P = Right(P)
                Loop
            Else

                I = R.Next(_Cols)
                For J = 1 To I
                    P = Right(P)
                Next
                If P = _Head Then P = Right(_Head)
                C1 = P
            End  If

            RemoveCol(C1)

            I = Down(C1)

            Do  While I = C1
                ResumeCol(C1)

                K -= 1
                If K < 0 Then  Return  Nothing
                C1 = Col(Ans(K))
                I = Ans(K)
                J = Left(I)
                Do  While J <> I
                    ResumeCol(Col(J))
                    J = Left(J)
                Loop
                I = Down(I)
            Loop

            Ans(K) = I
            J = Right(I)
            Do  While J <> I
                RemoveCol(Col(J))
                J = Right(J)
            Loop

            K += 1
        Loop
    End  Function

    Private  Sub RemoveCol( ByVal ColIndex As  Integer)

        Left(Right(ColIndex)) = Left(ColIndex)
        Right(Left(ColIndex)) = Right(ColIndex)

        Dim I As  Integer, J As  Integer

        I = Down(ColIndex)
        Do  While I <> ColIndex
            J = Right(I)
            Do  While J <> I
                Up(Down(J)) = Up(J)
                Down(Up(J)) = Down(J)

                Count(Col(J)) -= 1

                J = Right(J)
            Loop

            I = Down(I)
        Loop

    End  Sub

    Private  Sub ResumeCol( ByVal ColIndex As  Integer)

        Left(Right(ColIndex)) = ColIndex
        Right(Left(ColIndex)) = ColIndex

        Dim I As  Integer, J As  Integer

        I = Up(ColIndex)

        Do  While (I <> ColIndex)
            J = Right(I)
            Do  While J <> I
                Up(Down(J)) = J
                Down(Up(J)) = J

                Count(Col(J)) += 1

                J = Right(J)
            Loop
            I = Up(I)
        Loop

    End  Sub
End  Class

 

注:

求解了1000個解,發現頗有趣的一個現象,就是長條(1*5的那個),幾乎都在邊上,在當中的解少之又少

下面貼幾個解

000t

 

001t

 

002t

 

003t

 

004t

 

005t

 

006t

 

007t

相關文章
相關標籤/搜索