链表及经典考题剖析

链表及经典考题剖析

链表的定义

那到底什么是链表呢?我们从最简单的单链表开始学习。单链表首先有一个数据字段,用来存储数据;其次它还有一个指针,但这个指针不同于 C++ 中的指针,这里的指针是指向下一个节点,它用来标记并链接下一个节点,然后下一个节点的 next 字段再链接到它的下一个节点,依次类推,便形成一个单链表。

除了单链表之外,常见的还有单向循环链表,可以让链表尾部节点的 next 指针指向头节点。然后是双向链表,增加了一个 pre 字段,它既可以向前指也可以向后指。我们今天就先以单链表为例,来给你演示如何定义和使用链表。

链表常见操作

首先,我们来看下链表的常见操作,链表的常见操作包括:

  • 新建

  • 尾部追加 append

  • 特定位置插入数据 insert

  • 遍历链表 travel

  • 查找数据位置 search

你可以新建一个链表后在尾部追加一个数据,追加数据之后还可以允许在特定位置插入数据,还可以通过遍历链表查找数据的位置,其中面试中最经常考的是在特定的位置插入数据,它考察了你对链表的熟悉程度。

尾部追加 append

然后,我们定义一个链表的数据结构,来演示下链表如何使用,首先我们创建一个  test_link 的测试文件,然后定义 LinkNode 的链表结构,它既有数据又有指向下一个字段的指针,所以它是一个整体的内容。

class LinkNode
    def __init__(self,data=None):
        self.data=data
        self.next=None

在初始化时,首先定义了一个数据域叫作 self.data,然后给它传入一个  None 值,然后让 self.data 等于 传入的参数 data,self.next 等于 None,next 永远指向下一个节点,在初始化时链表是空的所以让它先等于 None。

class TestLinkNode:
    def test_add(self):
        link = LinkNode(0)
        link.next = LinkNode(1)
        link.next.next = LinkNode(2)

这样一个简单的链表就创建好了,创建完链表后我们就需要往里面追加数据,并让 next 指向下一个 节点。

我们首先创建一个测试类 TestLinkNode,然后创建一个 test_add 的测试用例,再使用 link 让它等于 LinkNode 并传入一个数据 0,链表中追加的数据类型是不限的。创建完 link 后,怎么往后追加数据呢?首先它有一个 next,使用 next 等于 LinkNode 并往里面传入一个数据 1,这样就可以追加一个新的字段,link.next 指向新节点。同理,我们还可以继续往里面追加数据 3,但这种追加方式非常的麻烦,我们通常是不会这样去追加数据的。

def append(self,data=None):
    item=self
    while item.next is not None:
        item=item.next
        
    item.next=LinkNode(data)  
    return self

所以我们需要封装一个新的方法,如果这个链表已经有很多数据了,我们需要往最后一个节点的 next 追加我们想要的数据,所以我们要设计一个循环,这个循环的终止条件是 next is not None,也就是如果这个节点的下一个节点不为空,说明它下面还有一个节点,我们便使用 item 等于 item.next,直到下一个节点为空就可以得到链表的尾部节点,我们使用 item.next 等于 LinkNode 就可以在尾部追加一个数据。最后 return 一个当前的 self,这样就可以进行链式操作。

def test_append(self):
    link=LinkNode(0)
    link.append(1)
    link.append(2).append(3).append(4).append(5)

接下来,我们使用 test_append 给你演示下如果追加数据,我们创建一个链表变量 link,此时 link 就永远等于链表的头结点,然后使用 link.append 追加数据,比如这里追加 1~5。

遍历链表 travel

def travel(self):
    item=self
    while item.next is not None:
        print(item.data)
        item=item.next

写完链表之后,我们就需要检查链表对不对,这时就需要遍历链表并打印链表的全部内容。接下来我们便学习链表的第二个常见操作遍历链表,遍历链表我们可以使用 teavel,首先 travel 没有任何参数,而链表又一定是一个循环或递归结构。我们让 item 等于 self,然后进入 item.next.is not None 的循环并打印 item.data,使 item 等于 item.next。但是这个写法是有问题的,我们先使用它,不断地查找 next,打印下一个 item.data。


如果此时打印到最后一个节点的 item.next 为空,说明这个节点的下一个节点是没有的,也就是它是尾结点。但是尾结点到 is not None 就退出不再执行了,所以便会漏掉了一个尾结点,这种情况该怎样解决呢?我们将 item.next 改成 item,如果 item 不为空的话就一直执行,也就是说当前 item 的最后一个节点就可以被打印出来了,打印完也不用返回。 

我们运行看下效果,在这里追加打印了 link.travel,我们看到第一个节点是 0,第二个节点是 1,第三个节点是 2,以此类推,得到的结果和我们预期是一样的。

查找数据的位置 search

掌握了链表的追加和遍历之后,我们学习链表如何查找数据的位置,也就是给定一个数据,然后找出该数据最早出现的位置。

def search(self,data):
    item = self
    index = 0
    while item is not None:
        if item.data == data:
            return index
        else:
            index +=1
            item = item.next
    return -1

首先,查找数据也需要通过循环算法,仍然使用 while item is not None,如果不等 None,则判断 item.data 是否等于传入的 data,如果相等就返回 index,也就是我们要查找的数据的 index,否则进入 else,index 加 1,item 等于下一个节点。如果循环到最后仍没有找到,我们就简单 return 一个 -1。

class TestLinkNode:
    def setup(self):
        self.link = LinkNode(0)
        self.link.append(1)
        self.link.append(2).append(3).append(4).append(5)
        self.link.travel()

然后,我们添加一个 def setup,并创建一个公共的 link,这样可以使下面的案例更好的复用它。

def test_search(self):
    print(self.link.search(3))
    print(self.link.search(0))

最后,我们创建一个 test_search,并在 search 中把刚才的算法拷贝过来,比如我们现在 search(3) 和 search(0) 并打印结果:

发现结果中一个是 3 一个是 0,所以从结果我们可以看到,这个下标值是对的。

特定位置插入数据 insert

最后,我们再来看一个经典的考题,这个考题也是通常考察一个工程师基本功时经常被问到的一个问题,也就是给定一个单链表,在特定位置插入数据。

def insert(self,pos,data):
    item=self
    index=0
    while item is not None:
    
        index +=1
        item=item.next

我们首先创建一个 insert 函数,insert 函数通常需要给定一个下标位置,我们在这个下标位置中插入新值,同时后面的链表也会自动链接起来,我们现在看下具体怎么实现。往链表中插入数据的位置是通过 pos 变量决定的,所以插入数据仍离不开循环,我们仍然使用 while item is not None。接下来,我们看下怎样使用下标来标记特定位置,先在 while 循环中添加 index += 1 操作,表明每一次循环下标都加 1。然后是 item = item.next 完成循环。

def insert(self,pos,data):
    item=self
    index=0
    while item is not None:
        if index ==(pos - 1)
            break
        index += 1 
        item =item.next

假如我们想在原有 2 和 3 之间插入一个 2.5,此时 3 的节点需要往后移,2 的 next 指向新节点 2.5,2.5 的 next 再指向 3 即可。所以查找 index 的算法是这样的,如果 index 等于 pos,此时 pos 值需要找到的是前一个 2,想要在目前 3 的位置插入 2.5 就必须先找到 2,所以 index 等于 pos -1,这样我们就找到了它的位置了,这时 break。

一旦确定了要插入的位置我们就需要在 2 和 3 之间插入一个数据 2.5,其中新的数据的 next 指向 3,2 的 next 指向新节点。需要注意的是我们要先让新节点的 next 指向 3,再用2链接新节点就可以了,如果反过来节点 2 的 next 被赋值到新节点,那么原来节点 2 的 next 指向的节点 3 就会丢失找不到了。按照这个思路我们写这样一个方法。

def insert(self,pos,data):  
    item = self
    index = 0
    while item is not None:
        if index ==(pos - 1):
            break
        index +=1
        item = item.next
        
    node=LinkNode(data)
    node.next=item.next
    item.next=node

通过这样一次互换,就可以利用新节点 2.5 隔开 2 和 3 了。

def test_insert(self):
    self.link.insert(3,2.5)
    self.link.travel

我们来验证下效果,创建一个新的 test_insert 的测试用例,然后在 self.link 中使用 insert,比如我在 3 的位置插入一个 2.5,插完之后我们使用 link.travel 来打印链表。

我们发现插入没有问题,那么 2.5 的位置是多少呢?我们使用 self.link.search 来查找 2.5。

可以发现 2.5 的位置是 3,说明我们已经成功插入了数据,通过这个算法我们可以看到链表的基本使用。链表的核心数据域有两个,data 用来存储数据,next 用来指向下一个节点。


  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值