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