Swift 数据结构与算法

排序算法

冒泡排序

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

  1. 从数组a的第0个下标开始操作, 取出元素.
  2. 用该元素对比下一个元素, 如果大于(或小于), 则把该元素的与其交换, 否则不交换.
    意义: 把大的数据往后移.
  3. 下标+1, 取出元素.
  4. 重复步骤2, 直到下标到达数组最后1位.
  5. 重复步骤1到步骤4, 步骤4操作至数组下标最后第2位. 以此类推, 直到最后步骤4的下标到达数组的第1位.
func bubbleSort(_  arr: inout [Int]) {
    for i in 0..<arr.count {
        for j in 0..<(arr.count - i - 1){
            if arr[j] > arr[j+1] {
                arr.swapAt(j, j+1)
            }
        }
    }
}

快速排序

取数组的一个元素作为基准, 将数组拆分两段, 左边一段的元素总小于右边一段的元素, 然后再对这两段数据进行同样的操作, 当拆分至两段数组的宽度小于等于1的时候, 数组中任何一个元素左边的元素均小于等于它, 其右边的元素均大于等于它.

  1. 取数组a的中间下标的元素x.

  2. 把小于(或等于)x的其他元素放在x的前面(数组b), 大于x的其他元素形放在x的后面(数组b).

  3. 重复对数组b和数组c做步骤1和步骤2的操作, 直到在步骤1中被操作的数组长度变成不大于1.

    解释: 每次操作都把小于某元素的子集放到左边, 大于的放在右边, 以此类推操作, 最后每个元素x左边的值都会小于x, 其右边的值都会大于x, 这样就实现了排序目的.

func quickSort(_ arr: [Int]) -> [Int] {
    if arr.count <= 1 {
        return arr
    }
    let mid = arr[arr.count / 2]
    var left: [Int] = [], right: [Int] = []
    for i in 0..<arr.count {
        if i != arr.count / 2 {
            let val = arr[i]
            if val > mid {
                right.append(val)
            }else {
                left.append(val)
            }
        }
    }
    return quickSort(left) + [mid] + quickSort(right)
}

归并排序

递归地把数组拆分成两段子数组, 分别对两段子数组进行排序, 然后把已经有序的两段数组合并起来. 当拆分的子数组宽度小于等于1的时候递归函数即可返回.

步骤(合并):

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针达到序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾
func mergeSort(_ arr: [Int]) -> [Int] {

    if arr.count <= 1 {
        return arr
    }

    let mid = arr.count / 2

    var left: [Int] = [], right: [Int] = []

    for i in 0..<arr.count {
        if i < mid {
            left.append(arr[i])
        }else {
            right.append(arr[i])
        }
    }

    var sorted1 = mergeSort(left), sorted2 = mergeSort(right)

    return _mergeArray(&sorted1, &sorted2)
}

func _mergeArray(_ sorted1: inout [Int], _ sorted2: inout [Int]) -> [Int] {
    var result: [Int] = []

    while !sorted1.isEmpty && !sorted2.isEmpty {
        let val1 = sorted1.first!, val2 = sorted2.first!
        if val1 < val2 {
            result.append(sorted1.removeFirst())
        }else {
            result.append(sorted2.removeFirst())
        }
    }

    if sorted1.isEmpty {
        result.append(contentsOf: sorted2)
    }

    if sorted2.isEmpty {
        result.append(contentsOf: sorted1)
    }
    return result
}

选择排序

  1. 取数组a中的第1个元素,逐一和其后面的元素做对比,把其中最小的元素下标标记出来,直到数组末尾。

  2. 交换被标记的元素和第1个元素的位置,然后取数组中下1位元素,重复步骤1,直到取到数组最后一位元素为止。

    解释: 每一次都可以把当前循环中, 数组右边(待排序的子数组)里最小的元素取出并且交换到数组左端,(已经排序的子数组的末端), 相比冒泡排序对比元素的次数多, 交换次数较少.

func selectSort(_ arr: [Int]) -> [Int] {
    var _arr = arr, currentIndex = 0, minIndex = 0

    while currentIndex < _arr.count {

        for i in currentIndex..<_arr.count {
            if _arr[i] < _arr[minIndex] {
                minIndex = i
            }
        }
        _arr.swapAt(currentIndex, minIndex)
        currentIndex += 1
        minIndex = currentIndex
    }
    return _arr
}

插入排序

  1. 取数组a中的第2个元素x,和它左边的所有元素做比较,如果被比较的元素更大,则交换位置,直到被比较的元素比x小,或者x已经在数组的最左边。

  2. 取下一个元素y,重复步骤1,直到下标到达数组最右端为止。

    意义: 保持让数组左边的子数组是排序好的, 每1轮循环都通过逐个对比交换位置的方式, 把右边子数组中的元素放进左边子数组中.

func insertSort(_ arr: inout [Int]) {
    if arr.count <= 1 {
        return
    }

    for i in 1..<arr.count {
        var current = i
        while current >= 1 && arr[current] <= arr[current - 1] {
            arr.swapAt(current, current - 1)
            current -= 1
        }
    }
}

Shell排序

其实是插入排序的改良, 插入排序每次只交换相邻的两个元素, 而Shell排序则交换距离更远的两个 元素, 加快了元素交换的速度. 相当于

  1. 定义一个变量n, n小于数组a的宽度.
  2. 取数组中的第n个元素x, 比较x和数组第0个元素y, 如果x更小, 交换xy的位置.
  3. 取数组中的第2n个元素z, (下标从大到小)依次比较z和数组左边与z间隔为n的整数倍 的元素, 直到被比较的元素比z小, 或者z已经在数组的最左边.
  4. 取数组中的第3n个元素v, 从步骤2开始重复, 直到下标到达数组右侧不能再往上增加.
  5. 把变量n-1, 从步骤2开始重复操作, 直到n的值变为1.
func shellSort(_ arr: inout [Int]) {
    if arr.count <= 1 {
        return
    }

    var gap = arr.count / 2
    while gap >= 1 {
        var i = gap
        while i < arr.count {
            while i >= gap && arr[i] <= arr[i - gap] {
                arr.swapAt(i, i - gap)
                i -= gap
            }
            i += gap
        }
        gap -= 1
    }
}

堆排序

利用了二叉树的性质, 对数组进行转换, 建立堆完成之后, 树的根节点的值是数组中最大的值. 取出最大值后交换数组末尾, 再对根节点做heapify操作, 又可以在根节点中得到数组中最大的值. 所以依次可以得到有序数组.

  1. 对数组a, 把数组a视作为完全二叉树, 对所有节点自下往上进行heapify操作.
  2. 把根节点的值取出(就是拿出数组a的第0个元素), 添加到新数组b的前端, 然后把二叉树末端的值移到根节点(数组最后一个元素移到数组前端).
  3. 对根节点进行heapify操作, 然后重新回到步骤2, 直到数组a中的元素都添加到了b中, 此时数组b的元素就是升序.

heapify & build heap:
根据完全二叉树的性质, 对于这个二叉树数组a里的元素a[i], 它的父节点为a[(i-1)/2], 左节点为a[2n+1], 右节点为a[2n+2](如果这3个节点存在的话). heapify就是把一个完全二叉树构建成堆(heap)数据结构的一个子操作. 通过以上性质, 对比并交换每个子树的三个节点, 让父节点总是大于(或小于)它的子节点. build heap操作则是从堆底的子树开始往上对子树的根节点执行heapify操作.

func heapSort(_ arr: inout [Int]) {

    func _buildHeap(_ arr: inout [Int], length: Int? = nil) {
        var p = ((length  ?? arr.count) - 1 - 1) / 2
        while p >= 0 {
            _heapify(&arr, p, length)
            p -= 1
        }
    }

    func _heapify(_ arr: inout [Int], _ index: Int, _ length: Int? = nil) {
        let total = length ?? arr.count
        let leftIndex = 2 * index + 1
        let rightIndex = leftIndex + 1
        var maxIndex = index

        if index < 0, index >= total { return }

        if leftIndex < total && arr[maxIndex] < arr[leftIndex] {
            maxIndex = leftIndex
        }

        if rightIndex < total && arr[maxIndex] < arr[rightIndex] {
            maxIndex = rightIndex
        }

        if maxIndex != index {
            arr.swapAt(index, maxIndex)
            _heapify(&arr, maxIndex, length)
        }
    }

    _buildHeap(&arr)

    var length = arr.count

    while length != 1 {
        arr.swapAt(0, length - 1)

        length -= 1

        _heapify(&arr, 0, length)
    }
}

基数排序(LSD)

func radixSort(_ arr: inout [Int]) {

    func _length(of num: Int) -> Int {
        var result = 1
        var tmp = num / 10
        while tmp != 0 {
            result += 1
            tmp /= 10
        }
        return result
    }

    func _getValue(from num: Int, at bit: Int) -> Int {
        if bit < 1 { return 0 }
        let dec = NSDecimalNumber(decimal: pow(10, bit - 1)).intValue
        return (num / dec) % 10
    }

    func _getMaxLength(of arr: [Int]) -> Int {
        var result = 1
        for val in arr {
            let length = _length(of: val)
            result = max(result, length)
        }
        return result
    }

    var cache: [[Int]] = Array(repeating: [], count: 10)
    let maxLength = _getMaxLength(of: arr)

    for bit in 1...maxLength {
        while !arr.isEmpty {
            let val = arr.removeFirst()
            cache[_getValue(from: val, at: bit)].append(val)
        }
        for i in 0...9 {
            arr.append(contentsOf: cache[i])
            cache[i].removeAll()
        }
    }

}


搜索算法

二分搜索

对于一个有序的数组, 迭代(递归)地拿数组中间值对比目标值, 根据对比结果调整下一次取值的位置, 直到命中目标值或者取值范围大小收缩至0.

func binarySearch(_ arr: [Int], val: Int) -> Int? {
    if arr.isEmpty || val > arr.last! {
        return nil
    }
    var h = arr.count - 1
    var l = 0
    while h >= l {
        let index = (h + l) / 2
        let _val = arr[index]
        if _val == val {
            return index
        }
        if _val > val {
            h = index
        }
        if _val < val {
            l = index + 1
        }
    }

    return nil
}

二叉树搜索

利用二叉排序树的性质, 递归(或迭代)地对比节点和目标值, 根据对比结果进行返回, 或者切换到当前节点的子节点进行下一次对比. 直到得到对应的结果或者没有下一个子节点结束.
二叉搜索树
(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树

func binaryTreeSearch(_ node: TreeNode?, _ target: Int) -> TreeNode? {
    var result: TreeNode? = nil
    var current = node
    while current != nil {
        if current!.val == target {
            result = current
            break
        }
        if current!.val > target {
            current = current!.left
        }
        if current!.val < target {
            current = current!.right
        }
    }

    return result
}

源码实现: https://github.com/p36348/algorithm

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容