【算法日积月累】1-选择排序

选择排序-1

“算法”的入门,我们从“排序算法”开始,希望通过“排序算法”这一部分的学习,能够让我们认识到“算法”的威力。“算法”不仅仅只存在与我们的面试中(那时只是因为我不知道“算法”而已),“算法”无处不在,“算法”很有用。

下面是一些说明:1、会直接使用“空间复杂度”和“时间复杂度”的概念,不妨先有个印象,实在纠结的话,可以去翻翻书,“空间复杂度”和“时间复杂度”最多的应用就在于比较不同算法的优劣;

2、“排序算法”这一章节为了方便说明,使用的例子都是以“整数数组”为例,并且是“升序排序”,学习过 Java 语言的朋友就知道,待排序的也可以是对象,只要实现了相关的接口,实现了相应的比较规则,就可以进行排序。

选择“选择排序”作为算法入门的开篇。理由如下:

1、“选择排序”算法的思想十分简单,非常接近我们的思维方式:先找最小的数、再找第 2 小的数,依次类推,最后剩下的就是数组中最大的元素;

2、“选择排序”的实现也很简单。

“选择排序”的思想:减而治之

“选择排序”的思想说起来是很简单的,就是把一个问题划分成两个部分:

1、其中一个部分是一个比原始问题小很多的,很容易解决的问题;

2、另一个部分是原始问题的一个子问题,与原始问题的区别就是,这个子问题比原问题看起来少了那么一些。

这就好比“愚公移山”,“移山”这个庞大的工程,在愚公看来,他每次一凿一斧头,虽然看起来微不足道,但是山就那么大,山上的石头不会越挖越多,每次挖一点,子子孙孙挖下去,山总会被挖完。“减而治之”就是这么一个朴素的思想。

应用于“选择排序”,即我们每一次把数组中的元素看一遍,找到一个最小的数,这个数就一定是排好序后放在第 1 个的数,那么我们在剩下的数里就继续找最小的数,一直这样做下去,每一轮看的数都比前一轮看的数的数量少 1,未排定的数会越来越少,直至没有。

这是一个很重要的思想,算法中的使用得“递归”正是“减而治之”的体现。

通过具体例子理解“选择排序”的思想

思想:不断地选择剩余元素之中的最小者。

选择排序-2

“选择排序”算法的特点

1、每一轮交换都能排定一个元素,交换的总次数是固定的;

说明:“交换的总次数”等于“元素的总数 - 1”,因此算法的时间复杂度取决于比较的次数;

2、运行时间和输入无关,即:一个“已经有序”的数组、一个所有的元素都相等的数组、一个元素随机排列的数组所用的排序时间是一样的;

说明:后续我们会编写一些测试用例,比较不同的算法在不同的测试用例上的运行时间。这些测试用例中,就有以下 3 种。

(1)一个“已经有序”的数组:例如:[4, 5, 6, 8, 9, 10],以后我们学习的排序算法中,就有一种算法名叫“插入排序”就能检测出数组是不是有序的,极端情况下,“插入排序”算法看一遍数组中的元素,就知道数组已经有序了,后续就什么都不用做了。而“选择排序”得一遍又一遍看数组的元素好几遍,“几乎是”有多少个数,就会看数组多少遍,每一遍选出当前没有排定元素中的最小者;

(2)一个所有的元素都相等的数组,例如:[6, 6, 6, 6, 6, 6]

(3)一个元素随机排列的数组,就是我们一般意义下,杂乱无序的数组,例如:[8, 18, 10, 6, 5, 4, 20]

3、数据移动是最少的。

这点应该说是“选择排序”的优点了,如果我们的排序任务对交换操作非常敏感,不妨考虑“选择排序”。

例如:我们待排序的是码头上的集装箱,交换集装箱的成本是很高的,此时“选择排序”就是最好的选择。

小贴士:这一部分内容不需要记住,等到后面接触了“插入排序”、“归并排序”、“快速排序”等其它排序算法以后,再与“选择排序”进行比较,就不难理解了。

“选择排序”算法实现

Python 代码1:

def select_sort(nums):
    """
    选择排序,记录最小元素的索引,最后才交换位置
    :param nums:
    :return:
    """
    l = len(nums)
    for i in range(l):
        min_index = i
        for j in range(i + 1, l):
            if nums[j] < nums[min_index]:
                min_index = j
        swap(nums, i, min_index)

def swap(nums, idx1, idx2):
    if idx1 == idx2:
        return
    temp = nums[idx1]
    nums[idx1] = nums[idx2]
    nums[idx2] = temp

说明:交换两个数组中的元素,在 Python 中有更简单的写法,这是 Python 的语法糖,其它语言中是没有的。

Python 代码2:主体部分和“Python 实现1”是一样的。

def select_sort(nums):
    """
    选择排序,记录最小元素的索引,最后才交换位置
    :param nums:
    :return:
    """
    l = len(nums)
    for i in range(l):
        min_index = i
        for j in range(i + 1, l):
            if nums[j] < nums[min_index]:
                min_index = j
        nums[i], nums[min_index] = nums[min_index], nums[i]

这就是“选择排序”算法。如果你看到自己编写的程序不正确,可以在程序中增加打印输出,帮助你调试程序:

选择排序-3

时间复杂度与空间复杂度

时间复杂度:O(n^2)

分析:第 1 轮要看 n 个元素;第 2 轮要看 n-1 个元素;第 3 轮要看 n-2 个元素;……第 n 轮要看 1 个元素;用等差数列前 n 项和的通项公式对它们求和。不过其实你也不用计算它,“时间复杂度”的计算我们只看次数最高的,所以“选择排序”是平方时间复杂度。

空间复杂度:O(1)

分析:我们在交换两个数组元素位置的时候,使用了 1 个辅助的空间。

热身练习

是不是觉得很简单,后面难度会一点一点加上来。此时,我们不妨做一些热身的练习,我们后面会用到。这些练习只是减轻一点我们后面编写测试用例的工作量,自己设计函数参数就好。

练习1:编写三个函数,分别生成上文中提到的 3 种类型的数组,要求能够自定义生成数组的大小,这样我们以后编写测试用例的时候,就可以使用这些函数了。

练习2:编写一个函数,判断一个数组是否是升序排序。这个函数用于判断我们的算法是否正确。

补充知识

以下补充的知识是针对零基础的朋友们的,因为我也是零基础过来的,觉得这些东西可以说一下。

1、交换两个变量的值

交换两个变量的值,在排序中是常见的操作,并且也是程式化的,特别好记。先给出 Java 的写法,再给出 Python 的写法,最后给出“不是人的写法”。

Java 代码:

int temp = a;
a = b;
b = temp;

说明:这段代码其实很好理解,要交换两个变量的值,给要让变量 a 把位置让出来,即 int temp = a,然后把另一个变量 b 的值复制给 a,即 a = b,最后把之前 a 放在 temp 里的值赋给 b。这么说比较拗口,但其实我每次写这段代码的时候,都不用想这个过程的。因为这段代码有规律可循:首先引入一个辅助变量 temp,这是必要的,然后就开始“首尾相接”了,你们看一下,是不是这个特点,最后接回 temp,记住这个规律就可以了。在 Python 中是这样写的:

Python 代码1:

temp = a
a = b
b = temp

不过,Python 是一门神奇的编程语言,它提供了语法糖。

使用 Python 代码语法糖交换两个变量的值

Python 代码:

a, b = b, a

就可以交换两个变量的值,不妨动手验证一下:

选择排序-4

是不是很酷,Python 的写法有的时候更像伪代码,更符合人的思维,但我没有说 Python 更好的意思。其实 Python 解释器在后台也是引入了辅助变量完成两个变量的交换。其实,交换两个变量的值,有更高效的做法,下面给出两个交换变量的代码,这两种方法都不用引入辅助变量,相信聪明的你一定不难理解。

基于加减法交换两个变量的值

选择排序-5

基于异或运算交换两个变量的值

选择排序-6

这里利用到了异或运算的特点:异或运算可以理解成不进位的加法。那么一个数两次异或同一个数,就和原来的数相等。上面基于异或运算交换两个变量的值就利用这个性质。如果你还不熟悉异或运算,不妨查阅一些资料。

2、Java 和 Python 语言中比较器的实现

前面我们说到了,我们为了突出排序算法的思想,将所有的例子仅限在数组排序中。事实上 Java 和 Python 这些面向对象的编程语言都支持对象的排序,只要给它们定义相应的比较规则即可。有两种方式,Python 和 Java 都是支持的:

(1)为对象添加用于比较的函数

  • 在 Python 中,有一个魔法函数,实现它即可:

Python 代码:

def __cmp__(self, other):
    pass

定义这个魔法函数,就可以使用对象集合进行排序了。

  • 在 Java 中,实现 Comparable 接口中的 compareTo 方法。

如果你觉得给对象添加用于比较的函数,这种做法的侵入性比较强(因为修改了类),那么你可以在排序的方法中,传入比较规则。

(2)在排序的方法中,传入比较规则

  • 在 Python 中,比较规则可以通过 lambda 表达式传入:

  • 在 Java 中,可以传入一个实现了 Comparator 接口的对象。

3、注意到我们每一轮都要在剩下没有排定的数中,找到一个最小的数,我们都会把剩下的数看一遍,以后我们会接触到一个很常用的数据结构,叫“最小堆”,“最小堆”可以很快地告诉我们一个集合中的最小者,于是“选择排序” + “堆”就成为了“堆排序”。

本文源代码

Python:代码文件夹,Java:代码文件

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

推荐阅读更多精彩内容