单链表

Posted 在这里, 意淫和实干都值得尊重

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单链表相关的知识,希望对你有一定的参考价值。

写给自己看的笔记, 很多坑

1. 创建一个节点类

class Node(object):
    '''节点类'''

    def __init__(self, elem):
        self.elem = elem
        self.next = None

类内部的 __init__函数,是初始化函数,只要是类的实例对象,就会自动执行__init__函数内部的代码块,也就是说,类的实例对象都会有__init__函数的属性

ex:

__node = Node()__

__node__就是Node类的__实例__对象
该node具有__elem__和__next__属性

[========]

2. 创建一个链表类

class SingleLinkList():

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

1. 定义实例的首节点属性,该属性为实例的私有属性

self__head = node

表示self的head属性是self私有的

2. 链表可以定义为空, 但是空链表内必须存在首节点,node=None,默认的首节点指向None,但以传递进来的首节点node为准

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

node在函数的参数元祖内, 如果不传递node,默认的node的值为None

ex1:

linklist = SingleLinkList()
  1. 创建一个SingleLinkList类的实例对象
  1. 该对象没有给SingleLinkList类传递node节点
  2. 所以该实例的首节点self.__head = None

ex2:

linklist = SingleLinkList(110)
  1. 创建一个SingleLinkList类的实例对象
  1. 该实例对象的首节点self.__head = 110

[========]

3.给SingleLinkList类,添加内置方法

1. 所有的内置方法

def __init__(self, node=None):
    """SingleLinkList类初始化,私有的,首节点方法"""
    self.__head = node

def is_empty(self):
    """链表是否为空"""

def length(self):
    """链表的长度"""

def tarvel(self):
    """遍历整个链表"""

def add(self, item):
    """链表头部添加元素, 首插法"""

def append(self, item):
    """链表尾部添加元素, 尾插法"""

def insert(self, pos, item):
    """指定位置插入元素"""

def remove(self, item):
    """删除节点"""

def search(self, item):
    """查找节点是否存在"""

[========]

4. 创建实例对象,及调用内部方法

ex:

linklist= SingleLinkList()

创建一个实例对象

linklist.is_empty()

调用该实例的is_empty方法

[========]

5. 详解该链表的每一个方法

1. is_empty()

def is_empty(self):
    return self.__head == None

__作用:__判断该链表是否为空
步骤详解:

  1. self.__head是该链表的首节点,
  2. 如果self._head==None,就表示该链表首节点为None,即该链表没有节点
  3. return返回的是布尔值,如果为空返回True,否则返回False

[========]

2. length()

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

__作用:__查看该链表的长度
步骤详解:

cur = self.__head

创建游标,起始是指向链表的首节点

count = 0

创建count,起始值为0,进行数数

while cur != None:

遍历所有元素
1.判断条件:如果游标(cur)指向的不是None,就一直循环.
2.最后一个节点的next域指向的就是None
3.当cur == None的时候,就是已经遍历完所有的节点

count += 1

如果游标cur所指向的节点不是None,则执行count加一操作

cur = cur.next

将游标cur进行后移一位

return count

返回count的数值

注意点:
1. 如果该链表为空怎么办?

  1. 当链表为空的时候,游标cur的指向None;cur = self.__head
  2. count=0已经设置好了;
  3. 链表为空,跳过循环体;while cur != None:
  4. return count 返回count的值0

2. cur == None 与 cur.next == None的判断条件的问题

  1. cur == None表示当前游标指向的节点为None
  2. cur.next == None表示当前游标指向的节点的后继节点为None
  3. __后继结点:__表示当前节点的后一个节点

[========]

3. tarvel()

def tarvel(self):
        cur = self.__head
        while cur != None:
            print(cur.elem, end=" ")
            cur = cur.next
        print("")

__作用:__遍历所有节点[遍历整个链表]
步骤详解:

  1. cur = self.__head
    创建游标,游标的起始指向是链表的首节点
  2. while cur != None:
    遍历所有节点,如果游标cur指向的不是None,执行此循环
  3. print(cur.elem, end=" ")
    在循环体内,如果cur所指向节点不是None,则打印当前节点的elem的值‘‘‘
  4. cur = cur.next
    将游标进行后移一位
  5. print("")
    1.当连续调用此方法的时候,终端输出的内容为连续
    2.如果设置print(""),每次打印完毕都会换行

注意点
1. 如果该链表为空怎么办?

  1. 当链表为空的时候,游标cur的指向None;cur = self.__head
  2. 链表为空,跳过循环体;while cur != None:
  3. 打印print("")

2. cur == None 与 cur.next == None的判断条件的问题

  1. cur == None表示当前游标指向的节点为None
  2. cur.next == None表示当前游标指向的节点的后继节点为None
  3. 如果判断条件为cur.next == None,则会丢掉最后一个元素
  4. __后继结点:__表示当前节点的后一个节点

[========]

4. add():

def add(self, item):
        node = Node(item)
        node.next = self.__head
        self.__head = node

__作用:__链表头部添加元素, 首插
步骤详解:

  1. node = Node(item)
    创建一个Node(item)的新节点,要插入到链表的首部, 也就是作为当前链表的首节点
  2. 假设链表本身是有很多节点的
    如果直接将链表的self.__head首节点指向新节点,那么就会丢失原有的所有节点
  3. 先将新节点的next域指向原先的首节点node.next = self.__head
  4. 然后再将self.__head指向现在的新节点self.__head = node

注意点:

  1. 当链表为空的时候, 默认的首节点是指向None的
    slef.__head = None
  2. 以上三行代码完全可以应对链表为空的情况

[========]

5. append()

def append(self, item):
    node = Node(item)
    if self.is_empty():
        self.__head = node
    else:
        cur = self.__head
        while cur.next != None:
            cur = cur.next
        cur.next = node

__作用:__链接尾部添加节点,尾追加

步骤详解:

  1. node = Node(item)
    创建一个新节点,该节点的elem为item
  2. if self.is_empty():
    判断该链表是否为空
    1.self.__head = node
    将节点node作为链表的首节点
  3. cur = self.__head
    利用首节点来创建游标
  4. while cur.next != None:
    cur = cur.next
    如果游标指向的节点的next域不指向None,则进行后移操作
  5. cur.next = node
    1.循环结束
    2.游标指向最后一个节点(最后一个节点的next域指向None)
    3.将最后一个节点的next域指向node(最后一个节点的next域指向新创建的node节点)

[========]

6. insert()

def insert(self, pos, item):
        if pos <= 0:
            self.add(item)
        elif pos > (self.length()-1):
            self.append(item)
        else:
            node = Node(item)
            pro = self.__head
            count = 0
            while count < (pos-1):
                count += 1
                pro = pro.next
            node.next = pro.next
            pro.next = node

__作用:__指定位置插入节点

步骤详解:

  1. if pos <= 0:
    self.add(item)
    如果给定的下标小于等于0,直接在链表首部添加元素
  2. elif pos > (self.length()-1):
    self.append(item)
    如果给定的下标大于链表的尾部下标,直接进行尾部添加元素

3.else:
进入插入节点的代码体
node = Node(item)
创建一个新节点
pro = self.__head
创建游标
count = 0
我们计数的方式来控制游标
while count < (pos-1):
当下标到pos前一位元素的时候,结束循环
count += 1
pro = pro.next
游标向后移动一位
node.next = pro.next
新节点的next域指向pos上一个位置处的next域的指向
pro.next = node
将pos上一个位置处的next域指向新节点
注意: 如果给定的下标等于尾部下标,就等于是在尾部下标处添加元素,原有的尾部元素要向后移动一位, 我们把他归类到插入节点的代码中

[========]

7. search()

def search(self, item):
    cur = self.__head
    while cur != None:
        if cur.elem == item:
            return True
        else:
            cur = cur.next
    return False

__作用:__查找节点是否存在

  1. cur = self.__head
    创建游标,起始指向首节点
  2. while cur != None:
    遍历所有节点,也就是说只要游标指向的不是None,此循环一直执行
    1.if cur.elem == item:
    如果节点的elem等于item,返回True
    return True
    2.else:
    cur = cur.next
    游标向后移动一位
  3. return False
    循环结束后,也就是遍历了所有节点之后没有找到item元素,返回False

[========]

8. remove()

def remove(self, item):
        cur = self.__head
        pro = None
        while cur != None:
            if cur.elem == item:
                if cur == self.__head:
                    self.__head = cur.next
                else:
                    pro.next = cur.next
                break
            else:
                pro = cur
                cur = cur.next

作用:_删除节点

  1. 创建两个游标,__cur__和pro
    cur = self.__head
    游标cur,起始指向链表的首节点.
    作用:指向当前节点
    pro = None
    游标Pro,起始指向None
    作用:指向cur游标的上一个节点
  2. 遍历所有元素,条件为cur不等于None
    while cur != None:
    2.1. if cur.elem == item:
    如果cur的elem等于item(如果游标所指向的节点的元素等于要查找的元素), 执行删除操作
    2.2.if cur == self.__head:
    先判断item的节点是否为首节点
    2.3self.__head = cur.next
    让首节点指向cur的后继节点

3.else:
pro.next = cur.next
item如果不是首节点的elem,
直接将当前节点的__前一个节点的next域__指向当前节点的__后继节点的next域__
4.break
pro = cur
pro为cur的上一个元素的游标
cur = cur.next
注意: pro与cur的位置不能颠倒
注意点:

  1. 先判断该链表是否为空
    如果是空链表不执行任何操作
  2. 如果只有一个节点
    以上代码完全可以完成任务(即:self.__head == cur.next)
  3. 首节点
    如果是首节点,将链表的首节点self.__head指向cur当前节点的后继节点
  4. 尾节点
    以上代码完全可以完成任务(即:pro.next = cur.next)

顺序表与单链表的对比

时间复杂度的对比:

操作 链表 顺序表
访问元素 O(n) O(1)
从头部删除元素 O(1) O(n)
从尾部删除元素 O(n) O(1)
在中间插入元素 O(n) O(n)

总结:

链表失去了顺序表随机读取的优点,同时链表由于增加了节点的指针域,空间开销比较大,但对存储空间使用要相对灵活

链表和顺序表在插入和删除是进行的是完全不同的操作

链表:

1.主要耗时的操作是遍历查找
2.删除和插入操作本身的复杂度是O(1)

顺序表:

1.主要耗时的操作是拷贝覆盖
2.抛除元素在尾部的情况
顺序表进行插入和删除时,需要对操作点之后的所有元素进行前后移动操作
只能通过拷贝和覆盖的方法进行

以上是关于单链表的主要内容,如果未能解决你的问题,请参考以下文章

数据结构代码(用C语言) 单链表的插入和删除

单链表

数据结构--单链表简单代码实现(总结)

单链表逆置

循环链表(循环单链表循环双链表)的相关操作的代码实现(C语言)

单链表反转java代码