链表之单向链表

本文详细介绍了单向链表的概念及其与数组的区别,通过实例展示了单向链表的基本结构。接着,文章逐步讲解了如何用编程实现单向链表,包括创建节点、链表是否为空、获取链表长度、遍历、添加、插入、删除、查找等核心操作,并提供了反转链表的代码示例。
摘要由CSDN通过智能技术生成

在进行存储数据时,你请求计算机提供存储空间,计算机会给你一个存储地址。当有很多数据需要存储的时候,可以采用两种形式——数组和链表,那么这两种方式又有什么区别呢?

(一) 数组与链表

某大学教室的每排座椅11个,从左到右排号,为0-10号(类似内存空间),某宿舍6名女生来上自习,从0号开始依次入座,她们都是连续的挨在一起的。一段时间后,某女同学的男朋友来了,非得和她坐在一起,浴室这个女生之后其他女生就依次往后移动一下,空出一个位置让该男生坐下了。这对情侣太腻歪,其他女生不乐意了,于是纷纷把男朋友叫来上自习,然后就像之前一样,不断的给来的男生挪位置,很不巧,最后来的一位男生没位置了,他们就商量去更大的教室,那里的每排座椅较多。他们心里开始埋怨了,真麻烦。这就是数组的形式了。
数组的存储就是和上述的情形是一样的,添加一个新元素,其余的元素需要移动,空间不足时需要移到其他的内存中去。也许可以预留空间,但这显然不是好办法,浪费内存。

后来这6名女生再去小教室上自习的时候,如果有男朋友来的话,女生就会说:自己找教室去,然后告诉我你的教室号。这样方便多了,该女生只需要记住男朋友的教室号,就能找到男朋友所在了。这就是链表的形式了。

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

先来看看比较简单的单向列表

(二) 单向链表结构

首先来看看单向列表的基本结构:1,,2,3,4表示的是链表的节点,节点1表示的是链表的头部,4节点是链表的尾部,其指针存放的是null.。
在这里插入图片描述
每一个节点是由数据域指针域组成,如下图所示:
在这里插入图片描述
通过数据可以找到节点,通过指针域可以找到下一个节点。通过节点可获得节点的数据和指针信息。

明白了基本的结构,下面通过编程来实现单向列表结构。

(三)、编程实现

在代码实现之前,需要确定单向列表需要实现哪些功能。

功能

  1. is_empty() 链表是否为空
  2. length() 链表的长度
  3. scan() 遍历链表的节点
  4. add(item) 头部添加节点
  5. append(item) 在尾部添加节点
  6. insert(position,item) 中间插入节点
  7. remove(item) 移除指定节点
  8. searchbyvalue(item) 通过元素查找节点是否存在
  9. searchbyindex(index) 通过索引查找节点的元素
  10. reverse() 反转单向链表

编程实现

(1) 创建节点的类

添加节点之前,需要创建节点,可以采用类的形式来实现该功能。节点包括数据指针。创建的时候,指针是空的。

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

(2) 创建单向链表

先创建单向链表的类,后续一步一步的实现链表的功能。

class SingleLinkedlist(object):
	"""单向链表"""
	def __init__(self,node=None):
	#如果创建的时候具有节点,那个链表头就是该节点,如果不具有节点,那链表头就是None
		self.__head=node   

后面的函数都是包含在上述的单向链表的类中的。

(2.1) 链表是否为空

如果链表的头元素不为None时,则链表不为空,否则为空

def is_empty(self):
	"""链表是否为空"""
	return self.__head==None
(2.2) 链表的长度

这里采用的利用函数来返回链表的长度。在设计的时候,也可以将length设计成链表的属性,在增加,删除元素的时候,进行length的更新。以属性的形式更加合理。

def length(self):
	"""链表长度"""
	count=0
	"""遍历元素"""
	curnode=self.__head
	while curnode:   #当前节点为None时,遍历完毕
		count+=1 
		curnode=curnode.next
	return count
(2.3)链表的遍历

当前节点为None时,链表遍历完毕

    def scan(self):
        """遍历列表"""
        curNode = self.__head  # 当前节点
        while curNode:
            print(curNode.data, end=' ')
            curNode = curNode.next
        print()
(2.4)链表的头部添加
def add(self,item)
	node=Node(item)
	node.next=self.__head
	self.__head=node

当决定将新节点node从头部插入链表的时候,此时的self.__head代表的就是新链表的第二个节点了。因此node.next=self.__head就表示将第二个节点存入上一个节点的指针中,然后将链表的头部进行更新:self.__head=node

(2.5)链表的尾部插入
    def append(self, item):
        """尾部插入"""
        node = Node(item)  # 创建新节点
        lastnode = None  # 保存最后一个节点
        if self.is_empty():  # 链表为空
            node.next = None  # 新插入的节点为第一个节点
            self.__head = node
        else:  # 链表非空
            """遍历找到最后一个节点,节点的next属性为None时为最后一个节点"""
            cur = self.__head
            while cur.next:  # 此处的循环条件如果使用cur的话,在尾部添加的时候会出错
                cur = cur.next
                lastnode = cur
            lastnode.next = node
	

当要从尾部插入节点时,需要遍历节点,查找到最后一个节点,然后进行相应的操作。
在这里踩了一个坑:下面的代码节选自上一段代码,不同的是,循环条件是cur。

while cur: 
	cur = cur.next
	lastnode = cur
lastnode.next = node

试想一下,如果采用的是cur作为循环条件时,当遍历到最后一个节点时,节点不为空,循环条件满足,继续进行下循环,cur = cur.next,该语句使得cur变为了None,lastnode也变成了None。退出循环后,执行后面的 lastnode.next = node 就会报错,显示NoneType没有next属性。
如前面的代码,采用cur.next作为条件则不会。因为,cur.next为None时,说明遍历完成了,退出循环后,cur指向的就是最后一个节点。

(2.6) 插入节点到指定位置
    def inset(self, position, item):
        """插入节点到指定位置"""
        if position <= 0:
            # 如果插入位置在0或者以前,那么可以当做头插法来进行插入
            self.add(item)
        elif position > self.length() - 1:
            self.append(item)
        else:
            targetNode = self.__head
            count = 0
            while count < position - 1:
                count += 1
                targetNode = targetNode.next
            # 退出循环后,targetNode指向的是position-1处的节点
            node = Node(item)
            node.next = targetNode.next
            targetNode.next = node
(2.7)移除指定节点

删除节点,即是断开指定节点与其前后节点的联系,然后将指定位置节点的前一个节点与后一个节点接在一起。在查找节点时,需要记录指定节点的前一个节点,便于将断裂的链表续接起来。
需要判断指定位置的节点是否是链表的头部,如果是的话,需要将链表的头部后移一位。

    def remove(self, item):
        targetNode = self.__head
        pretarget = None  # 目标节点的前一个节点
        while targetNode:
            if targetNode.data == item:
                # 判断是否是头节点
                if targetNode == self.__head:
                    self.__head = targetNode.next  # 将头结点后移一位
                else:
                    pretarget.next = targetNode.next
                break
            else:
                pretarget = targetNode
                targetNode = pretarget.next
(2.8) 通过值查找节点是否存在

该函数返回的是bool值。

    def searchbyValue(self, item):
        targetNode = self.__head
        while targetNode:
            if targetNode.data == item:
                return True
            else:
                targetNode = targetNode.next
        return False
(2.9) 通过索引查找节点元素

节点的索引从0开始。返回的是节点的元素。

    def searchbyIndex(self,index):
        if index<=0:
            return self.__head.data
        elif index>=self.length()-1:
            curnode=self.__head
            while curnode.next:
                curnode=curnode.next
            return curnode.data
        else:
            curnode=self.__head
            while index:
                curnode=curnode.next
                index-=1
            return curnode.data

以上各函数的代码是在记录这篇博客的时候,边写边用手打的,主要是为了练习手写代码,后面将写博客之前在pycharm中调试过的代码附上,这部分代码测试没问题的。

(2.10) 反转单向链表

先上代码。这是我根据对链表的理解,写出的反转链表的代码,不知道是否简便,先记录下来再说。

    def reverse(self):
    """curNode:表示当前节点的指针"""
    """nextNode:表示curNode下一个节点的指针"""
    """tempNode:存储nextNode的下一个节点的指针,在nextNode往后移动时,赋给nextNode"""
    
        if self.__head==None:   #链表空,直接返回
            return
        else:
            curNode=self.__head   
            nextNode=curNode.next  
            curNode.next = None    #头部变尾部,next属性为None
            tempNode=None  #临时存储节点
            while nextNode:
                temp=nextNode   #临时存储节点
                tempNode=nextNode.next
                if not tempNode:  #如果下一个节点为None,说明遍历已经快完成,此时只有两个节点了。
                    nextNode.next=curNode
                    self.__head=nextNode
                    break
                else:
                    nextNode.next = curNode
                    curNode = temp
                    nextNode = tempNode
            self.scan()

(四)完成的代码

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None  # 初始设置下一节点为空


class SingleLinkList(object):
    """单链表"""

    def __init__(self, node=None):
        self.__head = node

    def is_empty(self):
        """链表是否为空"""
        return self.__head == None

    def length(self):
        """链表的长度"""
        count = 0
        # 遍历的节点
        curnode = self.__head
        while curnode:
            count += 1
            curnode = curnode.next
        return count

    def scan(self):
        """遍历列表"""
        curNode = self.__head  # 当前节点
        while curNode:
            print(curNode.data, end=' ')
            curNode = curNode.next
        print()

    def add(self, item):
        """从头插入"""
        node = Node(item)  # 创建节点
        node.next = self.__head  # 新节点指向下一个节点(在头部插入节点时,新节点的指针其实指向的就是原来链表的头部)
        self.__head = node  # 更新头结点到最新插入的节点上

    def append(self, item):
        """尾部插入"""
        node = Node(item)  # 创建新节点
        lastnode = None  # 保存最后一个节点
        if self.is_empty():  # 链表为空
            node.next = None  # 新插入的节点为第一个节点
            self.__head = node
        else:  # 链表非空
            """遍历找到最后一个节点,节点的next属性为None时为最后一个节点"""
            cur = self.__head
            while cur.next:  # 此处的循环条件如果使用cur的话,在尾部添加的时候会出错
                cur = cur.next
                lastnode = cur
            lastnode.next = node

    def inset(self, position, item):
        """插入节点到指定位置"""
        if position <= 0:
            # 如果插入位置在0或者以前,那么可以当做头插法来进行插入
            self.add(item)
        elif position > self.length() - 1:
            self.append(item)
        else:
            targetNode = self.__head
            count = 0
            while count < position - 1:
                count += 1
                targetNode = targetNode.next
            # 退出循环后,targetNode指向的是position-1处的节点
            node = Node(item)
            node.next = targetNode.next
            targetNode.next = node

    def remove(self, item):
        targetNode = self.__head
        pretarget = None  # 目标节点的前一个节点
        while targetNode:
            if targetNode.data == item:
                # 判断是否是头节点
                if targetNode == self.__head:
                    self.__head = targetNode.next  # 将头结点后移一位
                else:
                    pretarget.next = targetNode.next
                break
            else:
                pretarget = targetNode
                targetNode = pretarget.next

    def searchbyValue(self, item):
        targetNode = self.__head
        while targetNode:
            if targetNode.data == item:
                return True
            else:
                targetNode = targetNode.next
        return False

    def searchbyIndex(self,index):
        if index<=0:
            return self.__head.data
        elif index>=self.length()-1:
            curnode=self.__head
            while curnode.next:
                curnode=curnode.next
            return curnode.data
        else:
            curnode=self.__head
            while index:
                curnode=curnode.next
                index-=1
            return curnode.data

    def reverse(self):
        if self.__head==None:
            return
        else:
            curNode=self.__head
            nextNode=curNode.next
            curNode.next = None
            tempNode=None
            while nextNode:
                #temp=nextNode
                tempNode=nextNode.next
                if not tempNode:
                    nextNode.next=curNode
                    self.__head=nextNode
                    break
                else:
                    nextNode.next = curNode
                    curNode = nextNode
                    nextNode = tempNode
            self.scan()

sl = SingleLinkList()
sl.append(2)
sl.add(1)
sl.append(3)
sl.inset(4, 4)
sl.append(5)
sl.scan()  #链表遍历
sl.reverse()  #链表反转

在这里插入图片描述
从上图的结果可以看出,链表反转正确。

上述内容如有不妥之处,敬请指正,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值