基本的内部排序

  在上一篇笔记《顺序表的查找》中,我们用到了有序表,所以这里简单的搞一下有序表——对任意给定的列表按照某种顺序进行排序,使其成为一个新的有序列表。

  对列表进行排序的方法有很多,这里只简单的介绍一下插入排序(Insertion Sort)冒泡儿排序(Bubble Sort)选择排序(Selection Sort),以及快速排序(Quick Sort)

一、插入排序

  插入排序(Insertion Sort)是最简单的排序,其基本操作是,将一个元素插入到一个已经排序好了的有序列表中,从而得到一个新的、元素加1的有序列表。具体来说,就是先假设第一个数据是已经排序好了的,然后再用第二个数据和第一个数据进行对比,如果发现它比第一个数据小,就将它插入到第一个数据的前面,如果它比第一个数据大,就保持原来的位置不变;接着,再用第三个数据去和前面已经排好序的两个数据进行比较,然后将其插入到合适的位置,依此类推。

  插入排序的基本思想是不变的,但是实际应用可能有所不同。比如说,假设给定的数组numberList是不可变的,那么我们需要借助一个可变的临时数组tmpArr,先将numberList赋值给tmpArr,然后再对tmpArr执行排序。用代码表示为:

// 给定一个无序的整数列表
let numberList = [49, 38, 65, 97, 76, 13, 27, 49]


extension Array where Element: Comparable {
    
    /// 排序完成以后返回一个新的有序列表
    func insertionSort() -> Array<Element> {
        
        // 如果数组中的元素只有1个,就没必要进行排序了
        guard self.count > 1 else { return self }
        
        // 容器数组,用于对数组中的元素进行排序操作
        var tmpArr = self
        
        // 遍历数组tmpArr的下标
        for i in 0..<tmpArr.count {
            
            // 根据下标i取出元素key
            let key = tmpArr[i]
            // for循环第1趟,i = 0,key = tmpArr[i] = tmpArr[0] = 49
            // for循环第2趟,i = 1,key = tmpArr[i] = tmpArr[1] = 38
            
            
            
            // 再声明一个索引j,并对其初始化
            var j = i
            // for循环第1趟,j = i,所以j = 0
            // for循环第2趟,j = i,所以j = 1
            
            
            
            // 因为i的取值为[0, tmpArr.count)
            // 所以一定会执行下面的循环
            while j > -1 {
                
                
                
                // 如果元素key小于元素tmpArr[j]
                if key < tmpArr[j] {
                    // for循环第1趟,while循环第1趟,key = tmpArr[i] = tmpArr[0] = 49,tmpArr[j] = tmpArr[0] = 49,所以不会执行if中的代码
                    // for循环第2趟,while循环第2趟,key = tmpArr[i] = tmpArr[1] = 38,tmpArr[j] = tmpArr[1] = 38,所以不会执行if中的代码
                    // for循环第2趟,while循环第3趟,key = tmpArr[i] = tmpArr[1] = 38,tmpArr[j] = tmpArr[0] = 49,所以会执行if中的代码
                    
                    
                    
                    // 删除数组中指定的元素
                    tmpArr.remove(at: j + 1)
                    // while循环执行到第3趟之后,此时j = 0,而j + 1 = 1,所以tmpArr.remove(at: j + 1)会删除元素38
                    
                    
                    
                    // 在指定位置插入一个元素
                    tmpArr.insert(key, at: j)
                    // while循环执行到第3趟之后,此时key = 38,j = 0,所以tmpArr.insert(key, at: j)表示将元素38插入到0所在位置
                    // 插入完成之后,元素49之前所在的下标为0,现在变为1了,而元素38之前的下标为1,现在变为0了,此时tmpArr排序为:
                    // [38, 49, 65, 97, 76, 13, 27, 49]
                }
                
                
                
                // 排序完成,退出while循环
                j -= 1
                // for循环第1趟,while循环第1趟过后,j = -1,则退出while循环,再次进入for循环
                // for循环第2趟,while循环第2趟过后,j = 0,它是大于-1的,所以还会在执行一次while循环
                // for循环第2趟,while循环第3趟过后,j = -1,则退出while循环,再次进入for循环
            }
        }
        
        // 返回排序好了的数组
        return tmpArr
    }
}

// 执行排序
let results = numberList.insertionSort()  // 结果为:[13, 27, 38, 49, 49, 65, 76, 97]

  另外一种情况,假设给定的数组numberList本身就是可变的,那么就无需借助临时数组了,可以直接对其执行排序操作:

// 给定一个无序的整数列表
var numberList = [49, 38, 65, 97, 76, 13, 27, 49]

/// 插入排序
/// - 参数list:表示可变数组的引用
public func insertionSort<T: Comparable>(_ list: inout [T] ) {
    
    // 如果只有一个元素,则不需要执行排序
    if list.count <= 1 {
        return
    }
    
    // 取出除第0个元素之外所有元素的下标
    for i in 1..<list.count {
        
        // 取出元素i,并假设它已经排好序
        let x = list[i]
        
        
        // 声明一个下标j并对其初始化
        var j = i
        
        // 当下标j大于0,并且前一个元素大于后一个元素
        while j > 0 && list[j - 1] > x {
            
            // 用元素list[j - 1]覆盖元素list[j]
            list[j] = list[j - 1]
            
            // 下标j的值减1
            j -= 1
        }
        
        // 将元素x覆盖元素list[j](此时j的值变小了一位,因此相当于元素x的位置向前移动了一位)
        list[j] = x
    }
}

// 对无序数组numberList执行排序
insertionSort(&numberList)  // [13, 27, 38, 49, 49, 65, 76, 97]

二、冒泡儿排序

  冒泡儿排序(Bubble Sort)的核心思想就是交换,其具体执行过程为,先将第一个元素和第二个元素进行比较,如果为逆序(第一个元素比第二个元素大),则交换第一个元素和第二个元素的位置,接着比较第二个元素和第三个元素,如果仍然为逆序,则交换第二个元素和第三个元素的位置,以此类推直至第一趟冒泡儿完成,此时序列中最大的元素置于尾部。紧接着按照上述步骤执行第二趟、第三趟....直到序列中所有元素都按照顺序进行排列为止。用代码表示为:

// 给定一个无序数组列表
let numberList = [49, 38, 65, 97, 76, 13, 27, 49]


extension Array where Element: Comparable {
    
    /// 对任意给定的数组进行排序,然后返回一个排序好的数组
    func bubbleSort() -> Array<Element> {
        
        // 数组中元素个数大于1时才有意义
        guard self.count > 1 else { return self }
        
        // 临时数组,用于对数组中的元素进行排序操作
        var tmpArr = self
        
        // 取出数组中所有元素的下标值,从前往后遍历
        for i in 0..<self.count {
            
            // passes的取值范围[0, 7]
            let passes = (tmpArr.count - 1) - i
            
            // 取出数组中所有元素的下标值,从后往前遍历
            for j in 0..<passes {
                
                // 取出元素key
                let key = tmpArr[j]
                
                // 如果元素key大于元素tmpArr[j + 1]
                if (key > tmpArr[j + 1]) {
                    
                    /// 调用swapAt(_:_:)函数,交换集合中指定索引位置的元素值
                    ///
                    /// 这两个参数必须是集合的有效索引,因此都不能等于endIndex。
                    /// 另外,如果参数i和j的值相等,那么调用swapAt(_:_:)函数则
                    /// 没有任何意义
                    ///
                    /// - 函数参数:
                    ///   - i: 要交换的第一个值的索引
                    ///   - j: 要交换的第二个值的索引
                    tmpArr.swapAt(j, j + 1)  // swap(&tmpArr[j], &tmpArr[j + 1])
                }
            }
        }
        
        // 返回排序好的数组
        return tmpArr
    }
}


// 执行排序
let results = numberList.bubbleSort()  // [13, 27, 38, 49, 49, 65, 76, 97]

三、选择排序

  选择排序(Selection Sort)的核心思想是,每一趟排序都在n-i+1(其中,i = 1, 2, ..., n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。说得直白点,每一趟的选择排序就是从序列中未排好顺序的元素中选择一个最小的元素,然后将该元素与这些未排好序的元素的第一个元素交换位置。用代码表示为:

let numberList = [49, 38, 65, 97, 76, 13, 27, 49]


extension Array where Element: Comparable {
    
    /// 对一个无序序列进行排序,然后返回一个
    /// 已经排好顺序的数组
    func selectionSort() -> Array<Element> {
        
        guard self.count > 1 else { return self }
        
        var tmpArr = self
        
        for i in 0..<tmpArr.count {
            
            var minimum = i
            
            var j = i + 1
            
            while j < tmpArr.count {
                
                // 将最低值存储为最小值
                if tmpArr[minimum] > tmpArr[j] {
                    
                    minimum = j
                }
                
                j += 1
            }
            
            // 交换最小值
            if i != minimum {
                tmpArr.swapAt(i, minimum)  // swap(&tmpArr[i], &tmpArr[minimum])
            }
        }
        
        return tmpArr
    }
}

// 执行排序
let results: Array<Int> = numberList.selectionSort()  // [13, 27, 38, 49, 49, 65, 76, 97]

四、快速排序

  快速排序(Quick Sort)是对冒泡儿排序的一种改进,其基本思想是,在当前排序序列中任意选择一个元素作为支点(pivot),把小于或等于支点的所有元素都移动到支点的前面,把大于支点的所有元素都移动到支点后面,这样一来,以支点为分界线,将整个序列分割成前后两个子序列,并且支点刚好就处于整个排序过程的最终位置。接下来,对支点前后两个子序列重复执行上述操作,直至整个序列变得有序。用代码表示为:

var numberList = [49, 38, 65, 97, 76, 13, 27, 49]

extension Array where Element: Comparable {
    
    /// 对无序列表执行快速排序
    mutating func quickSort() -> Array<Element> {
        
        /// 对序列进行排序
        /// - 参数start:表示序列的起始位置
        /// - 参数end:表示序列的末尾位置
        func qSort(_ start: Int, _ end: Int) {
            
            if (start < end) {
                
                // 确立支点,对序列继续进行分割
                let iPivot = qPartition(start, end)
                
                // 递归调用qSort(_ : , _ : )函数,继续对序列进行分割
                qSort(start, iPivot - 1)  // 所有比支点小的
                qSort(iPivot + 1, end) }  // 所有比支点大的
        }
        
        
        // 调用qSort(_ : , _ : )函数,传入数组的开始索引和尾部索引
        // - startIndex: 表示非空数组中第一个元素的位置
        // - endIndex: 表示非空数组中最后一个元素的下一个位置
        // - 如果数组为空,startIndex和endIndex相等
        qSort(self.startIndex, self.endIndex - 1)
        
        
        // 返回排序完成的序列
        return self
    }
    
    
    /// 对序列进行分割(确立支点)
    /// - 参数startIndex:
    /// - 参数endIndex:
    /// - returns:
    mutating func qPartition(_ start: Int, _ end: Int) -> Int {
        
        // 声明一个支点,并对其初始化
        var pivotIndex = start  // 用子表的第一个元素作为支点

        for currentIndex in pivotIndex..<end {
            
            if self[currentIndex] <= self[end] {
                
                if pivotIndex != currentIndex {
                    
                    // 交换currentIndex和pivotIndex所对应的元素
                    self.swapAt(currentIndex, pivotIndex)  // Swift 3中的写法:swap(&self[currentIndex], &self[pivotIndex])
                }
                
                pivotIndex += 1
            }
        }
        
        if pivotIndex != end {
            
            self.swapAt(pivotIndex, end)  // Swift 3中的写法:swap(&self[pivotIndex], &self[end])
        }
        
        // 返回支点
        return pivotIndex
    }
}

// 执行排序
let results = numberList.quickSort()  // [13, 27, 38, 49, 49, 65, 76, 97]

五、总结

  插入排序冒泡儿排序选择排序的时间复杂度均为O( n²),而快速排序的时间复杂度是O(logn)。因此,相比而言,快速排序的效率更高。事实上,在实际开发过程中,前面三种排序使用的比较少,而快速排序在解决一般问题时则拥有大量的应用,应该掌握。

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

推荐阅读更多精彩内容

  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,225评论 0 2
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,723评论 0 15
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,149评论 0 52
  • 【香知蜜读580】2017/07/17星期一 荐书蜜友:刘芹 文:古典 ————认知,决定了你月薪3000还是30...
    自律时光阅读 2,156评论 0 2
  • 宫腔镜分诊断性操作和治疗性操作。宫腔镜适应证包括:1.异常子宫出血2宫腔占位性病变3.宫内节育器异常及宫内异物4....
    袁澜月阅读 869评论 0 1