swift演算法練習筆記

在路上重名了啊發表於2018-12-17

一、氣泡排序及優化

[TOC]

protocol SortAbleSwift {
    func sort(item:[NSNumber]) -> [NSNumber]
}
複製程式碼

五種寫法的執行結果

優化核心:過濾掉已經排好序的

最後一種優化完成後,比較10個數字,只比較了19次

swift演算法練習筆記

class SortVC: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let array: [NSNumber] = [1, 7, 3, 5, 6, 4, 2, 8, 9, 10]
        print("source = \(array)\n")
        do {
            let bubulle: BubbleSwift0 = BubbleSwift0.init()
            let result = bubulle.sort(item: array)
            print("result = \(result)\n")
        }
        do {
            let bubulle: BubbleSwift1 = BubbleSwift1.init()
            let result = bubulle.sort(item: array)
            print("result = \(result)\n")
        }
        do {
            let bubulle: BubbleSwift2 = BubbleSwift2.init()
            let result = bubulle.sort(item: array)
            print("result = \(result)\n")
        }
        do {
            let bubulle: BubbleSwift3 = BubbleSwift3.init()
            let result = bubulle.sort(item: array)
            print("result = \(result)\n")
        }
        do {
            let bubulle: BubbleSwift4 = BubbleSwift4.init()
            let result = bubulle.sort(item: array)
            print("result = \(result)\n")
        }
    }
}
複製程式碼

一:最先想到的(效率最低)

class BubbleSwift0: SortAbleSwift {
    func sort(item: [NSNumber]) -> [NSNumber] {
        var swapCount: Int = 0                  // 交換次數
        var compareCount: Int = 0               // 比較次數
        var result = item                       // 可變陣列
        let count = result.count                // 陣列長度
        for _ in 0..<count {
            for j in 0..<count-1 {
                compareCount = compareCount + 1
                if result[j].intValue > result[j+1].intValue {
                    result.swapAt(j, j+1)
                    swapCount = swapCount + 1
                }
            }
        }
        print("比較次數 = \(compareCount) 交換次數 = \(swapCount)")
        return result
    }
}
複製程式碼

二:對每趟比較次數做優化

class BubbleSwift1: SortAbleSwift {
    func sort(item: [NSNumber]) -> [NSNumber] {
        var swapCount: Int = 0                  // 交換次數
        var compareCount: Int = 0               // 比較次數
        var result = item                       // 可變陣列
        let count = result.count                // 陣列長度
        for i in 0..<count {
            for j in 0..<count-i-1 {
                compareCount = compareCount + 1
                if result[j].intValue > result[j+1].intValue {
                    result.swapAt(j, j+1)
                    swapCount = swapCount + 1
                }
            }
        }
        print("比較次數 = \(compareCount) 交換次數 = \(swapCount)")
        return result
    }
}
複製程式碼

三:對(整體)已經排好序的做優化

class BubbleSwift2: SortAbleSwift {
    func sort(item: [NSNumber]) -> [NSNumber] {
        var swapCount: Int = 0                  // 交換次數
        var compareCount: Int = 0               // 比較次數
        var result = item                       // 可變陣列
        let count = result.count                // 陣列長度
        for i in 0..<count {
            var isOrderly: Bool = true          // 是否已經排好序
            for j in 0..<count-i-1 {
                compareCount = compareCount + 1
                if result[j].intValue > result[j+1].intValue {
                    result.swapAt(j, j+1)
                    swapCount = swapCount + 1
                    isOrderly = false
                }
            }
            if isOrderly { break }
        }
        print("比較次數 = \(compareCount) 交換次數 = \(swapCount)")
        return result
    }
}
複製程式碼

四:對(右半部分)已經排好序的做優化

class BubbleSwift3: SortAbleSwift {
    func sort(item: [NSNumber]) -> [NSNumber] {
        var swapCount: Int = 0                  // 交換次數
        var compareCount: Int = 0               // 比較次數
        var result = item                       // 可變陣列
        let count = result.count                // 陣列長度
        var lastPosition: Int = count - 1       // (向右上浮最大值)最後一次的交換位置
        for _ in 0..<count {
            var isOrderly: Bool = true          // 是否已經排好序
            var lastSwap: Int = 0
            for j in 0..<lastPosition {
                compareCount = compareCount + 1
                if result[j].intValue > result[j+1].intValue {
                    result.swapAt(j, j+1)
                    swapCount = swapCount + 1
                    isOrderly = false
                    lastSwap = j
                }
            }
            if isOrderly { break }
            lastPosition = lastSwap
        }
        print("比較次數 = \(compareCount) 交換次數 = \(swapCount)")
        return result
    }
}
複製程式碼

五:對(左半部分 + 右半部分)已經排好序的做優化

class BubbleSwift4: SortAbleSwift {
    func sort(item: [NSNumber]) -> [NSNumber] {
        var swapCount: Int = 0                      // 交換次數
        var compareCount: Int = 0                   // 比較次數
        var result = item                           // 可變陣列
        let count = result.count                    // 陣列長度
        var orderlyMaxPosition: Int = count - 1     // (向右上浮最大值)最後一次的交換位置
        var orderlyMinPosition: Int = 0             // (向左下沉最小值)最後一次的交換位置
        for _ in 0..<count {
            var isOrderly: Bool = true              // 是否已經排好序
            var orderlyMaxSwapPosition: Int = 0     // 有效的最大交換位置(後面的已經排好序)
            var orderlyMinSwapPosition: Int = 0     // 有效的最小交換位置(前面的已經排好序)
            // 向右尋上浮大值
            do {
                var j: Int = orderlyMinPosition
                while j < orderlyMaxPosition {
                    compareCount = compareCount + 1
                    if result[j].intValue > result[j+1].intValue {
                        result.swapAt(j, j+1)
                        swapCount = swapCount + 1
                        orderlyMaxSwapPosition = j
                        isOrderly = false
                    }
                    j=j+1
                }
            }
            if isOrderly { break }
            orderlyMaxPosition = orderlyMaxSwapPosition

            // 向左下沉最小值
            do {
                var j: Int = orderlyMaxPosition
                while j > orderlyMinPosition {
                    compareCount = compareCount + 1
                    if result[j-1].intValue > result[j].intValue {
                        result.swapAt(j-1, j)
                        swapCount = swapCount + 1
                        orderlyMinSwapPosition = j
                        isOrderly = false
                    }
                    j=j-1
                }
            }
            if isOrderly { break }
            orderlyMinPosition = orderlyMinSwapPosition
        }
        print("比較次數 = \(compareCount) 交換次數 = \(swapCount)")
        return result
    }
}
複製程式碼

參考部落格

【排序】:氣泡排序以及三種優化

二、五個常用演算法中的貪心演算法

swift技能點練習

五大常用演算法

  • 貪心案例一:會議安排

  • 貪心案例二:零錢支付

  • 貪心案例三:過河問題

驗證結果

swift技能點練習

  • Arraysort使用
  • Arrayreduce函式使用
  • tuple的使用
  • struct的使用
  • class的使用
  • Swift中的高階函式

五大常用演算法

  • 1、分治演算法
  • 2、動態規劃演算法
  • 3、貪心演算法
  • 4、回溯法
  • 5、分支界限法

貪心案例一:會議安排

問題描述:

只有一間會議室,在有限時間內(一天)安排最多的會議(不能衝突,兩個會議不能同時進行)

方案一: (非最優)每次選擇持續時長最短的會議,但是有可能持續時長最短的結束時間最晚

方案二: (非最優)每次選擇開始時間最早的會議,但是有可能開始時間最早的持續時長對長

方案三: (最優)每次選擇開始時間最早&持續時間最短的會議,也就是結束時間最早的會議,這是最優策略,可以安排更多的會議

struct Meeting {
    var number: Int = 0     // 會議編號
    var begin: Int = 0      // 會議開始時間
    var end: Int = 0        // 會議結束時間
}

class ArrangeMeeting {
    /// 隨機建立count個會議
    ///
    /// - Parameter count: 會議數量
    class func createRandomMeetings(count: Int) -> [Meeting] {
        var meetings:[Meeting] = []
        for idx in 0..<count {
            var meeting: Meeting = Meeting.init()
            meeting.number = idx
            meeting.begin = Int(arc4random()%100)
            meeting.end = meeting.begin + Int(arc4random()%100)
            meetings.append(meeting)
        }
        return meetings
    }
    
    /// 從給定的會議陣列中選出期望的會議陣列
    ///
    /// - Parameter meetingIn: 給定的會議陣列
    /// - Returns: 期望的會議陣列
    class func getExpectMeetings(meetingIn: [Meeting]) -> [Meeting] {
        var meetings: [Meeting] = meetingIn
        // 儲存符合條件的會議的陣列
        var expectMeetings: [Meeting] = []
        // 第一步:將所有會議按照結束時間從小到大排序
        meetings.sort { (c1, c2) -> Bool in
            return c1.end < c2.end
        }
        // 第二步:新增符合條件的會議到陣列中
        for var idx in 0..<meetings.count {
            if idx == 0 {
                expectMeetings.append(meetings[idx])
                continue
            }
            // 找到第一個開始時間比上一次會議結束時間晚的會議
            var goal: (duration: Int, idx: Int) = (0, 0)
            if meetings[idx].begin >= expectMeetings.last!.end {
                goal.duration = meetings[idx].end - meetings[idx].begin
                goal.idx = idx
            }
            // 獲取開始時間相同 && 持續時間最短的會議
            while((idx+1 < meetings.count) && (meetings[idx].begin == meetings[idx+1].begin)) {
                idx = idx+1
                let curDuration: Int = meetings[idx].end - meetings[idx].begin
                if curDuration < goal.duration {
                    goal.duration = curDuration
                    goal.idx = idx
                }
            }
            if goal.duration > 0 {
                expectMeetings.append(meetings[goal.idx])
            }
        }
        return expectMeetings
    }
}
複製程式碼

貪心案例二:零錢支付

問題描述:

有1元硬幣、2元硬幣、5元硬幣、10元硬幣,每一種有若干個,從中選擇幾種組合支付N元,找出話費硬幣個數最少的支付方案

這種場景可能沒有結果,比如所有硬幣用完,但是還沒有達到支付總額

方案一: (非最優解)只選擇幣值最大的,行不通,沒法解決比最大值小的場景,而且還有幣種個數限制

方案二: (非最優解)只選擇幣值最小的,行不通,最後結果不可能是最少的,而且還有幣種個數限制

方案三: (最優解)優先選擇幣值最大的優先支付,然後依次選擇次大的幣值優先支付...

struct Coin {
    var value: Int = 0
    var count: Int = 0
}

class PayWithCoin {
    class func payCase1(amount: Int) -> [Coin] {
        var payCoins: [Coin] = []
        let ownedCoins: [Coin] = [Coin.init(value: 10, count: 10),
                                  Coin.init(value: 5, count: 5),
                                  Coin.init(value: 2, count: 1),
                                  Coin.init(value: 1, count: 10)]
        // 通過reduce函式計算出手中的硬幣能夠支付的最大額度
        let maxAmount = ownedCoins.reduce(0, { result, obj in
            result + obj.count * obj.value
        })
        if amount > maxAmount {
            // 超出最大支付額度
            return []
        }
        var amountWillPay: Int = amount
        for idx in 0..<ownedCoins.count {
            if amountWillPay >= ownedCoins[idx].value {
                // 當前幣種消耗的個數
                let value = ownedCoins[idx].value
                let count = ownedCoins[idx].count
                // 使用該幣種支付全部需要 needCount 個
                let needCount = amountWillPay / value
                // 如果該幣種能夠支付,並且有剩餘,或者正好能夠支付
                if needCount <= count {
                    // 剩餘需要消耗的金額
                    let surplus = amountWillPay % value
                    amountWillPay = surplus
                    let coin: Coin = Coin.init(value: value, count: needCount)
                    payCoins.append(coin)
                }
                // 如果該幣種不能夠支付,自己可以支付的部分
                else {
                    // 剩餘需要消耗的金額
                    let surplus = amountWillPay - value * count
                    amountWillPay = surplus
                    let coin: Coin = Coin.init(value: value, count: count)
                    payCoins.append(coin)
                }
                if amountWillPay <= 0 {
                    break
                }
            }
        }
        return payCoins
    }
}
複製程式碼

貪心案例三:過河問題

問題:

n個人過河,船每次最多隻能坐兩個人,船載每個人過河的所需時間不同,問最快的過河時間。

最優方案:

如果n >= 4time = t[1] + t[0] + t[n-1] + t[1]

主旋律: 最快的人,次最快的人...次最慢的人,最慢的人

步調: 把最快的人和次最快的人,送完次最慢的人和最慢的人,稱為一次基本迴圈 . . . 如果 n = 3time = t[1] + t[0] + t[2]

如果 n = 2time = t[1]

如果 n = 1time = t[0]

struct Crosser {
    var timeConsuming: Int = 0
}

class CrossRiver {
    
    /// 隨機建立count個過河的人,每個人的過河時間隨機
    ///
    /// - Parameter count: 過河的人的數量
    /// - Returns: 所有過河的人
    class func createRandomCrossers(count: Int) -> [Crosser] {
        var crossers: [Crosser] = []
        for _ in 0..<count {
            let crosser: Crosser = Crosser.init(timeConsuming: Int(arc4random()%10))
            crossers.append(crosser)
        }
        return crossers
    }
    
    /// 獲取所有人過河所需要的最短時間
    ///
    /// - Parameter corssers: 所有過河的人
    /// - Returns: 最短的過河時間
    class func getMinimumTimeConsuming(corssers: [Crosser]) -> Int {
        var leftCrosser: [Crosser] = corssers
        // 對所有過河的人排序(規則 = 最快的人排在最前面,最慢的人排在最後面)
        leftCrosser.sort { (c1, c2) -> Bool in
            return c1.timeConsuming < c2.timeConsuming
        }
        var totalTimeConsuming: Int = 0
        while leftCrosser.count > 0 {
            if leftCrosser.count == 1 {
                totalTimeConsuming = totalTimeConsuming + leftCrosser[0].timeConsuming
                // 移除掉已經過河的人
                leftCrosser.removeFirst()
            }
            else if leftCrosser.count == 2 {
                totalTimeConsuming = totalTimeConsuming + leftCrosser[1].timeConsuming
                // 移除掉已經過河的人
                leftCrosser.removeFirst()
                leftCrosser.removeFirst()
            }
            else if leftCrosser.count == 3 {
                totalTimeConsuming = totalTimeConsuming +
                    leftCrosser[1].timeConsuming +
                    leftCrosser[0].timeConsuming +
                    leftCrosser[2].timeConsuming
                // 移除掉已經過河的人
                leftCrosser.removeFirst()
                leftCrosser.removeFirst()
                leftCrosser.removeFirst()
            }
            else {
                // 最快的和次最快的過去,最快的回來;最慢的和次最慢的過去,次最快的回來。
                totalTimeConsuming = totalTimeConsuming +
                    leftCrosser[1].timeConsuming +
                    leftCrosser[0].timeConsuming +
                    leftCrosser[leftCrosser.count-1].timeConsuming +
                    leftCrosser[1].timeConsuming
                // 移除掉已經過河的人
                leftCrosser.removeLast()
                leftCrosser.removeLast()
            }
        }
        return totalTimeConsuming
    }
}
複製程式碼

驗證結果

class GreedyAlgorithmVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 會議安排問題
        do {
            let allMeetings: [Meeting] = ArrangeMeeting.createRandomMeetings(count: 10)
            let expectMeetings: [Meeting] = ArrangeMeeting.getExpectMeetings(meetingIn: allMeetings)
            print("expectMeetings = \(expectMeetings)")
        }
        // 零錢支付問題
        do {
            let payCoins: [Coin] = PayWithCoin.pay(amount: 101)
            print("payCoins = \(payCoins)")
        }
        // 過河問題
        do {
            // 網上四人過河的例子
            do {
                let leftCrosser: [Crosser] = [Crosser.init(timeConsuming: 5),
                                              Crosser.init(timeConsuming: 2),
                                              Crosser.init(timeConsuming: 1),
                                              Crosser.init(timeConsuming: 10)]
                let timerConsuming: Int = CrossRiver.getMinimumTimeConsuming(corssers: leftCrosser)
                print("timerConsuming = \(timerConsuming)")
            }
            // 通用的例子(隨機建立n個過河的人,每個人的過河時間也隨機)
            do {
                let crossers: [Crosser] = CrossRiver.createRandomCrossers(count: 10)
                let timerConsuming: Int = CrossRiver.getMinimumTimeConsuming(corssers: crossers)
                print("timerConsuming = \(timerConsuming)")
            }
        }
    }
}
複製程式碼

三、查詢指定字串中第一個只出現一次的字元

思路:

第一步:遍歷字串,使用字典儲存字元出現的個數,key為字元,value為出現的次數

第二步:再次遍歷字串,記錄遍歷的下標,如果字元對應的個數==1,則返回該字元和對應的下標

/// 【劍指Offer】第一個只出現一次的字元位置
///
/// - Parameter src: 傳入的字串
/// - Returns: 由字元和下標組成的元組(Int, Character),如果下標 index = -1,表示沒有找到
func firstNotRepeatingChar(src: String) -> (Int, Character) {
    var mapCount: [Character:Int] = [:]
    for case let ch in src {
        mapCount[ch] = (mapCount[ch] ?? 0) + 1;
    }
    var index: Int = 0
    for case let ch in src {
        if let count = mapCount[ch] {
            if count == 1 {
                return (index, ch)
            }
        }
        index = index + 1
    }
    return (-1, Character.init("0"))
}
複製程式碼

思路2: (ASCII碼對照表)[ascii.911cha.com/]

第一步:每個字元的最大值為126,建立一個長度為126的陣列,遍歷字串用字元對應的ASCII碼對應的下標對應陣列中的值儲存字元個數

第二步:同思路一,再次遍歷字串,記錄遍歷的下標,如果字元對應的個數==1,則返回該字元和對應的下標

四、實現在字串中找出連續最長的字串

思路:

  • 從頭開始遍歷字串,記錄連續字元開始位置index和個數count
  • 使用元組curData記錄當前的連續字串的下標index和個數count
  • 使用元組resultData記錄count最大的連續字串的下標index和個數count
  • curData記錄下一個或者字串遍歷完畢的時候,取curDataresultDatacount最大的元組儲存到resultData
  • 最後resultData中儲存的是目標字串的開始位置和個數
import UIKit
class ArithmeticTest: NSObject {
    /// 找出給定字串中連續最長的字串(滿足一定條件)
    ///
    /// - Parameters:
    ///   - str: 源字串
    ///   - range: 條件
    /// - Returns: 目標字串
    func findMaxCountNumberStr(str: String, range:ClosedRange<Int>) -> String {
        guard str.count > 0 else {
            return ""
        }
        var curData = (index:0, count:0)        // 記錄(當前)連續字串的開始位置index和個數count
        var resultData = (index:0, count:0)     // 記錄(結果)連續字串的開始位置index和個數count
        var curIndex: Int = 0
        while curIndex+1 < str.count {
            curData.count = 1
            curData.index = curIndex
            while curIndex+1 < str.count, range.contains(str[curIndex]?.int() ?? -1), str[curIndex]?.int() == str[curIndex+1]?.int() {
                curData.count = curData.count + 1
                curIndex = curIndex + 1
            }
            if curData.count > resultData.count {
                resultData.count = curData.count
                resultData.index = curData.index
            }
            curIndex = curIndex + 1
        }
        let resultStr: String = str[resultData.index..<(resultData.index+resultData.count)]
        return resultStr
    }
}

extension Character {
    func int() -> Int {
        var intFromCharacter:Int = 0
        for scalar in String(self).unicodeScalars {
            intFromCharacter = Int(scalar.value)
        }
        return intFromCharacter
    }
}

extension String {
    subscript (i: Int) -> Character? {
        guard i < self.count else{
            return nil
        }
        return self[self.index(self.startIndex, offsetBy: i)]
    }
    subscript (i: Int) -> String? {
        if let char: Character = self[i] {
            return String(char)
        }
        return nil
    }
    subscript (r: Range<Int>) -> String {
        let start = index(startIndex, offsetBy: r.lowerBound)
        let end = index(startIndex, offsetBy: r.upperBound)
        return String(self[start..<end])
    }
    subscript (r: ClosedRange<Int>) -> String {
        let start = index(startIndex, offsetBy: r.lowerBound)
        let end = index(startIndex, offsetBy: r.upperBound)
        return String(self[start...end])
    }
}
複製程式碼

參考資料

[程式設計題]第一個只出現一次的字元

五、快速排序OC實現:

/**
 測試程式碼
 */
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSMutableArray* array = [NSMutableArray array];
    [array addObject:@10];
    [array addObject:@5];
    [array addObject:@12];
    [array addObject:@1];
    [array addObject:@45];
    [self quickSort:array left:0 right:array.count-1];
    NSLog(@"array = %@", array);
}

/**
 交換陣列中的兩個下標位置的值

 @param arr 陣列
 @param x x下標
 @param y y下標
 */
- (void)swaparr:(NSMutableArray<NSNumber*>*)arr x:(NSInteger)x y:(NSInteger)y {
    NSInteger temp = arr[x].integerValue;    
    [arr replaceObjectAtIndex:x withObject:@(arr[y].integerValue)];
    [arr replaceObjectAtIndex:y withObject:@(temp)];
}

/**
 快速排序演算法
 */
- (void)quickSort:(NSMutableArray<NSNumber*>*)arr left:(NSInteger)left right:(NSInteger)right {
    if (arr.count <= 0) {
        return;
    }
    
    // 遞迴退出條件
    if (left >= right) {
        return;
    }
    
    // 選取左值為參考物件
    NSInteger pivot = arr[left].integerValue;
    NSInteger i = left, j = right;
    while (i < j) {
        // j 向左移動,直到arr[j] <= pivot
        while (arr[j].integerValue >= pivot && i < j) {
            j--;
        }
        // i 向右移動,直到arr[i] >= pivot
        while (arr[i].integerValue <= pivot && i < j) {
            i++;
        }
        // 交換arr[i] 和 arr[j]的值
        if (i < j) {
            [self swaparr:arr x:i y:j];
        }
    }
    // 交換 pivot 和 arr[i] 的值
    [self swaparr:arr x:left y:i];
    
    // 遞迴pivot左側的陣列
    [self quickSort:arr left:left right:i-1];
    // 遞迴pivot右側的陣列
    [self quickSort:arr left:i+1 right:right];
}
複製程式碼

相關文章