(Python3)数据结构——07.单链表之判断链表是否有环

前言

  • 有Python基础
  • 有数据结构单链表基础,没接触过的可以看下面链接
https://blog.csdn.net/sf9898/article/details/104946291

原理和实现

问题分析

  • 有一个链表,但是我们不知道这个链表是否内部有形成一个环(形成回路),因此需要有一个函数来检测是否有环(由此可知,这个函数的返回值应是布尔型)。

在这里插入图片描述

实现

  • 先去继承一下单链表的蚂蚁花呗(属性和方法),详见链接:单链表链接 将单链表的定义拷贝下来。
  • 直接可以做测试,构建一个链表(或许有环,这一部分先写没环的),代码如下:
ll = Link()
# 先构建测试用的链表
for i in range(5):
    ll.addFront(i)
ll.addFront(100)
ll.addBack(250)
for i in range(5):
    ll.addBack(i)
# 到目前为止是一串没有环的链表
ll.travel()       # 100 4 3 2 1 0 250 0 1 2 3 4
  • 在代码中加入了几个明显与其他成员格格不入的data,这样子也更容易区分下。
  • 接下来构建有环的链表,设置一个函数getLastNode使得我们可以取到链表的最后一个节点(这个例子中就是4这个节点),然后将它的next指向250的这个节点(又要有个函数能够取到这个节点,因此有了getNode这个函数),这样就可以形成环了。效果大致如下图。

在这里插入图片描述

	# class Link的方法
    def getLastNode(self):
        cur = self.__head
        pre = None
        while cur:
            pre = cur
            cur = cur.next
        return pre

    def getNode(self, item):
        cur = self.__head
        res = None
        while cur:
            if cur.item == item:
                res = cur
                break
            else:
                cur = cur.next
        return res
ll = Link()
# 先构建测试用的链表
for i in range(5):
    ll.addFront(i)
ll.addFront(100)
ll.addBack(250)
for i in range(5):
    ll.addBack(i)
# 到目前为止是一串没有环的链表
ll.travel()       # 100 4 3 2 1 0 250 0 1 2 3 4
# 接下来构建有环的链表
# 设置一个函数getLastNode使得我们可以取到链表的最后一个节点(这个例子中就是4这个节点),
# 然后将它的next指向250的这个节点(又要有个函数能够取到这个节点,因此有了getNode这个函数),这样就可以形成环了

# print(ll.getNode(250).item)       # 这一行用来验证getNode 函数的正确性,应打印250

ll.getLastNode().next = ll.getNode(250)
ll.travel()     # 事实上现在遍历一下就可以发现事情不简单了
  • 既然是要写一个函数来判断,那建议还是将这个函数写成class Link的方法。
    def hasLoop(self):
        if self.__head is None:
            return False
        # 能到这说明头结点不是空的,但是有可能只有一个节点,也有可能 N个(N > 1)
        aNode = self.__head
        bNode = self.__head
        res = False
        # 2个结点,往前跑,一个跑的快,一个跑的慢,如果没有环,慢的结点不会追上快的
        # 如果有环,肯定有一个时刻,快的点绕回来追上了慢的点
        # 如果只有一个或两个结点,则不会进行下面的循环,直接返回False (res)
        while bNode.next and bNode.next.next:
            bNode = bNode.next.next     # 这里用B结点代表跑的快的,A是跑的慢的
            aNode = aNode.next
            if bNode == aNode:
                res = True
                break
        return res

完整代码

测试一下,完整代码:

class Node(object):
    def __init__(self, item):
        self.item = item
        self.next = None


class Link(object):
    def __init__(self):
        self.__head = None

    def isEmpty(self):
        return self.__head is None

    # 头部插入
    def addFront(self, item):
        node = Node(item)
        node.next = self.__head
        self.__head = node

    # 尾部插入
    def addBack(self, item):
        node = Node(item)
        if self.__head is None:
            self.__head = node
            return
        cur = self.__head
        pre = None
        while cur:
            pre = cur
            cur = cur.next
            # 当cur 为最后一个节点时带入,pre更新为最后一个节点,cur更新为最后一个节点的下一个节点即为空,
            # 下一次while cur 时会退出循环,此时的pre表示的就是最后一个节点,将node挂到pre的后面即可
        pre.next = node

    def size(self):
        count = 0
        cur = self.__head
        while cur:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        cur = self.__head
        while cur:
            print(cur.item, end=' ')
            cur = cur.next
        print('')

    # 删除头部节点
    def removeFront(self):
        cur = self.__head
        self.__head = self.__head.next
        cur.next = None

    # 删除尾部节点
    def removeBack(self):
        # 空节点时
        if self.__head is None:
            return
        # 只有一个节点
        if self.__head and self.__head.next is None:
            self.__head = None
            return
        # 链表节点有两个及以上
        cur = self.__head  # 当前节点
        pre = None  # 前一个节点
        cn = cur.next  # 后一个节点
        # 刚开始cur取到的是第一个节点,cn是第二个
        while cn:
            pre = cur
            cur = cur.next
            cn = cur.next
        pre.next = None

    def getLastNode(self):
        cur = self.__head
        pre = None
        while cur:
            pre = cur
            cur = cur.next
        return pre

    def getNode(self, item):
        cur = self.__head
        res = None
        while cur:
            if cur.item == item:
                res = cur
                break
            else:
                cur = cur.next
        return res

    def hasLoop(self):
        if self.__head is None:
            return False
        # 能到这说明头结点不是空的,但是有可能只有一个节点,也有可能 N个(N > 1)
        aNode = self.__head
        bNode = self.__head
        res = False
        # 2个结点,往前跑,一个跑的快,一个跑的慢,如果没有环,慢的结点不会追上快的
        # 如果有环,肯定有一个时刻,快的点绕回来追上了慢的点
        # 如果只有一个或两个结点,则不会进行下面的循环,直接返回False
        while bNode.next and bNode.next.next:
            bNode = bNode.next.next  # 这里用B结点代表跑的快的,A是跑的慢的
            aNode = aNode.next
            if bNode == aNode:
                res = True
                break
        return res


ll = Link()
# 先构建测试用的链表
for i in range(5):
    ll.addFront(i)
ll.addFront(100)
ll.addBack(250)
for i in range(5):
    ll.addBack(i)
# 到目前为止是一串没有环的链表
ll.travel()  # 100 4 3 2 1 0 250 0 1 2 3 4
# 接下来构建有环的链表
# 设置一个函数getLastNode使得我们可以取到链表的最后一个节点(这个例子中就是4这个节点),
# 然后将它的next指向250的这个节点(又要有个函数能够取到这个节点,因此有了getNode这个函数),这样就可以形成环了

# print(ll.getNode(250).item)       # 这一行用来验证getNode 函数的正确性,应打印250

ll.getLastNode().next = ll.getNode(250)		# 注释掉这行,就无法形成环了,这时下面的打印结果应是False
# ll.travel()     # 事实上现在遍历一下就可以发现事情不简单了
print(ll.hasLoop())
  • 结果

在这里插入图片描述

  • 后面发现这样其实是不严谨的,当节点数为2的时候,并且第二个node的next指向第一个,这个时候也算是一个环,在上面的代码中把这种情况当做不是环。为了实现这个要求,需要对上面的代码进行修改。
    def hasLoop(self):
        if self.__head is None:
            return False
        # 能到这说明头结点不是空的,但是有可能只有一个节点,也有可能 N个(N > 1)
        if self.__head and self.__head.next is None:
            # 只有一个头结点,不认为是环
            return False
        # 能到这里就说明,至少有2个节点了
        # 有两个及以上结点,这里第二个节点的next指向None可以说明没有环,否则这里就有可能会出现环
        if self.__head.next.next == self.__head:
            # 如果第二个节点的next指向第一个节点则认为是环,否则可能是指向空或者是下一个节点
            return True
        aNode = self.__head
        bNode = self.__head
        res = False
        # 设置2个结点,往前跑,一个跑的快,一个跑的慢,如果没有环,慢的结点不会追上快的
        # 如果有环,肯定有一个时刻,快的点绕回来追上了慢的点
        # 如果只有两个结点(也就是说第二个节点的next指向None),则不会进行下面的循环,直接返回False
        while bNode.next and bNode.next.next:
            bNode = bNode.next.next  # 这里用B结点代表跑的快的,A是跑的慢的
            aNode = aNode.next
            if bNode == aNode:
                res = True
                break
        return res
  • 完整代码:
class Node(object):
    def __init__(self, item):
        self.item = item
        self.next = None


class Link(object):
    def __init__(self):
        self.__head = None

    def isEmpty(self):
        return self.__head is None

    # 头部插入
    def addFront(self, item):
        node = Node(item)
        node.next = self.__head
        self.__head = node

    # 尾部插入
    def addBack(self, item):
        node = Node(item)
        if self.__head is None:
            self.__head = node
            return
        cur = self.__head
        pre = None
        while cur:
            pre = cur
            cur = cur.next
            # 当cur 为最后一个节点时带入,pre更新为最后一个节点,cur更新为最后一个节点的下一个节点即为空,
            # 下一次while cur 时会退出循环,此时的pre表示的就是最后一个节点,将node挂到pre的后面即可
        pre.next = node

    def size(self):
        count = 0
        cur = self.__head
        while cur:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        cur = self.__head
        while cur:
            print(cur.item, end=' ')
            cur = cur.next
        print('')

    # 删除头部节点
    def removeFront(self):
        cur = self.__head
        self.__head = self.__head.next
        cur.next = None

    # 删除尾部节点
    def removeBack(self):
        # 空节点时
        if self.__head is None:
            return
        # 只有一个节点
        if self.__head and self.__head.next is None:
            self.__head = None
            return
        # 链表节点有两个及以上
        cur = self.__head  # 当前节点
        pre = None  # 前一个节点
        cn = cur.next  # 后一个节点
        # 刚开始cur取到的是第一个节点,cn是第二个
        while cn:
            pre = cur
            cur = cur.next
            cn = cur.next
        pre.next = None

    def getLastNode(self):
        cur = self.__head
        pre = None
        while cur:
            pre = cur
            cur = cur.next
        return pre

    def getNode(self, item):
        cur = self.__head
        res = None
        while cur:
            if cur.item == item:
                res = cur
                break
            else:
                cur = cur.next
        return res

    def hasLoop(self):
        if self.__head is None:
            return False
        # 能到这说明头结点不是空的,但是有可能只有一个节点,也有可能 N个(N > 1)
        if self.__head and self.__head.next is None:
            # 只有一个头结点,不认为是环
            return False
        # 能到这里就说明,至少有2个节点了
        # 有两个及以上结点,这里第二个节点的next指向None可以说明没有环,否则这里就有可能会出现环
        if self.__head.next.next == self.__head:
            # 如果第二个节点的next指向第一个节点则认为是环,否则可能是指向空或者是下一个节点
            return True
        aNode = self.__head
        bNode = self.__head
        res = False
        # 设置2个结点,往前跑,一个跑的快,一个跑的慢,如果没有环,慢的结点不会追上快的
        # 如果有环,肯定有一个时刻,快的点绕回来追上了慢的点
        # 如果只有两个结点(也就是说第二个节点的next指向None),则不会进行下面的循环,直接返回False
        while bNode.next and bNode.next.next:
            bNode = bNode.next.next  # 这里用B结点代表跑的快的,A是跑的慢的
            aNode = aNode.next
            if bNode == aNode:
                res = True
                break
        return res


ll = Link()
# # 先构建测试用的链表
# for i in range(5):
#     ll.addFront(i)
# ll.addFront(100)
# ll.addBack(250)
# for i in range(5):
#     ll.addBack(i)
# # 到目前为止是一串没有环的链表
# ll.travel()  # 100 4 3 2 1 0 250 0 1 2 3 4
# # 接下来构建有环的链表
# # 设置一个函数getLastNode使得我们可以取到链表的最后一个节点(这个例子中就是4这个节点),
# # 然后将它的next指向250的这个节点(又要有个函数能够取到这个节点,因此有了getNode这个函数),这样就可以形成环了
#
# # print(ll.getNode(250).item)       # 这一行用来验证getNode 函数的正确性,应打印250
#
# # ll.getLastNode().next = ll.getNode(250)
# # ll.travel()     # 事实上现在遍历一下就可以发现事情不简单了
# print(ll.hasLoop())

# 一种特殊的情况,只有两个结点,但是形成环
ll.addBack(1)
ll.addBack(2)
ll.getLastNode().next = ll.getNode(1)
print(ll.hasLoop())		# 现在结果应该是True
  • 结果

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我们开始Python模拟登录imooc.com的练习。 首先,我们需要导入需要的库,包括requests和BeautifulSoup。requests库用于发送http请求,BeautifulSoup库用于解析html文本。 ```python import requests from bs4 import BeautifulSoup ``` 接下来,我们需要获取登录页面的html代码。我们可以使用requests库的get()方法来发送一个get请求,并将返回的html代码存储在一个变量中。 ```python login_url = 'https://www.imooc.com/' login_page = requests.get(login_url) login_soup = BeautifulSoup(login_page.text, 'html.parser') ``` 接下来,我们需要从登录页面中获取登录所需的信息,包括表单的action、method、以及需要提交的表单数据。我们可以使用BeautifulSoup库来解析html代码,并使用find()方法来查找表单元素。 ```python # 获取登录表单元素 login_form = login_soup.find('form', attrs={'id': 'signup-form'}) # 获取表单的action和method action_url = login_form['action'] method = login_form['method'] # 获取需要提交的表单数据 email = input('请输入邮箱:') password = input('请输入密码:') data = { 'email': email, 'password': password } ``` 接下来,我们可以使用requests库的post()方法来发送一个post请求,将表单数据提交到服务器。需要注意的是,我们需要将表单数据和请求头一起提交到服务器。 ```python # 构造请求头 headers = { 'Referer': login_url, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'} # 发送post请求 response = requests.post(action_url, data=data, headers=headers) ``` 最后,我们可以判断登录是否成功,可以通过检查返回的html代码中是否包含登录成功的提示信息来判断。 ```python # 判断登录是否成功 if '个人中心' in response.text: print('登录成功!') else: print('登录失败!') ``` 完整代码如下: ```python import requests from bs4 import BeautifulSoup # 获取登录页面html代码 login_url = 'https://www.imooc.com/' login_page = requests.get(login_url) login_soup = BeautifulSoup(login_page.text, 'html.parser') # 获取登录表单元素 login_form = login_soup.find('form', attrs={'id': 'signup-form'}) # 获取表单的action和method action_url = login_form['action'] method = login_form['method'] # 获取需要提交的表单数据 email = input('请输入邮箱:') password = input('请输入密码:') data = { 'email': email, 'password': password } # 构造请求头 headers = { 'Referer': login_url, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'} # 发送post请求 response = requests.post(action_url, data=data, headers=headers) # 判断登录是否成功 if '个人中心' in response.text: print('登录成功!') else: print('登录失败!') ``` 注意,此代码仅供学习参考。在实际应用中,请勿使用模拟登录的方式获取个人信息,以免造成不必要的风险。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值