用swift創建一個quick sort 的演示動畫

前言

visualgo這個網站啟發,于是想要實現一個iOS版的排序動畫演示版,本篇主要介紹如何用swift3寫一個帶有演示效果快速排序。同時希望以這種方式幫助大家趣味性、具體性的在腦海里形成回路來學習算法
你也可以在我的GitHub中下載源碼github Example
如有錯誤歡迎指正,同時歡迎在GitHub上提issue

QuickSort.gif

提供自定義配置

需修改請參考QuickSortViewController.swift文件上部進行修改

let JVMaxNumber:UInt32 = 50  //產生隨機數的最大值

let sleepTime:UInt32 = 300000    //1 sec = 1000000 休眠時間可調整動畫快慢

let numberCount=13   //多少個待排序的數

let normalColor:UIColor = UIColor.init(red: 172/255, green: 216/255, blue: 231/255, alpha: 1.0)

let pivotColor:UIColor = UIColor.init(red: 255/255, green: 255/255, blue: 2/255, alpha: 1.0)

let currentColor:UIColor = UIColor.init(red: 220/255, green: 20/255, blue: 60/255, alpha: 1.0)

let minColor:UIColor = UIColor.init(red: 60/255, green: 179/255, blue: 113/255, alpha: 1.0)

let maxColor:UIColor = UIColor.init(red: 153/255, green: 51/255, blue: 204/255, alpha: 1.0)

let endColor:UIColor = UIColor.init(red: 255/255, green: 165/255, blue: 0/255, alpha: 1.0)


關鍵代碼

定義類和接口

在下手之前,我們要構思好我們要寫的東西,先抽象出類、寫好提供給外部調用的方法。這也是一種好習慣與君共勉
我們需要自定義一個渲染的View來渲染出圖表,這里我們定義為JVgraphView。
JVgraphView需要提供一些方法給ViewController調用

func drawGraph(array:Array<Int>)
func startSort()

同時我們還需要自定義一個每一個Item的View來展示當前的數字、長短、顏色,我們把它命名為JVgraphItemView,提供如下方法

init(frame: CGRect,itemValue:Int)
func changeState(state:GIoptionState)

//表示狀態的枚舉,根據狀態來轉換顏色
enum GIoptionState:Int{
    case normal,pivot,current,min,max,end
}

填充定義方法

JVgraphItemView

注意 你將在本文多次見到 ** usleep(sleepTime)** 是為了讓動畫演示過程中有停頓,為了讓UI不被卡住,都是在次線程中停頓,再返回到主線程渲染UI

//MARK: - init 根據frame 和當前的數字 渲染每一個Item
    init(frame: CGRect,itemValue:Int) {
        super.init(frame: frame)
        number=itemValue
        oneHeight=self.frame.height/CGFloat(JVMaxNumber)
        let gHeight=oneHeight*CGFloat(itemValue)
        let y = self.frame.height-gHeight
        ghView.frame=CGRect.init(x: 0, y: y, width: self.frame.width, height: gHeight)
        self.addSubview(ghView)
//大于臨界值的數字渲染在圖像里面
        if itemValue>critical {
            valueLabel.frame=CGRect.init(x: 0, y: y, width: self.frame.width, height: labelHeight)
        }else{
            valueLabel.frame=CGRect.init(x: 0, y: y-labelHeight, width: self.frame.width, height: labelHeight)
        }
        valueLabel.text=String(itemValue)
        valueLabel.textColor=UIColor.black
        valueLabel.textAlignment=NSTextAlignment.center
        self.addSubview(valueLabel)
        
        self.ghView.backgroundColor=normalColor
    }
//根據狀態改變顏色
    func changeState(state:GIoptionState) {
        
        DispatchQueue.main.async(execute: {
            switch state {
            case .normal:
                self.ghView.backgroundColor=normalColor
            case .current:
                self.ghView.backgroundColor=currentColor
            case .pivot:
                self.ghView.backgroundColor=pivotColor
            case .min:
                self.ghView.backgroundColor=minColor
            case .max:
                self.ghView.backgroundColor=maxColor
            default:
                self.ghView.backgroundColor=endColor
            }
        })
    }
JVgraphView code
//    MARK: - draw UI
    func drawGraph(array:Array<Int>) {
        self.clearView()
        count=array.count
//計算item之間的間隙
        gap=(self.frame.width-CGFloat(count)*itemWidth)/CGFloat(count+1)
        //for 循環渲染 item
        for i in 0..<count {
            let item=array[i]
            let view=JVgraphItemView.init(frame: self.getItemRect(item: CGFloat(i)),itemValue: item)
            self.addSubview(view)
            viewArray.append(view)
        }
    }
    
    //計算每一個item的frame
    func getItemRect(item:CGFloat) -> CGRect {
        var x:CGFloat=0,y:CGFloat=0,w:CGFloat=0,h:CGFloat=0
        x=item==0 ? gap : (item+1)*gap+item*itemWidth
        y=0
        w=itemWidth
        h=self.frame.height
        return CGRect.init(x: x, y: y, width: w, height: h)
    }

在實現func startSort()的相應之前呢 我們需要實現快速排序:

func quickSort(start:Int,end:Int)
func swap(changeIdx:Int,toIdx:Int)

func swap 最簡單我們先從簡單的開始

   func swap(changeIdx:Int,toIdx:Int){
       if changeIdx==toIdx {
           return
       }
       let view1=self.viewArray[changeIdx]
       let view2=self.viewArray[toIdx]
       let cRT:CGRect=view1.frame
       let tRT:CGRect=view2.frame
       
//交換兩個數據
       let temporary:JVgraphItemView = self.viewArray[changeIdx]
       self.viewArray[changeIdx]=self.viewArray[toIdx]
       self.viewArray[toIdx]=temporary
       
               usleep(sleepTime)
       //創建信號量次線程等待主線程更新完畢之后在執行
       let semaphore = DispatchSemaphore(value: 0)
       //在主線程更新UI
       DispatchQueue.main.sync {
           UIView.animate(withDuration: 0.25, animations: {
               view1.frame=tRT
               view2.frame=cRT
           }, completion: { (finished) in
//          執行完畢發送信號
               semaphore.signal()
           })
       }
//            等待信號  
       semaphore.wait()
   }

func quickSort的關鍵代碼和注釋

func quickSort(start:Int,end:Int) {
        //把已經完成排序的改變endColor
        if start == end{
            usleep(sleepTime)
         self.viewArray[start].changeState(state: .end)
        }
        //跳出遞歸
        if start>=end {
            return;
        }
        let startItem = self.viewArray[start]
        usleep(sleepTime)
//        改變基準點顏色
        startItem.changeState(state: .pivot)
        
        let pivot:Int=startItem.number
        var i:Int=start+1
        var storeIndex:Int=i
        while i<end+1 {
//         再進行排序之前需要改變之前進行過判斷的item的顏色
//         這里我是把之前的信息用Dictionary<JVgraphItemView,Bool>來存儲,
//          每次里面只有一個key和一個value 其他存儲方式也是可以的, Bool表示之前的大小,根據大小改變對應的顏色
            if lastItemDict.keys.count>0 {
                for item:JVgraphItemView in (lastItemDict.keys) {
                    if (lastItemDict[item])!{
                        usleep(sleepTime)
                        item.changeState(state: .max)
                    }else{
                        usleep(sleepTime)
                        item.changeState(state: .min)
                    }
                }
                lastItemDict.removeAll()
            }
//            改變當前光標顏色
            let currentItem=self.viewArray[i]
            usleep(sleepTime)
            currentItem.changeState(state: .current)
            //交換比較小的到大小相鄰的位置
            if currentItem.number<pivot {
                self.swap(changeIdx: i, toIdx: storeIndex)
                storeIndex+=1
//            存儲大小信息
                lastItemDict.updateValue(false, forKey: currentItem)
            }else{
//            存儲大小信息
                lastItemDict.updateValue(true, forKey: currentItem)
            }
            i += 1
        }
        
        self.swap(changeIdx: start, toIdx: storeIndex-1)
        
        
        usleep(sleepTime)
//        除了已經確定的基準點以外的點全部的狀態改為normal
        for (i,item) in self.viewArray.enumerated() {
            if i>=start&&i<=end {
                if item.isEqual(startItem) {
                    item.changeState(state: .end)
                }else{
                    item.changeState(state: .normal)
                }
                lastItemDict.removeAll()
            }
        }
        
        //判斷特殊情況的顏色改變,只有兩個item的時候全部改為end狀態
        if (end-start) == 1 {
            usleep(sleepTime)
            viewArray[end].changeState(state: .end)
        }
        
//        遞歸調用
        self.quickSort(start: start, end: storeIndex-2)
        self.quickSort(start: storeIndex, end: end)
    }

接下來只需要在func startSort里面調用就好了

//    MARK: - QuckSort
    func startSort(){
        DispatchQueue.global().async {
            self.quickSort(start: 0, end: self.viewArray.count-1)
//      提供給viewController 的回調
            if self.finishAction != nil{
                self.finishAction?()
            }
            usleep(sleepTime)
            for item in self.viewArray{
                item.changeState(state: .normal)
            }
        }
    }

結束語

通過寫完演示動畫之后相信你一定不會在覺得算法枯燥了,無趣了吧。
后期如果有時間接著分享關于算法的趣味學習
Thank You

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容