Python面试常考点之深入浅出链表操作

在Python开发的面试中,我们经常会遇到关于链表操作的问题。链表作为一个非常经典的无序列表结构,也是一个开发工程师必须掌握的数据结构之一。在本文中,我将针对链表本身的数据结构特点,以及链表的一些常见操作给大家做一个深入浅出的讲解,希望本文的读者能够掌握链表的操作。

1. 什么是链表?

简单地说,链表是一种无序的列表。你可以把链表里面的数据看成是随机排列的,元素之间并没有固定的先后顺序。所以,既然是无序的,那么我们就无法像操作list对象一样简单地用index来去定位和操作里面的元素,如下图所示:

图1

那么针对这种情况,我们怎么才能找到里面的各个元素呢?

其实,在链表里面的每个元素其实是一种叫Node的节点对象,每个Node会保存两个信息,一个是这个节点的值value,另一个是这个节点对应的下一个节点的引用next。这样,节点与节点之间其实就形成了一种链式结构,就像一根铁链一样,一环扣一环,这也是链表这个名字的由来。有了这样的链式结构,只要给我们一个起点,我们就能沿着起点一个节点一个节点地找到所有的节点,这就是链表整个数据结构的核心。

2. 如何实现链表?

前面已经讲过,链表结构的关键是Node节点对象,所以要实现链表之前,我们需要先定义Node节点对象。每个节点中保存着当前节点的数据value,以及当前节点的下一个节点的引用next,所以我们可以这样来定义一个Node对象:

  1. class Node:
  2. def __init__(self, data):
  3. self.data = data # 保存当前节点的值
  4. self.next = None # 保存当前节点中下一个节点的引用

为了能够获取和设置Node里面的信息,我们还需要定义几个方法,代码如下:

  1. def __init__(self, data):
  2. self.data = data
  3. self.next = None
  4. # 获取node里面的数据
  5. def getData(self):
  6. return self.data
  7. # 获取下一个节点的引用
  8. def getNext(self):
  9. return self.next
  10. # 设置node里面的数据
  11. def setData(self, newdata):
  12. self.data = newdata
  13. # 设置下一个节点的引用
  14. def setNext(self, newnext):
  15. self.next = newnext

这些方法用于存取Node里面的数据,方便在链表结构里面去使用。

Node对象定义好之后,接下来我们就可以开始定义链表对象了。我们这里讲的是单向链表,所以英文成为Single Link List。定义链表时,最主要的是定义好链表的头(head),因为之前我们说过,我们只要找到了链表的头,就能够沿着这个头找到其他所有的node。所以,链表的初始定义很简单,我们只要定义一个head属性即可,代码如下:

  1. class MySingleLinkList():
  2. def __init__(self):
  3. # 定义链表的头结点
  4. self.head = None

这里大家要注意一点,链表对象本身是不包含任何节点Node对象的,相反,它只包含对链接结构中第一个节点的单个引用(self.head),这个head实际上永远会指向链表中的第一个节点。如果head为None,实际上意味着这是一个空的链表。链表LinkList对象和Node对象从定义上是独立的,互相并不包含对方。这个基本思想很重要,只要大家记住这个基本原则,那么我们就可以开始接着实现链表中的其他方法。

图2

一般来说,一个链表中应该包含的基本操作主要有以下几个:

  • 判断链表是否为空 isEmpty()
  1. def isEmpty(self):
  2. '''
  3. 判断head指向的节点是否为None,如果head指向None,说明该链表为空
  4. '''
  5. return self.head == None
  • 获取链表的长度 size()

获取链表的长度的关键是要遍历这个链表,并对节点数进行计数。遍历链表是链表操作中会被频繁使用到的基本操作,像链表节点的查询、删除等操作都会涉及到链表的遍历。我们之前说过,遍历链表时,我们必须先找到链表的head节点,从链表的头部开始不断地查找每个节点的next节点,最终直到某一个节点的next指向None,就说明遍历完成了。

  1. def size(self):
  2. current = self.head # 将链表的头节点赋值给current,代表当前节点
  3. count = 0
  4. while current != None:
  5. count += 1
  6. current = current.getNext() # 计数后,不断把下一个节点的引用赋值给当前节点,这样我们就能不断向后面的节点移动
  7. return count
  • 向链表中增加一个节点 add()

向链表中增加一个节点的时候,我们要考虑两个问题。第一个问题是,新加入的节点该加到哪里?大家可以想一想,链表是一个无序结构,其实把新的节点加到哪里对链表本身来说是无所谓的。但加入到链表的不同位置,对于我们的代码操作难度是有区别的。因为我们之前定义的链表结构始终只保持对第一个节点的引用,所以从这个角度来看,最简单的方法就是把新的节点加入到链表的头部,使新节点成为链表的第一个节点,然后以前的节点依次后移。第二个问题是,加入新节点的时候,要进行哪些操作?实际上要加入一个新的节点,需要两个步骤。首先需要把之前的head节点赋值给新节点的下一个节点,也就是新节点的next,然后再把新节点赋值给head节点,让它成为新的head(如图3所示)。

图3

  1. def add(self, val):
  2. temp = Node(val)
  3. temp.next = self.head # 将原来的开始节点设置为新开始节点的下一节点
  4. self.head = temp # 将新加入节点设置为现在的第一个节点

必须要注意temp.next = self.head和self.head=temp这两个语句的先后顺序,如果把self.head=temp写在前面,则会使得head原来指向的下一个节点的信息全部丢失,这并不是我们想要的结果(如图4所示)。

图4

  • 查找指定节点是否在链表中 search()

要实现查找算法,必然也是要遍历链表的,我们可以设置一个布尔变量作为是否查找到目标元素的标志,然后通过遍历链表中的每个元素,判断该元素的值是否等于要查找的值,如果是,则将布尔值设置为True,最后返回该布尔值即可。代码如下:

  1. def search(self, item):
  2. current = self.head
  3. found = False
  4. while current != None and not found:
  5. if current.getData() == item:
  6. found = True
  7. else:
  8. current = current.getNext()
  9. return found
  • 移除指定节点 remove()

移除指定节点也是在链表中一个常见的操作,在移除指定节点时,除了要先遍历链表找到指定元素外,还需要对这个即将被移除的节点做一些处理,以确保剩下的节点能够正常工作。在我们找到要被移除的节点时,按照之前写过的遍历方法我们知道,current应该是指向要被移除的节点。可问题是怎么才能移除掉该节点呢?为了能够移除节点,我们需要修改上一个节点中的链接,以便使其直接指向当前将被移除节点的下一个节点,使没有任何其他节点指向这个被移除的节点,以达到移除节点的目的。但这里有个问题,就是当我们循环链表到当前节点时,没法回退回去操作当前节点的前一个节点。所以,为了解决这个问题,在遍历链表时,除了需要记录当前指向的节点current外,还需要设置一个变量来记录当前节点的上一个节点previous,每次循环时,如果当前节点不是要被移除的节点,那么就将当前节点的值赋值给previous,而将下一个节点的引用赋值给当前节点,以达到向前移动的目的。同时,在找到了将被移除的节点后,我们会把found设置为true,停止遍历。

另外,在删除节点时,可能会有三种情况:

(1)被移除的节点就是链表中的开始节点,这时previous一定是None值,我们只需要将current.next赋值给head即可。

(2)被移除的节点是链表中最后的节点。

(3)被移除的节点是普通节点(即不是第一个也不是最后一个节点)。

其中第(2)(3)种情况并不需要特殊处理,直接设置previous的next为current的next即可。

  1. def remove(self, item):
  2. current = self.head
  3. previous = None
  4. found = False
  5. # 判断指定值是否存在于链表中
  6. if not self.search(item):
  7. return
  8. while not found:
  9. if current.getData() == item:
  10. found = True
  11. else:
  12. previous = current
  13. current = current.getNext()
  14. if previous == None:
  15. self.head = current.getNext()
  16. else:
  17. previous.setNext(current.getNext())
  • 获取链表中所有节点的值 getAllData()

获取链表中的所有节点的值方便我们随时查看链表中究竟有哪些值。由于链表并不像普通的list一样可以直接打印出来看,所以一般我们需要借助于遍历链表把链表中的每个节点的值取出来放到一个列表中,然后再打印这个列表,从而取得链表中所有节点值的目的。

  1. def getAllData(self): # 得到链表中所有的值
  2. data = []
  3. current = self.head
  4. while current:
  5. data.append(current.getData())
  6. current = current.getNext()
  7. return data

这些就是我们在链表中常用的操作方法及对应的代码实现,接下来我们可以尝试来操作并测试一下我们写的这些方法对不对。

  1. linkList = MySingleLinkList()
  2. for i in range(10, 50, 5):
  3. linkList.add(i)
  4. print(linkList.size()) # output: 8
  5. print(linkList.getAllData()) # output: [45, 40, 35, 30, 25, 20, 15, 10]
  6. linkList.remove(25)
  7. print(linkList.getAllData()) # output: [45, 40, 35, 30, 20, 15, 10]
  8. linkList.search(25) # output: False
  9. linkList.isEmpyt() # output: False

以上就是我们在面试中经常遇到的python算法中的链表的定义和常见操作

Python面试常考点之深入浅出链表操作的更多相关文章

  1. 10个Python面试常问的问题

    概述 Python是个非常受欢迎的编程语言,随着近些年机器学习.云计算等技术的发展,Python的职位需求越来越高.下面我收集了10个Python面试官经常问的问题,供大家参考学习. 类继承 有如下的 ...

  2. Python面试常问的10个问题

    很多人想找Python工作,面试往往在基础知识点采坑了 Python是个非常受欢迎的编程语言,随着近些年机器学习.云计算等技术的发展,Python的职位需求越来越高.下面我收集了10个Python面试 ...

  3. python基础之面试常问

    目录 python相对其他语言有什么特点? python内存管理机制,gc机制的了解,gc回收三种算法. lambda函数 高级函数 map.reduce.filter.sorted等. 简述六种基本 ...

  4. Python链表操作(实现)

    Python链表操作 在Python开发的面试中,我们经常会遇到关于链表操作的问题.链表作为一个非常经典的无序列表结构,也是一个开发工程师必须掌握的数据结构之一.在本文中,我将针对链表本身的数据结构特 ...

  5. python面试总结2(函数常考题和异常处理)

    python函数常考题 可变类型为参数 不能类型为参数 python如何传递参数 传递值还是引用呢?都不是.唯一支持的参数传递是共享穿参 Call by Object(Call by Object R ...

  6. Python面试知识点小结

    一.Python基础 1.Python语言特性: 动态型(运行期确定类型,静态型是编译型确定类型),强类型(不发生隐式转换,弱类型,如PHP,JavaScript就会发生隐患式转换) 2.Python ...

  7. BAT面试常的问题和最佳答案

    原标题:BAT面试常的问题和最佳答案 技术面试 1.servlet执行流程 客户端发出http请求,web服务器将请求转发到servlet容器,servlet容器解析url并根据web.xml找到相对 ...

  8. python面试总结4(算法与内置数据结构)

    算法与内置数据结构 常用算法和数据结构 sorted dict/list/set/tuple 分析时间/空间复杂度 实现常见数据结构和算法 数据结构/算法 语言内置 内置库 线性结构 list(列表) ...

  9. PHP面试常考内容之Memcache和Redis(2)

    你好,是我琉忆.继周一(2019.2-18)发布的"PHP面试常考内容之Memcache和Redis(1)"后,这是第二篇,感谢你的支持和阅读.本周(2019.2-18至2-22) ...

随机推荐

  1. LK光流算法公式详解

    由于工程需要用到 Lucas-Kanade 光流,在此进行一下简单整理(后续还会陆续整理关于KCF,PCA,SVM,最小二乘.岭回归.核函数.dpm等等): 光流,简单说也就是画面移动过程中,图像上每 ...

  2. html,css,js实现的一个钟表

    效果如图: 实现代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...

  3. Go语言编程中字符串切割方法小结

    1.func Fields(s string) []string,这个函数的作用是按照1:n个空格来分割字符串最后返回的是[]string的切片 复制代码代码如下: import ( "fm ...

  4. 微服务RESTful 接口设计规范

    1.RESTful发展背景及简介 网络应用程序,分为前端和后端两个部分.当前的发展趋势,就是前端设备层出不穷(手机.平板.桌面电脑.其他专用设备......).因此,必须有一种统一的机制,方便不同的前 ...

  5. Python 串口通讯

    摘要: pyserial module: https://github.com/tbusf/pyserial Python使用pyserial进行串口通信:http://blog.csdn.net/l ...

  6. sqlserver创建链接服务器连接sqlserver脚本

    示例: EXEC sp_addlinkedserver @server='MyLinkServer', --链接服务器别名 @srvproduct='', @provider='SQLOLEDB', ...

  7. 经济-AMA:百科

    ylbtech-经济-AMA:百科 美国市场营销协会(American Marketing Association,简称AMA)于1937年由市场营销企业界及学术界具有远见卓识的人士发起成立.如今,该 ...

  8. C#WinForm程序显示控制台窗口Console

    启动一个WINFORM项目,使用一些API函数将控制台显示出来: AllocConsole 和 FreeConsole. 本程序只在DEBUG模式下显示控制台 [DllImport("ker ...

  9. linux简单命令8---软件包安装

    1:使用yum安装,它不能包查询和包校验.它安装的是RPM格式文件.没有yum文件 ---------------------------------------------------------- ...

  10. idea忽略隐藏文件、文件夹的设置操作

    左上角setting 如果要忽略文件夹,则直接填写文件夹名字即可,例如:要忽略target文件夹[建议:尽量不要把target忽略,因为可能编译出问题排查,还需要查看target文件夹中的编译结果] ...