欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《算法图解》学习记录_chapter2(选择排序)

程序员文章站 2022-03-23 12:08:37
...

Chapter2 选择排序

本章内容:
✔️学习两种最基本的数据结构——数组和链表(ref- C语言);
✔️学习第一种排序算法——选择排序

1. 内存的工作原理

计算机内存的工作原理:计算机就像是很多抽屉的集合体,每个抽屉都有地址。

需要将数据存储到内存时,你请求计算机提供存储空间,计算机给你一个存储地址。需要存储多项数据时,有两种基本方式——数组和链表。

2. 基本数据结构:数组和链表

问题:假设你要编写一个管理待办事项的应用程序,为此需要将这些待办事项存储在内存中。应使用数组还是链表呢?

- 数组:

我们先将待办事项存储在数组中。使用数组意味着所有待办事项在内存中都是相连的(紧靠在一起的)。

《算法图解》学习记录_chapter2(选择排序)

现在假设你要添加第四个待办事项,但后面的那个抽屉放着别人的东西!在这种情况下,你需要请求计算机重新分配一块可容纳4个待办事项的内存,再将所有待办事项都移到那里。
《算法图解》学习记录_chapter2(选择排序)

在数组中添加新元素也可能很麻烦。如果没有了空间,就得移到内存的其他地方,因此添加新元素的速度会很慢。
一种解决之道是“预留座位”:即便当前只有3个待办事项,也请计算机提供10个位置,以防需要添加待办事项。

这种方式存在如下两个缺点:
・你额外请求的位置可能根本用不上,这将浪费内存。你没有使用,别人也用不了。
・待办事项超过10个后,你还得转移。

对于这种问题,可使用链表来解决。

- 链表:

如下图所示,链表中的元素可存储在内存的任何地方。
《算法图解》学习记录_chapter2(选择排序)

链表的每个元素都存储了下一个元素的地址,从而使一系列随机的内存地址串在一起。

《算法图解》学习记录_chapter2(选择排序)

在链表中添加元素很容易:只需将其放入内存,并将其地址存储到前一个元素中。使用链表时不需要移动元素,只要有足够的内存空间,就能为链表分配内存。

链表的优势在插入元素方面,那数组的优势又是什么呢?

在需要读取链表的最后一个元素时,你不能直接读取,因为你不知道它所处的地址,必须先访问元素#1,从中获取元素#2的地址,再访问元素#2并从中获取元素#3 的地址,以此类推,直到访问最后一个元素。需要同时读取所有元素时,链表的效率很高:你读取第一个元素,根据其中的地址再读取第二个元素,以此类推。但如果你需要跳跃,链表的效率真的很低。
数组则不同:你知道其中每个元素的地址。例如,假设有一个数组,它包含五个元素,起始地址为00,那么元素#5的地址是多少呢?只需执行简单的数学运算就知道元素#5的地址为04。**需要随机地读取元素时,数组的效率很高,因为可迅速找到数组的任何元素。**在链表中,元素并非靠在一起的,你无法迅速计算出第五个元素的内存地址,而必须先访问第一个元素以获取第二个元素的地址,再访问第二个元素以获取第三个元素的地址,以此类推,直到访问第五个元素。
总结一下就是:数组的读取速度快, 而插入速度慢;链表的读取速度慢,而插入速度快。

注意:
数组的元素带编号,编号从0而不是1开始。几乎所有的编程语言都从0开始对数组元素进行编号。元素的位置称为索引。因此,不说“元素20的位置为1”,而说“元素20位于索引1处”。

- 在中间插入:

问题:假设你要让待办事项按日期排列。之前,你在清单末尾添加了待办事项。但现在你要根据新增待办事项的日期将其插入到正确的位置,数组和链表哪个更好呢?

《算法图解》学习记录_chapter2(选择排序)
需要在中间插入元素时,使用链表时插入元素很简单:只需修改它前面的那个元素指向的地址。
《算法图解》学习记录_chapter2(选择排序)

而使用数组时,则必须将后面的元素都向后移。如果没有足够的空间(如
下图所示),可能还得将整个数组复制到其他地方!
《算法图解》学习记录_chapter2(选择排序)

  • Q:假设要在数组开头插入一个元素,你将如何做?这需要多长时间?如果在数组末尾插入一个元素又如何呢?想象插入时内存空间不够的情况。

- 删除:

Q:如果你要删除元素,数组和链表哪个更好呢?

如果使用链表,你只需修改前一个元素指向的地址即可。而使用数组时,删除元素后,必须将后面的元素都向前移。因此链表是更好的选择。

不同于插入,删除元素总能成功。如果内存中没有足够的空间,插入操作可能失败,但在任何情况下都能够将元素删除。

常见的数组和链表操作的运行时间:

数组 链表
读取 O(1) O(n)
插入 O(n) O(1)
删除 O(n) O(1)

(需要指出的是,仅当能够立即访问要删除的元素时,删除操作的运行时间才为O(1)。通常我 们都记录了链表的第一个元素和最后一个元素,因此删除这些元素时运行时间为O(1)。)

  • Q:那什么情况下不能够立即访问要删除的元素呢?

数组和链表哪个用得更多呢?显然要看情况。但数组用得很多,因为它支持随机访问。有两种访问方式:随机访问和顺序访问。顺序访问意味着从第一个元素开始逐个地读取元素。链表只能顺序访问:要读取链表的第十个元素,得先读取前九个元素,并沿链接找到第十个元素。随机访问意味着可直接跳到第十个元素。本书经常说数组的读取速度更快,这是因为它们支持随机访问。很多情况都要求能够随机访问,因此数组用得很多。数组和链表还被用来实现其他数据结构, 这将在后面介绍。

  • Q:练习2.5

3. 选择排序:

问题:假设你的计算机存储了很多乐曲。对于每个乐队,你都记录了其作品被播放的次数。你要将这个列表按播放次数从多到少的顺序排列,从而将你喜欢的乐队排序。该如何做呢?

算法:一种办法是遍历这个列表,找出作品播放次数最多的乐队,并将该乐队添加到一个新列表中。继续这样做,你将得到一个有序列表。
《算法图解》学习记录_chapter2(选择排序)
反复,直到得到最终结果:
《算法图解》学习记录_chapter2(选择排序)

运行时间:
要找出播放次数最多的乐队,必须检查列表中的每个元素。正如你刚才看到的,这需要的时间为O(n)。因此对于这种时间为O(n)的操作,你需要执行n次。
《算法图解》学习记录_chapter2(选择排序)
因此,需要的总时间为 O(n × n),即O(n^2)。

  • Q:随着排序的进行,每次需要检查的元素数在逐渐减少,最后一次需要检查的元素都只有一 个。既然如此,运行时间怎么还是O(n^2)呢?这个问题问得好,这与大O表示法中的常数相关。 第4章将详细解释

- Python实现:

# selection sort algorithm

def findSmallest(arr):
	smallest = arr[0]
	smallest_index = 0

	for i in range(1,len(arr)):
		if arr[i] < smallest:
			smallest = arr[i]
			smallest_index = i
	return smallest_index


def selectionSort(arr):
	newArr = []
	for i in range(len(arr)):
		smallest_index = findSmallest(arr)
		newArr.append(arr.pop(smallest_index))
	return newArr

arr = [5,3,6,2,10]

print(selectionSort(arr))

(试着编写出对乐队进行排序的代码)

@ 小结:

  • 计算机内存犹如一大堆抽屉。
  • 需要存储多个元素时,可使用数组或链表。
  • 数组的元素都在一起。
  • 链表的元素是分开的,其中每个元素都存储了下一个元素的地址。
  • 数组的读取速度很快。
  • 链表的插入和删除速度很快。
  • 在同一个数组中,所有元素的类型都必须相同(都为int、double等)。