数据结构与算法 | 链表-2:单链表的实现

1 前言

目前大家已经了解了链表的相关概念,但仅停留在理论层面是远远不够的,下面我们就实际的用Python来实现单链表,比如定义节点,以及链表相关的增删改查操作!

2 单链表可以进行哪些操作?

具体来说,有下面这些骚操作:

  • is_empty() 判断链表是否为空
  • length() 求链表长度
  • travel() 遍历整个链表
  • add(item) 链表头部添加元素
  • append(item) 链表尾部添加元素
  • insert(pos, item) 指定位置添加元素
  • remove(item) 删除节点
  • search(item) 查找节点是否存在

3 如何实现上面这些操作?

使用Python语言实现上述操作,其实用哪种语言还ok,关键是实现的思路(嗨呀,主要是就会Python…)

3.1 定义节点

思路:链表的实现首先需要定义一个节点,如何定义呢?

  • 单链表的节点有两部分,一个元素,一个链接(初始化默认指向None)
  • 传入的参数:节点的值即可
class Node(object):
    '''定义节点'''
    def __init__(self, elem):
        self.elem = elem
        self.next = None
# 创建实例 即节点
node = Node(100) # 表示传入的数值为100
node # 0x1144f9e80 表示存储的地址!
<__main__.Node at 0x1144f9e80>

3.2 判断链表是否为空

思路:

  • 如何判断?如果首节点的指向就为None,是不是说明就是空链表?对!就相当于我们上面说到的变量p指向头结点,所以如果定义变量p的指向呢?使用self.__head 让其指向我们创建的节点即可!如果为空说明为空链表,反之。
  • 先创建一个节点?需要,直接放在一开始的初始化方法中了!
class SingleLinkList(object):
    # 初始化节点!
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
        # 头结点指向我们传入的node,默认是None
    
    def is_empty(self):
        # node = Node()
        return self.__head == None
ll = SingleLinkList()
print(ll.is_empty()) # 说明时空链表
ll = SingleLinkList(23)
print(ll.is_empty()) # 说明时非空链表
True
False

上面测试ok!

3.3 求链表长度

思路:

  • 如何求长度?由于链表必须依次访问元素,所以求长度应该就是遍历,直到最后一个节点的next指向为空None!
  • 这时候要定义两个东东:一个游标,一个计数的count
  • 特殊情况:如果为空链表呢?
class SingleLinkList(object):
    # 初始化节点!
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
        # 头结点指向我们传入的node,默认是None
    
    def length(self):
        count = 0
        cur = self.__head
        while cur != None:
            count += 1
            cur = cur.next
        return count
ll = SingleLinkList()
print(ll.length()) # 空链表长度为0
0

3.4 遍历整个链表

思路:

  • 遍历的意思就是打印链表的值,如何打印?直接遍历元素,然后打印即可!
class SingleLinkList(object):
    # 初始化节点!
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
        # 头结点指向我们传入的node,默认是None
    
    def travel(self):
        
        cur = self.__head
        while cur != None:
            print(cur.elem, end = ' ') # 打印元素值
            cur = cur.next

3.5 链表头部添加元素

思路:

  • 头部添加的复杂度比尾部添加要小一些
  • 重点是指向发生变化即可!变量p指向新的节点,然后新的节点的next指向原来的首节点 即可!
  • 注意:上述两步,先执行第二步,再第一步,即不破坏原来链表的结构!
  • 特殊情况:如果为空链表呢?可以cover
  • 传入的参数:得有添加的元素值
class SingleLinkList(object):
    # 初始化节点!
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
        # 头结点指向我们传入的node,默认是None
    
    def travel(self):
        
        cur = self.__head
        while cur != None:
            print(cur.elem, end = ' ') # 打印元素值
            cur = cur.next
            
    def add(self, elem):
        # 先创建一个新节点
        node = Node(elem)
        # 改变指向
        node.next = self.__head
        self.__head = node
ll = SingleLinkList()
ll.add(23) 
ll.travel()
23 

没有问题!

3.6 链表尾部添加元素

思路:

  • 稍微复杂了一些!因为需要遍历所有的元素之后再插入!
  • 故要定义游标cur,判断是否到达了最后一个元素,然后再插入元素
  • 改变指向:最后一个元素指向新的节点,然后新的节点的next指向None
  • 传参:一个元素值!
  • 特殊情况:如果为空链表呢?【None没有next 有问题】如果只有一个元素呢?[没有问题]
class SingleLinkList(object):
    # 初始化节点!
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
        # 头结点指向我们传入的node,默认是None
    
    def append(self, elem):
        # 还是先定义节点
        node = Node(elem)
        # 然后遍历元素
        cur = self.__head
        if cur.next == None:
            cur.next = node
            node.next = None
        else:
            cur = cur.next
  • 如果为空链表呢?【None没有next 有问题】
  • 即12行有问题!得要加个判断是否有空链表!引入上面定义好的函数!
  • 类中用其余的函数:self.函数
class SingleLinkList(object):
    # 初始化节点!
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
        # 头结点指向我们传入的node,默认是None
        
    def is_empty(self):
        # node = Node()
        return self.__head == None
    
    def add(self, elem):
        # 先创建一个新节点
        node = Node(elem)
        # 改变指向
        node.next = self.__head
        self.__head = node
    
    def travel(self):
        
        cur = self.__head
        while cur != None:
            print(cur.elem) # 打印元素值
            cur = cur.next
        print('*'*10)
    
    def append(self, elem):
        # 还是先定义节点
        node = Node(elem)
        if self.is_empty(): 
            # 如果为空 直接头结点指向该插入的元素
            self.__head = node
        else:
            # 然后遍历元素
            cur = self.__head
            while cur.next != None:
                cur = cur.next
            cur.next = node   
ll = SingleLinkList()
ll.add(23)
ll.travel()
ll.append(100)
ll.travel()
23
**********
23
100
**********

测试ok

3.7 指定位置添加元素

思路:

  • 首先还是得遍历元素,然后一个游标,跑到相应位置之后然后插入元素
  • 改变指向:位置的前面元素的next指向新元素,新元素的next指向位置元素 (第二步先执行!)
  • 注意特殊情况:尾部添加+头部添加 可以做一个条件判断 用定义过的函数!
  • 传参:位置+值
class SingleLinkList(object):
    # 初始化节点!
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
        # 头结点指向我们传入的node,默认是None
        
    def is_empty(self):
        # node = Node()
        return self.__head == None
    
    def length(self):
        count = 0
        cur = self.__head
        while cur != None:
            count += 1
            cur = cur.next
        return count
    
    def add(self, elem):
        # 先创建一个新节点
        node = Node(elem)
        # 改变指向
        node.next = self.__head
        self.__head = node
    
    def travel(self):
        
        cur = self.__head
        while cur != None:
            print(cur.elem) # 打印元素值
            cur = cur.next
        print('*'*10)
    
    def append(self, elem):
        # 还是先定义节点
        node = Node(elem)
        if self.is_empty(): 
            # 如果为空 直接头结点指向该插入的元素
            self.__head = node
        else:
            # 然后遍历元素
            cur = self.__head
            while cur.next != None:
                cur = cur.next
            cur.next = node   
            
    def insert(self, pos, elem):
        # 新建一个节点
        node = Node(elem)
        if pos <= 0:
            self.add(elem)
        elif pos > (self.length()-1):
            self.append(elem)
        else:
            cur = self.__head # 初始游标
            count = 0 # 计数
            while count < pos-1 :
                count += 1
                cur = cur.next
            node.next = cur.next
            cur.next = node
ll = SingleLinkList()
ll.add(1)
ll.travel()
ll.add(2)
ll.travel()
ll.append(100)
ll.travel()
ll.insert(1,23)
ll.travel()
ll.insert(-1,1000)
ll.travel()
ll.insert(10,20000)
ll.travel()
1
**********
2
1
**********
2
1
100
**********
2
23
1
100
**********
1000
2
23
1
100
**********
1000
2
23
1
100
20000
**********

3.8 删除节点

  • 思路:先遍历找到该元素,记录位置,所以需要游标,不需要计数
  • 改变指向:位置的前面元素的next指向新元素 一步到位
  • 极端情况:删除头部元素?尾部元素?如果为空链表?
  • 思路逻辑:
    • 首先大的while 看游标cur是否为None 大的循环的终止的条件
    • 然后看每一个cur对应的元素是否和给定的相等,这时候又有一层,即看这个元素在不在首节点!
    • 2中的判断如果是相等,则break跳出循环,如果不等,继续移动游标即可!
 # 定义双游标 比较好理解!
class SingleLinkList(object):
    # 初始化节点!
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
        # 头结点指向我们传入的node,默认是None
        
    def is_empty(self):
        # node = Node()
        return self.__head == None
    
    def length(self):
        count = 0
        cur = self.__head
        while cur != None:
            count += 1
            cur = cur.next
        return count
    
    def add(self, elem):
        # 先创建一个新节点
        node = Node(elem)
        # 改变指向
        node.next = self.__head
        self.__head = node
    
    def travel(self):
        
        cur = self.__head
        while cur != None:
            print(cur.elem) # 打印元素值
            cur = cur.next
        print('*'*10)
    
    def append(self, elem):
        # 还是先定义节点
        node = Node(elem)
        if self.is_empty(): 
            # 如果为空 直接头结点指向该插入的元素
            self.__head = node
        else:
            # 然后遍历元素
            cur = self.__head
            while cur.next != None:
                cur = cur.next
            cur.next = node   
            
    def remove(self, elem):
        # 初始化
        cur = self.__head
        pre = None
        
        while cur != None:
            if cur.elem == elem:
                # 先判断此节点是否是头节点
                if cur == self.__head:
                    self.__head = cur.next
                else:
                    pre.next = cur.next
                    # 链表已经完成一条主线 不用管这个值对应下面的指向了
                break
            else:
                # 开始移动 两个游标之间差1个位置
                pre = cur
                cur = cur.next
ll = SingleLinkList()
ll.add(1)
ll.travel()
ll.add(2)
ll.travel()
ll.add(3)
ll.travel()
ll.add(4)
ll.travel()
ll.remove(3)
ll.travel()
ll.remove(100)
ll.travel()
1
**********
2
1
**********
3
2
1
**********
4
3
2
1
**********
4
2
1
**********
4
2
1
**********

3.9 查找节点是否存在

思路:

  • 遍历操作!逐一去判断,看是否匹配!引入游标
class SingleLinkList(object):
    # 初始化节点!
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
        # 头结点指向我们传入的node,默认是None
        
    def is_empty(self):
        # node = Node()
        return self.__head == None
    
    def add(self, elem):
        # 先创建一个新节点
        node = Node(elem)
        # 改变指向
        node.next = self.__head
        self.__head = node
    
    def travel(self):
        
        cur = self.__head
        while cur != None:
            print(cur.elem) # 打印元素值
            cur = cur.next
        print('*'*10)
    
    def search(self, elem):
        
        cur = self.__head
        while cur != None:
            if cur.elem == elem:
                return True
            else:
                cur = cur.next
        return False
ll = SingleLinkList()
ll.add(1)
ll.add(2)
ll.add(3)
ll.add(4)
ll.travel()
print(ll.search(3))
print(ll.search(100))
4
3
2
1
**********
True
False

完美!

4 完整封装成类

class SingleLinkList(object):
    '''单链表'''
    def __init__(self, node=None): # 设置一个默认参数
        self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
    
    def is_empty(self):
        '''is_empty() 链表是否为空'''
        return self.__head == None
    
    def length(self):
        '''length() 链表长度'''
        # 另外下面对于空链表的特殊情况符合,返回0
        # 先定义辅助的cur cur游标,用来移动遍历节点
        cur = self.__head
        # count用来计数
        count = 0
        while cur != None: # 区别于cur.next
            count += 1
            cur = cur.next
        return count
    
    def travel(self):
        '''travel() 遍历整个链表'''
        cur = self.__head # 进入了第一个结点
        while cur != None:
            # 保证每一个结点的元素都打印出来即可
            print(cur.elem, end = ' ') # 打印元素值
            cur = cur.next
        print(' ')
    
    def add(self, item):
        '''链表头部添加元素,头插法
        时间复杂度:O(1)
        '''
        # 先将加入的元素指向原有首节点,然后让头结点指向新插入的元素
        node = Node(item)
        node.next = self.__head
        self.__head = node
        # 能处理原有链表为空链表的情况
        
    def append(self, item):
        '''链表尾部添加元素,尾插法
        时间复杂度:O(n) 循环找到尾部
        '''
        # 先定义一个节点 然后循环到最后一个
        node = Node(item)
        if self.is_empty():
            # 如果为空 直接头结点指向该插入的元素
            self.__head = node
        else:
            # 非空引入一个游标cur
            cur = self.__head
            while cur.next != None: # 区别于上面,因为要在最后一个节点指向新的!
                # 这里无法直接兼容空链表 因为空链表没有.next
                cur = cur.next
            cur.next = node
            
    def insert(self, pos, item):
        '''指定位置添加元素
        参数pos:表示下标,从0开始索引
        区别于之前的cur 用pre 其实意思一致
        时间复杂度:O(n) 尾部插入 循环 也是n
        '''
        if pos <= 0:
            # 认为是头插法
            self.add(item)
        elif pos > (self.length()-1):
            # 认为是尾插法
            self.append(item)
        else: 
            pre = self.__head 
            count = 0
            while count < (pos-1):
                count += 1
                pre = pre.next
            # 当循环退出后,pre指向pos-1位置
            node = Node(item)
            node.next = pre.next # 先改变新节点 不打乱原来的关系
            pre.next = node # 即pre.next指向node所在位置
    
    def remove(self, item):
        '''删除节点
        引入两个游标 比较好理解
        先cur指向首节点,pre指向None
        然后让pre指向cur cur再移动
        '''
        # 初始化
        cur = self.__head
        pre = None
        
        while cur != None:
            if cur.elem == item:
                # 先判断此节点是否是头节点
                if cur == self.__head:
                    self.__head = cur.next
                else:
                    pre.next = cur.next
                    # 链表已经完成一条主线 不用管这个值对应下面的指向了
                break
            else:
                # 开始移动 两个游标之间差1个位置
                pre = cur
                cur = cur.next
        # 空链表不执行任何操作!可以没问题
        # 如果删除首节点 需要变化的是头结点指向下一个!
        # 如果只有一个节点,咋办呢?没问题 指向None
        # 上面是头部情况,尾部情况捏?没有问题,直接指向None
    
    def search(self, item):
        '''查找节点是否存在
        时间复杂度:O(n) 循环!
        '''
        cur = self.__head # 用来遍历链表
        while cur != None:
            if cur.elem == item:
                return True
            else:
                cur = cur.next
        return False
        # 能解决空链表的问题 如果为空 直接return false

参考

猜你喜欢

转载自blog.csdn.net/qq_27782503/article/details/93980330
今日推荐