算法2——链表,数组,和选择排序

首先我们来认识一下计算机的内存工作原理:

计算机的内存就像是我们平时存放东西的储物柜一样,我们把背包之类的物品放入柜子里,每个柜子有一个专属的编号,我们记住这个编号,然后根据编号就可以找到柜子拿回属于我们的物体。

在计算机里,就有很多这种柜子,叫内存单元,每个柜子的编号就是地址。那么如何将数据存放到相应的地址内呢,有两种基本方式,链表和数组,接下来我们分别介绍他们并比较其优缺点。

什么是数组(array)?

假如我们有一个四乘四的格子,即16个小格子,相当于计算机的内存,从左到右,从上到小分别编号0-15。每个格子代表一个地址。此时,我有一个包含四个数的数组a[4],我们想把它放到这16个格子当中,如果16格子全部为空,我可以放到任意的位置上,比如0-3或者4-7,这个编号就代表数组所存放的地址。但是这四个数必须是相邻的,不能有跳跃。这就是数组存储在计算机内存的方式。

但是,如果这16个格子不全为空,编号为3,7,11,15的格子里本身存有数据,我们不能占用,那我们还可以将数组a[4]存入这16个格子中吗?答案是不能,因为我们发现已经没有相邻的4个格子了,连在一起的格子最多只有3个,那这时我们要怎么存储数据呢,这就引出了链表。

什么是链表(linked list)?

链表也是数据存储在计算机内存里的一种方式,不同于数组的是,当我们存储一个数的时候,我们还会记下下一个数所在的地址。这有什么好处呢,回到上面的例子,现在编号3,7,11,15的格子已经被占用了,假设我们将前三个数存储在0-2编号的格子里,存储第一个数时会包括第二个数的地址,第二个数会包括第三个数的地址,存储放在编号2的格子里的第三个数还包括第四个数的地址。假设我们将第四个数存在编号4的格子里(因为编号3的格子已经被占用),当我们读到第三个数时,由于有了第四个数的地址,我们可以直接跳到编号4的格子中读取里面的数字。而数组由于不存储下一个数据所在的地址,所以我们必须将他们相邻排列,前一个地址加一就是后一个数的地址所在。

数组和链表的比较

现在我们知道了数组和链表的工作原理,那么谁更好呢。当然世界上没有绝对的优势与劣势,链表和数组也有各自擅长的情形,接下来我们考虑几个操作,读取,插入,删除。

读取 (数组 优于 链表)

假设我们想从数组里读取第四个元素,由于数组是相邻排列,我们只需要将头元素的地址加4就可以直接访问到第四个元素,只是简单的加法运算即可得到,相当于直接查表,所以运行时间是O(1)。

假设我们想从链表里读取第四个元素,链表不一定是相邻排列,我们必须从第一个查到第二个的地址,从第二个再查第三个,从第三个再查第四个,无论我们查第几个元素,都要遍历一遍这个元素前面的所有元素才可以找到我们想要的,所以运行时间是O(n)。

插入(链表 优于 数组)

假设我们想在包含四个数据的数组再插入一个数据,则数组的大小发生了变化,首先我们需要确保加了一个数据之后,内存中还有五个相邻的格子,其次,如果我们把新数据插在头一个,那么所有后面的数都要相应往后挪一位,每一个数据本身的地址都发生了变化,所以运行时间是O(n)。

假设我们想在包含四个数据的链表里插入一个新数据,假如插在第二位,我们可以将新数据放入到任意一个空的格子里,然后让第一个数记下这个新数据格子的地址,这样第一个数之后我们就查询到第二个数,也就是新数据,我们再让新数据存储原本第二个数的地址,这样从新数可以查到旧的第二位数,这样新数据就被加进了原本的第一位数和第二位数之间。那么后面的数据呢?我们完全不需要动,对吧。所以运行时间是O(1)。

删除(链表 优于 数组)

删除的操作和插入的操作很相像。对于数组而言,删除一个元素也是改变了数组本身的大小,如果删除第一个元素,后面所有元素都要向前挪一位,所有元素地址都要变,运行时间是O(n)。

而对于链表,一样原理,链表有一个叫头结点(head)的东西,顾名思义它是开始,它存储着第一个数的地址,如果我们要删除第一个元素的时候,只需要将头结点里的地址改为原本第二个数的地址,这样从头节点我们就直接跳到了原来的第二个数上,原本的第一个数自然而然就被删除掉了,而后面的数字则全部不需要动,所以运行时间是O(1)。

我想下面这幅图可以更好的帮助理解关于链表的操作。绿色箭头代表指向下一个元素的地址,紫色箭头是跨过E直接指向了A的地址,也就是相当于删除元素E的操作了。链表操作比较难理解,我会在之后放入更为详细的讲解。


选择排序

在算法1里我们讲到了二分查找,每次先找中间的然后比较,但这必须是有序的数列,如果是无序的数列呢?这里我们将一种比较简单直观,效率可能较低,但很基本的排序方法,选择排序。

假如我们有5个学生,他们是数学成绩如下,很明显这是无序的,我们想要按照成绩从高到低的顺序进行排列,要如何做呢?

  ->  ->...

最简单的方法就是,先从头到尾看一遍找到最高的挑出来,然后再从头到尾查一遍挑出第二高,...,然后第三高,第四高,直至最后。很简单对吧,这就是选择排序。很简单易懂,当然花费时间也很高,来让我们算算它的运行时间。

假设有n个同学需要排序,第一次需要查看n个同学找到最高,第二次只需要查看n-1个同学,然后n-2个同学,直至查到最后一名,所以总时间t = n + (n-1) +(n-2) + ... + 1 = n(n+1)/2 = (n^2+n)/2 ,而时间复杂度我们一般忽略常数项取最高次的多项,所以这里选择排序的运行时间是O(n^2).

代码实现

def findSmallest(lst):
  smallest = lst[0]
  smallest_index = 0
  for i in range(len(lst)):
    print('com',lst[i],smallest)
    if lst[i] < smallest:
      smallest = lst[i]
      smallest_index = i
  return smallest_index
  
def selectionSort(lst):
  new_list = []
  while len(lst) > 0:
    smallest_index = findSmallest(lst)
    new_list.append(lst.pop(smallest_index))
    print(lst)
  return new_list

mylst = [1,2,3,4,1,2]
mynew_lst = []
mynew_lst = selectionSort(mylst)
print(mynew_lst)
Reference: 理解来源于《算法图解》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值