计算机基础——python数据结构之顺序表和链表

news2024/11/14 4:11:23

计算机基础——数据结构

  • 概述
    • 数据结构
    • 算法
    • 时间复杂度
      • python的代码执行时间测量模块
  • 线性表
    • 顺序表
      • 顺序表的连续存储
      • 顺序表的两种基本实现方式
    • 链表
      • 单向链表
      • 顺序表和单链表的对比
      • 双向链表
      • 单向循环链表

概述

  • 数据是信息的载体,是描述客观师傅属性的数、字符以及所有能输入到计算机中并被计算机程序识别和处理的符号的集合
  • 数据是计算机程序加工的原料
  • 计算机可以识别和处理的,只有二进制数
  • 数据元素是数据的基本单位,通常作为一个整体进行考虑和处理
  • 一个数据元素可以由若干数据项组成,数据项是构成数据元素的不可分割的最小的单位
    • 例如APP个人中心就是一个数据元素,元素中包含的昵称,名称等就是一个个的数据项
  • 数据结构就是相互之间存在一种或者多种特定关系的数据元素的集合
    • 简单的说,我们如果要解决某个问题,就需要保存一组数据,如何让将
  • 数据对象是具有相同性质的数据元素的集合,是数据的一个子集
  • 数据结构的三要素:逻辑结构、物理结构和数据的运算
    • 逻辑结构指的是:数据元素之间的逻辑关系,分为集合、线性结构、树形结构和图状结构(网状结构)
    • 集合:各个元素同属于一个集合,没有其他关系
    • 线性结构:数据元素之间一对一的关系,除了第一个元素,所有的元素都有唯一的前驱;除了最后一个元素,所有的元素都有唯一的后继
    • 树形结构:元素之间是一对多的关系(父子结构)
    • 图状结构:数据元素之间是多对多的关系
  • 数据的存储结构:用计算机表示数据元素的逻辑关系的方式,存储方式分为:顺序存储、链式存储、索引存储和散列存储,就 线性结构而言:
    • 顺序存储:将逻辑上相邻的元素存储在屋里位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。采用这种方式存储时会分配连续的存储空间
    • 链式存储:逻辑上相邻的元素在屋里位置上可以不相邻,借助指示元素的指针来表示元素之间的关系(用指针指向下一个元素的存储位置)
    • 索引存储:在存储元素信息的同时,还建立附加索引表,索引表的每项称为索引项,索引项的一般形式时关键字和地址
    • 散列存储:根据元素的关键字直接计算出该元素的存储位置,又称为哈希存储
    • 因此,链式、索引和散列可以称为非顺序存储
    • 如果采用顺序存储,那么每个元素在物理上必须时连续的
    • 如果采用非顺序存储,则各个元素在物理上可以是离散的
    • 数据的存储结构,会影响存储空间分配的方便程度
    • 数据的存储结构,会影响对数据运算的速度
  • 数据的运算:运算的定义是针对逻辑结构的,指出运算的功能;运算的实现是针对存储结构的,指出运算的具体操作步骤
  • 数据类型:是一个值的集合和定义在此集合上的一组操作的总称
    • 原子类型:其值不可再分的数据类型,例如int、bool等
    • 结构类型:他的值可以再分解为若干成分(分量)的数据类型,例如函数
  • 抽象数据类型(ADT):是抽象数据组织及与之相关的操作
    • 简单的说,就是将一个数据模型以及定义在此数学模型上的一组操作
    • 即将数据类型和数据类型上的运算困在一起,进行封装
    • 引入抽象数据类型的目的是将数据类型的表示和数据类型上运算的实现与这些数据类型和运算在程序中的引用隔开,使他们相互独立,即实现方式和引用隔离开
    • 封装后,最常使用的数据运算有五种:插入、删除、修改、查找和排序

数据结构

  • 我们为了解决某个问题,就需要将数据保存下来,然后根据数据的存储方式来设计算法实现进行处理,因为数据存储方法的不同就需要不同的算法
    • 我们如果希望问题解决的速度快,就需要研究如何保存数据,这就是数据结构
    • 数据结构解决的就是一组数据如何保存,保存形式时怎样的问题
  • 数据是一个抽象的概念,将其进行哈分类之后得到程序语言中的基本类型,例如:int,float,char
  • 数据元素之间并不是独立的,存在特定的关系
  • 数据结构指数据对象中,数据元素之间的关系,数据结构就是对数据基本类型的一次封装
  • python中提供了很多现成的数据结构类型,即内置数据结构,例如列表、字典、远足等
  • 还有需要我们自己去定义实现的数据组织方式,这种组织方式称为python的扩展数据结构,例如栈和队列

算法

  • 程序= 数据结构+算法
    • 数据结构考虑如何将现实世界的问题信息化,将信息存进计算机,同时还要实现对数据结构的基本操作
    • 算法:如何处理这些信息,以解决实际问题
  • 算法的特性:
    • 有穷性:一个算法总在执行有穷之后结束,且每一步都可在有穷的时间内完成,算法必须是有穷的,但是程序可以是无穷的
    • 确定性:算法中每条这令必须有确切的含义,没有二义性,对于相同的输入只能得出相同的输出
    • 可行性:算法中描述的操作都可以痛殴已经实现的基本运算执行优先次来实现
    • 输入:一个算法有0个或者多个输出,这些输入取自于某个特定的对象的集合
    • 输出,一个算法有一个或者多个输出。这些输出是与输入有着某种特定关系的量
  • 一个好的算法,设计是要尽量追求目标,他有以下特质:
    • 正确性:算法应该能够正确的解决问题
    • 可读性:算法应具备良好的可读性,以帮助人们理解
      • 算法可以用伪代码描述,甚至用文字描述,重要的是要无歧义的描述出解决问题的步骤
    • 健壮性:输入非法数据时,算法能适当的做出反应货进行处理,而不会莫名其妙的输出结果
    • 高效率和低存储量的需求,即执行速度快,时间复杂度低和不废内存,空间复杂度低
  • 算法和数据结构的区别:
    • 数据结构只是静态的描述了数据元素之间的关系
    • 高效的成宿需要在数据结构的基础上设计和选择算法
    • 简单的说:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题的载体

时间复杂度

  • 时间复杂度:用于评价一个算法的时间开销,也可以理解为事前预估算法时间开销T(n)和问题规模n之间的关系
  • 一般情况下,时间复杂度只取最高级
  • 时间复杂度的大小顺序为:O(1)<O(logN)<O(n)<O(nlogN)<O(n的平方)<O(n的三次方)<O(2的n次方)<O(n的阶乘)<O(n的n次方)
  • 简单的来说,就是常对幂指阶(从小到大)
  • 代码执行时,顺序执行的代码只会影响常数,可以忽略
    • 只需要挑选循环中的基本操作,分析他的执行次数与n的关系即可
    • 如果有多层前套循环,只需要关注最深层的循环,他循环了几次
  • 分析算法的时候,存在集中考虑:
    • 算法完成工作最少需要的时间复杂度,叫做最优时间复杂度
    • 算法完成工作最多需要的时间复杂度,叫做最坏时间复杂度
    • 算法完成工作平均需要多少时间,即平均复杂度
  • 例如以下代码,如果查找的元素为第一个,就是最优复杂度;如果要查找的元素为最后一个就是最坏时间复杂度;每个元素平均多久可以找到,就是平均复杂度
list_data = ['hfdgj',8.0,33,"jdjf",6,3,7]
for i in list_data:
    if i==7:
        print("pass")
        break
  • 时间复杂度的计算
    • 1.基本操作:即常数项,认为其时间复杂度为O(1),例如循环体中的变量定义,if语句中的每个条件等
    • 2.顺序结构:时间复杂度按照加法进行计算
    • 3.循环结构:时间复杂度按照乘法进行计算
    • 4.分支结构(条件):时间复杂度取最大值
    • 5.判断一个算法的时间效率时,往往只需要关注操作数量的最高次项,其他次项和常数项可以忽略
    • 6.在没有特殊说明时,我们所分析的算法的时间复杂度应指向最坏时间复杂度

python的代码执行时间测量模块

  • 模块名:timeit
  • 首先要导入第三方模块,from timeit import Timer,Timer中需要传入几个参数,因为有初始化的存在,如下图示:
    在这里插入图片描述
  • 其中:stmt:参数是要测试的代码语句
  • setup:参数是运行代码时需要的设置
  • timer 和number是一个含义,运行的次数,其返回的时间为运行之后的平均值
  • 因此在调用这个类时,需要传两个参数
  • 代码编写及执行结果如下:
from timeit import Timer

def t1():
    data = []
    for i in range(10000):
        data.extend([i])
def t2():
    data =[i for i in range(10000)]
def t3():
    data = []
    for i in range(10000):
        data=data+[i]
def t4():
    data = []
    for i in range(10000):
        data.insert(0,1)
def t5():
    data = []
    for i in range(10000):
        data.append(i)
a = Timer("t1()","from __main__ import t1")
a_time = a.timeit(100)
b = Timer("t2()","from __main__ import t2")
b_time = b.timeit(100)
c = Timer("t3()","from __main__ import t3")
c_time = c.timeit(100)
d = Timer("t4()","from __main__ import t4")
d_time = d.timeit(100)
e = Timer("t5()","from __main__ import t5")
e_time = e.timeit(100)
print("extend:",a_time,"\n迭代器:",b_time,"\n+:",c_time)
print("append:",e_time,"\ninsert:",d_time)
-------------------------run_result----------------------
extend: 0.05123333399751573 
迭代器: 0.013302958999702241 
+: 8.697985875001905
append: 0.028532749998703366 
insert: 2.6066741249996994
  • list函数中,常见内置函数的时间复杂度:
函数时间复杂度元
index[i]O(1)
appendO(1)
pop()O(1)
pop(i)O(n)
insert(i,iterm)O(n)
delO(n)
reverseO(n)
sortO(nlogn)
for循环遍历O(n)
使用in判断元素是否存在O(n)
切片取值:list[x,y]O(k),k等于x到y之间的个数
删除列表切片O(n)
设置列表切片O(n+k)
两个列表相加O(k),k代表第二个列表中元素的个数
两个列表相乘法O(nk)
  • dict中,常见函数的时间复杂度
函数时间复杂度元
copyO(n)
get取值O(1)
setO(1)
deleteO(1)
in判断元素是否在字典中O(1)
for循环遍历O(n)

线性表

  • 编写程序的税后,经常要将一组(通常为某个类型)的数据作为整体来管理和使用,这样就需要创建元素组,使用变量来接收,传进和传出函数等,还可以对元素的个数进行调整(增加或者删除)
  • 对于这种需求,最简单的就是将一组数据看作一个序列,用元素在序列中的位置和顺序表示数据之间的某种关系
  • 这样一组序列元素的组织形式,就是线性表
  • 一个线性表是某类元素的一个集合,还会记录着元素之间的一种顺序关系
  • 线性表是最基本的数据结构之一,还经常被用作更复杂的数据结构的实现的基础
  • 根据线性表的存储方式,分为两种实现模型:
    • 顺序表:将元素存放在一块连续的存储区域内,元素之间的顺序由他们的存储顺序来表示
    • 链表:将元素存放在通过链接构造起来的一系列存储块当中,每个存储单元的链接指向下一个存储快,可以理解为指针

顺序表

顺序表的连续存储

  • 顺序表分为基本顺序表和外置顺序表两种结构
    • 基本顺序表的元素是连续存储的,每个元素占用的存储单元大小股东相同,元素的下表是其逻辑地址
    • 元素的物理地址(实际内存地址)可以通过存储区的其实地址加上逻辑地址(第i个元素)与顿出单元大小的乘积获得
    • 因此,访问股东元素时,不需要从头遍历,通过计算便可获得对应地址及其时间复杂度
  • 元素外置顺序表
    • 如果元素的存储单元大小不统一,就需要采用元素外置的顺序表,将实际数据元素另行存储,而顺序表中个单元位置上存储的是对应元素的地址信息(即链接)
    • 由于每个链接所修的存储量相同,那么就可以计算出元素链接的存储位置,然后通过链接就可以找到实际存储数据的物理地址
    • 注意:元素外置的顺序表中,存储的不是数据元素的大小,而是每一个元素的链接地址,这个顶底所占用的内存通常很小

顺序表的两种基本实现方式

  • 一个顺序表分为两部分,一部分是表中元素的集合,另外一部分是实现正确操作而记录的信息,这部分信息主要包括数据表的容量和当前已经存入的元素个数
  • 顺序表的基本实现方式有两种:
    • 一体式:存储信息的单元和元素存储区以连续的方式安排在一个区域内,两部分整体形成一个完整的顺序表对象(即相邻的内存单元中)
    • 分离式:表对象只保存整个表有关的信息(容量和元素个数)及指向数据元素存储位置的指针或链接,实际元素存放在临沂个独立的元素存储区内,通过放置在表对象中的链接或指针查找
  • 存储数据时,当集合的实际存储量大于预估的情况,就需要对元素的存储区进行扩充
    • 一体式结构,由于顺序表信息区与数据区连续存储在一起,因此如果想要扩充,就必须整体搬迁,即整个数据表对象(指存储顺序表的结构信息的区域)改变了
    • 分离式的扩充,可以在不改变对象的前提下,对其数据区域进行扩充, 代码中所有使用这个表的地方必须要修改
    • 只要程序的运行环境(计算机系统)还有空闲的存储,这种表结构就不会因为满了而无法进行
  • 因此,为了考虑数据的动态变化,通常会使用分离式存储
  • 扩充的两种策略:
    • 每次扩充增加固定树木的存储位置,这种策略叫做线性增常,这种方式节省空间,但是扩充操作频繁,操作次数多
    • 每次扩充量加倍,这种方式减少了扩充操作的执行次数,但是可能会浪费空间资源,以空间换时间,推荐的方式
  • 举个例子,list是离散方式的顺序表;str,数值,元祖是一体式的顺序表

链表

  • 顺序表的构建需要预先知道数据大小来神器你个连续的存储空间,而在进行扩充时没有需要进行数据的千一,因此这种方式使用起来不是很灵活
  • 链表结构,可以充分利用计算机的内存空间,实现领过的内存动态管理
  • 链表的定义:是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续的存储,而是在每个节点(数据存储单元)中存放下一个节点的位置信息(即地址)
  • 即每一个节点中,都包含当前节点的数据和下一个节点的地址信息

单向链表

  • 单项链表也叫单链表,是链表中最简单的一种形式
  • 他的每个节点包含两个域,一个是信息域,一个是链接域
  • 这个链接指向表中的下一个节点,而最后一个节点的链接域是一个空值
    在这里插入图片描述
  • 上图中,elem用来存放具体的数据
  • 链接域next,用来存放下一个节点的位置(即python中的标识)
  • 变量p指向链表头节点(首节点)的位置,从p出发能找到任意节点
  • 一个单链表,如果要实现的话,需要包含以下操作
    • 判断链表是否为空
    • 链表的长度
    • 遍历整个链表
    • 链表头部添加元素
    • 链表尾部添加元素
    • 指定位置添加元素
    • 删除节点
    • 查找某个节点是否存在
  • 单链表的代码实现方式如下:
class Node():

    def __init__(self,elem):
        '''定义链表的节点,elem是数据,next是下个节点的指针'''
        self.elem = elem
        self.next = None

class Link_List():

    def __init__(self,node=None):
        '''初始化函数,将节点作为一个整体传入'''
        self._head = node

    def is_empty(self):
        ''' 判断非空'''
        return self._head==None

    def length(self):
        ''' 计算链表长度'''
        ''' 需要一个游标的标识,来记录当前访问到哪个位置上了'''
        cur= self._head # cur初始值指向head,意味着他指向第一个值
        count = 0 # count记录列表中元素的个数
        '''不用cur.next=None作为条件,原因是最后一个元素的指针为None
        如果以他为判断条件,会少计数1'''
        while cur !=None:
            count +=1
            '''cur有next用法的原因:
            cur = self._head = node,node中存在next属性'''
            cur= cur.next
        return count

    def travel(self):
        ''' 遍历整个链表'''
        cur = self._head
        while cur != None:
            print(cur.elem,end=" ")
            cur = cur.next
        print("")

    def add(self,itrem):
        ''' 链表头部添加元素'''
        ''' 最终要达到的效果,新节点的next指向原头部,然后头部节点指向新node'''
        ''' 修改的时候要注意先后顺序,必须先让新节点的next指向原头部
        因为必须根据原头部来确定新节点后需要跟的node,否则会丢节点'''
        node = Node(itrem)# 数据元素封装为链表的节点
        node.next = self._head#新节点的next指向原头部
        self._head=node#头部指向新节点



    def append(self,itrem):
        ''' 链表尾部追加元素'''
        '''先设置新的节点,遍历整个列表之后,将最后一个节点的指针指向新节点'''
        node = Node(itrem)
        '''如果链表是一个空列表,那么node就是None,没有next属性,因此需要考虑特殊情况'''
        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,itrem):
        ''' 链表随意位置插入元素
        :pos 从0开始索引
        '''
        '''插入元素,移动的时候要移动到目标元素的前一个位置
        现将新节点的node指向下一个元素,:再将前一个的指针指向新节点'''
        if pos <=0:#小于0,相当于头部插入
            self.add(itrem)
        elif pos >(self.length()-1):#大于列表总长度,相当于尾部添加
            self.append(itrem)
        else:
            pre = self._head  # 可以理解为插入元素的游标,当前指向第一个节点
            count = 0
            while count < (pos - 1):  # 判断让其指向pos的前一个节点
                count += 1
                pre = pre.next
            # 当循环退出之后,pre指向pos-1
            node = Node(itrem)  # 创建新的node节点
            node.next = pre.next  # pre当前指向的就是要插入的位置,因此先让node指向目标节点
            pre.next = node  # 然后修改pre的指针,指向node


    def remove(self,itrem):
        ''' 删除节点'''
        '''可以使用两个游标节点,前一个和后一个保持一个节点的距离
        删除节点的时候,让前一个节点指向后一个节点所指向的节点'''
        cur = self._head
        pre = None
        while cur!=None:
            if cur.elem == itrem:
                # 元素相等的情况下,需要判断是否是头节点,
                # 因为刚开始pre是None,没有next属性
                if cur == self._head:
                    self._head = cur.next
                else:
                    pre.next  = cur.next
                break
            else:
                pre = cur
                cur = cur.next

    def search_index(self,pos):
        ''' 查找元素'''
        cur = self._head  # 可以理解为插入元素的游标,当前指向第一个节点
        if pos <=0:
            print(cur.elem)
        else:
            count = 0
            while count <=pos :  # 判断让其指向pos的前一个节点
                count += 1
                if count == self.length():
                    break
                cur = cur.next
            print(cur.elem)

    def search_value_1(self, itrem):
        cur = self._head
        count = 0
        while count <(self.length()-1):
            cur = cur.next
            if cur.elem == itrem:
                print(count)
                break
            count +=1
            if count == (self.length()-1):
                print("pass")

    def search_value_2(self, itrem):
        cur = self._head
        while cur !=None:#cur为None,意味着cur已经走到了链表的最后一位
            if cur.elem == itrem:
                return True
            else:
                cur= cur.next
        return False
if __name__ == '__main__':
    ll = Link_List()
    print("是否为空:",ll.is_empty())
    print("长度:",ll.length())
    ll.add(10)
    ll.insert(pos=1,itrem="ppp")
    ll.append(1)
    ll.insert(pos=-1,itrem="fdjfgir")
    print("是否为空:",ll.is_empty())
    print("长度:",ll.length())
    ll.append("i")
    ll.append("love")
    ll.append("lalal")
    ll.append(2)
    ll.append(3)
    ll.insert(pos=7,itrem="hahaha")
    ll.search_index(-1)
    ll.search_value_1("lik")
    print(ll.search_value_2("i"))
    ll.travel()
    ll.remove("fdjfgir")
    ll.travel()
    ll.remove(3)
    ll.travel()
    ll.remove("i")
    ll.travel()
    **************************执行结果***************************
是否为空: True
长度: 0
是否为空: False
长度: 4
fdjfgir
pass
True
fdjfgir 10 ppp 1 i love lalal hahaha 2 3 
10 ppp 1 i love lalal hahaha 2 3 
10 ppp 1 i love lalal hahaha 2 
10 ppp 1 love lalal hahaha 2 

顺序表和单链表的对比

  • 链表失去了顺序表堆积读取的优点,同时链表由于增加了节点的指针,空间开销较大,单对存储空间的使用要相对灵活
  • 链表和顺序表的各种操作复杂度如下:
操作链表顺序表
访问元素O(n)O(1)
在头部插入/删除O(1)O(n)
在尾部插入/删除O(n)O(1)
在中间插入/删除O(n)O(n)
  • 虽然表面看起来是时间复杂度都是O(n),但是链表和顺序表插入和删除时是完全不同的操作
    • 链表耗时是遍历查找,删除和插入操作本身的时间复杂度是O(1)
    • 顺序表查找很快,主要好事的操作是拷贝和覆盖
    • 因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时,需要对操作点之后的所有元素今夕你个前后移位的动作,只能通过拷贝和覆盖的方式进行

双向链表

  • 比单向链表更复杂的结构,每个节点有两个链接,一个指向前一个节点,当次节点为第一个节点时,指向空值,而另外一个指向下一个节点,当次节点为最后一个节点时,指向空值
    在这里插入图片描述
  • 双向链表应实现的操作有:
    • 链表是否为空
    • 链表长度
    • 遍历链表
    • 链表头部添加
    • 链表尾部添加
    • 指定位置添加
    • 删除节点
    • 查找节点是否存在
  • 代码如下:
class Node():

    def __init__(self,iterm):
        '''
        :param iterm: 传入节点的数据
        '''
        self.elem= iterm
        self.next = None#后继指向
        self.prev = None#前驱指向

class DoubleLinkList():
    '''双链表操作'''
    def __init__(self,node = None):
        '''
        双链表初始化函数,链表头指向节点
        :param node: 第一个节点
        '''
        self.__head = node

    def is_empty(self):
        ''' 判断是否为空'''
        return self.__head is None

    def length(self):
        '''求长度'''
        # cur为游标,指向移动的游标,遍历节点
        cur = self.__head
        #count 计算节点数量
        count = 0
        #遍历节点
        while cur != None:
            count +=1
            cur = cur.next # 游标指向下一个节点
        return count

    def travel(self):
        ''' 遍历链表'''
        cur = self.__head
        while cur != None:
            print(cur.elem,end = " ")
            cur = cur.next # 游标指向下一个节点
        print("")

    def add(self,iterm):
        '''
        直接在首节点插入元素,插入时,先将插入节点的N指向原首节点的P
        再将头部指向插入节点的P,最后将原首节点的P指向插入节点的N
        :param iterm: 要插入的元素
        '''
        #方法一,先将插入node的后继指向原节点的前驱
        flag = False # 定义一个标志,用于区分是否是空列表
        node = Node(iterm)  # 数据元素封装为链表的节点
        # 在这里判断的原因是,放在下面,新节点已添加,无法判断链表是否为空
        if self.is_empty():
            flag = True
        node.next = self.__head  # 新节点的next指向原头部
        self.__head  = node  # 头部指向新节点
        if flag == False :# 如果标志不是True,说明链表不为空,就需要执行下面的操作
            node.next.prev = node#将原首节点的前驱指向插入节点的后继

    def append(self,iterm):
        '''
        尾部追加元素
        :param iterm:
        :return:
        '''
        '''先设置新的节点,遍历整个列表之后,将最后一个节点的指针指向新节点'''
        node = Node(iterm)
        '''如果链表是一个空列表,那么node就是None,没有next属性,因此需要考虑特殊情况'''
        if self.is_empty():
            self.__head = node
        else:
            cur = self.__head
            while cur.next != None:
                cur = cur.next
            cur.next = node# 追加时将最后一个节点的后继指向新节点的前驱
            node.prev = cur# 再将新节点的前驱指向最后一个节点的后继

    def insert(self,pos,iterm):
        '''
        随机插入元素
        :param pos: 插入位置
        :param iterm: 插入的元素
        '''
        if pos <=0:#小于0,相当于头部插入
            self.add(iterm)
        elif pos >(self.length()-1):#大于列表总长度,相当于尾部添加
            self.append(iterm)
        else:
            pre = self.__head  # 可以理解为插入元素的游标,当前指向第一个节点
            count = 0
            while count <= (pos - 1):  # 判断让其指向pos的前一个节点
                count += 1
                pre = pre.next
            # 当循环退出之后,pre指向pos-1
            node = Node(iterm)  # 创建新的node节点
            node.next = pre
            # pre当前指向的就是要插入的位置,因此先让node的后继指向找到的目标节点
            node.prev = pre.prev# node的前驱指向前一个节点( pre.prev为前一个节点)
            pre.prev.next = node#前一个节点的后继指向node
            pre.prev = node#后一个节点的前驱指向node

    def remove(self,itrem):
        '''
        删除元素为elem的节点
        :param itrem: 元素信息
        '''
        cur = self.__head
        while cur != None:
            if cur.elem == itrem:
                # 元素相等的情况下,需要判断是否是头节点,
                # 因为刚开始pre是None,没有next属性
                if cur == self.__head:
                    self._head = cur.next
                    if cur.next !=None:#判断链表是否只有一个节点,是的话就不需要指向了
                        cur.next.prev = None
                else:
                    cur.prev.next = cur.next # 要删除节点的前一个节点的后继指向下一个节点
                    if cur.next !=None:
                        cur.next.prev = cur.prev# 要删除节点的后一个节点的前驱指向前一个节点
                break
            else:
                cur = cur.next

    def search(self,iterm):
        cur = self.__head
        while cur != None:  # cur为None,意味着cur已经走到了链表的最后一位
            if cur.elem == iterm:
                return True
            else:
                cur = cur.next
        return False


if __name__ == '__main__':
    ll = DoubleLinkList()
    print("是否为空:", ll.is_empty())
    print("长度:", ll.length())
    ll.add(10)
    ll.append(100)
    ll.add(20)
    ll.travel()
    ll.insert(1,"haha")
    ll.travel()
    ll.remove(100)
    ll.travel()
    print(ll.search(10))
    ***********************run——result*****************
是否为空: True
长度: 0
20 10 100 
20 haha 10 100 
20 haha 10 
True

单向循环链表

  • 单向循环链表是单链表的一个变形,脸比啊中最后一个节点的next不在乎为None,而是指向脸憋哦的头节点
    在这里插入图片描述
  • 单项循环链表的操作有:
    • 链表是否为空
    • 链表长度
    • 遍历链表
    • 链表头部添加
    • 链表尾部添加
    • 指定位置添加
    • 删除节点
    • 查找节点是否存在
  • 代码如下:
在这里插入代码片class Node():

    def __init__(self,iterm):
        '''
        :param iterm: 传入节点的数据
        '''
        self.elem= iterm
        self.next = None#后继指向

class ForLinkList():
    '''单向循环操作'''
    def __init__(self,node = None):
        '''
        单向循环链表表初始化函数,节点的next指向下一个节点
        最后一个节点的next区域指向头节点
        如果链表只有一个节点,就是节点的next区域指向自己
        :param node: 节点
        '''
        self.__head = node
        if node:
            node.next = node# node存在,需要设置他的next区域指向他本身
            #可以理解为链表中只有这一个节点

    def is_empty(self):
        ''' 判断是否为空'''
        return self.__head is None

    def length(self):
        '''求长度'''
        # cur为游标,指向移动的游标,遍历节点
        cur = self.__head# 创建指针
        #count 计算节点数量
        if self.is_empty():
            return 0  # 空链表,直接返回0
        count = 1 #count必须从1开始,因为节点的next等于头节点时,没有进入循环,count没有+1
        #遍历节点
        while cur.next != self.__head:
        # 这里不能使用cur,只能使用cur.next的原因是:创建游标的时候,就使用了cur等于头节点
        # 详见length创建指针的代码
            count +=1
            cur = cur.next # 游标指向下一个节点§
        return count

    def travel(self):
        ''' 遍历链表'''
        cur = self.__head
        if self.is_empty():# 为空,直接但会None,因为空链表进不去循环
            return None
        while cur.next != self.__head:
            print(cur.elem,end = " ")
            cur = cur.next # 游标指向下一个节点
        print(cur.elem,end=" ")# 退出循环的时候,需要再打印当前节点的值,
        # 因为最后一个节点的next,等于头节点,无法进入循环,因此无法打印
        print("")

    def add(self,iterm):
        '''
        链表头部添加元素
        最终要达到的效果,新节点的next指向原头部,然后头部节点指向新node
        最后将尾节点的next指向头节点,需要遍历找到尾节点
        :param iterm: 要插入的元素
        '''
        node = Node(iterm)  # 数据元素封装为链表的节点
        if self.is_empty():
            self.__head=node
            node.next = node
        cur = self.__head
        while cur.next!=self.__head:
            cur = cur.next
        node.next = self.__head  # 新节点的next指向原头部
        self.__head = node  # 头部指向新节点
        cur.next = self.__head


    def append(self,iterm):
        '''
        尾部追加元素
        :param iterm:
        :return:
        '''
        '''先设置新的节点,遍历整个列表之后,将最后一个节点的指针指向新节点'''
        node = Node(iterm)
        '''如果链表是一个空列表,那么node就是None,没有next属性,因此需要考虑特殊情况'''
        if self.is_empty():
            self.__head = node
            node.next = node
        else:
            cur = self.__head
            while cur.next != self.__head:
                cur = cur.next
            node.next = self.__head  # 先将新节点的next指向头节点
            cur.next = node# 再最后一个节点next指向node


    def insert(self,pos,iterm):
        '''
        随机插入元素
        :param pos: 插入位置
        :param iterm: 插入的元素
        '''
        if pos <=0:#小于0,相当于头部插入
            self.add(iterm)
        elif pos >(self.length()-1):#大于列表总长度,相当于尾部添加
            self.append(iterm)
        else:
            pre = self.__head  # 可以理解为插入元素的游标,当前指向第一个节点
            count = 0
            while count < (pos - 1):  # 判断让其指向pos的前一个节点
                count += 1
                pre = pre.next
            # 当循环退出之后,pre指向pos-1
            node = Node(iterm)  # 创建新的node节点
            node.next = pre.next  # pre当前指向的就是要插入的位置,因此先让node指向目标节点
            pre.next = node  # 然后修改pre的指针,指向node

           def remove(self,itrem):
        '''
        删除元素为elem的节点
        可以使用两个游标节点,前一个和后一个保持一个节点的距离
        删除节点的时候,让前一个节点指向后一个节点所指向的节点
        :param itrem: 元素信息
        '''
        cur = self.__head
        if self.is_empty():
            return
        pre = None
        while cur.next != self.__head:
            if cur.elem == itrem:
                # 元素相等的情况下,需要判断是否是头节点,
                # 因为刚开始pre是None,没有next属性
                if cur == self.__head:#头节点
                    # 如果删除的是头节点,需要遍历找到尾节点,修改尾节点的next指向
                    wei = self.__head
                    while wei.next != self.__head:
                        wei = wei.next
                    #跳出节点时,wei指向最后一个节点
                    self.__head = cur.next
                    wei.next = self.__head
                else:#中间节点
                    pre.next = cur.next
                return # 这里代表整个头节点的结束,使用return退出整个函数
            # 如果使用break,标识退出函数中的循环,下面的代码还会继续执行
            else:
                pre = cur
                cur = cur.next
        #因为cur为尾节点的时候不进入循环,因此跳出循环体,cur才指向尾节点
        if cur.elem == itrem :
            if cur ==self.__head:
                self.__head =None
            else:
                pre.next = cur.next

    def search(self,iterm):
        cur = self.__head # 如果是空列表,下面就无法去判断.next,因粗需要判断是否为空
        if self.is_empty():
            return False
        while cur.next != self.__head:  # cur为None,意味着cur已经走到了链表的最后一位
            if cur.elem == iterm:
                return True
            else:
                cur = cur.next
        if cur.elem == iterm:#因为最后一个节点未进入循环,因此没有进行比较,需要重新判断
            return True
        return False


if __name__ == '__main__':
    ll = ForLinkList()
    print("是否为空:", ll.is_empty())
    print("长度:", ll.length())
    ll.add(20)
    ll.append(100)
    ll.travel()
    ll.add(10)
    ll.travel()
    ll.insert(1,"haha")
    ll.travel()
    ll.remove(100)
    ll.travel()
    ll.remove(10)
    ll.travel()
    ll.remove(20)
    ll.travel()
    ll.remove("haha")
    print(ll.search(10))
    *************run_result****************
是否为空: True
长度: 0
20 100 
10 20 100 
10 haha 20 100 
10 haha 20 
haha 20 
haha 
False

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/176575.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

抖音小黄车挂淘宝店相关问题记录

不开淘宝店&#xff0c;但作为技术被客户问起来&#xff0c;总得知道个一二&#xff0c;今天研究了一下&#xff0c;大致情况如下&#xff1a; 抖音绑定淘宝推广id 大致流程就是抖音开通橱窗&#xff0c;橱窗和淘宝挂钩&#xff0c;淘宝商品引入橱窗。 首先要满足抖音要求&a…

AlmaLinux 9 安装Oracle GraalVM Enterprise Edition 21.3.x

今天我们尝试一下在AlmaLinux 9 安装Oracle GraalVM Enterprise Edition 21.3.x。 GraalVM Enterprise 21 是一个 Long-Term-Support (LTS) 版本。 注意&#xff1a;下载Oracle GraalVM Enterprise Edition需要有Oracle账户&#xff0c;如果没有&#xff0c;可以通过访问创建您…

【SpringCloud】Nacos注册中心、配置中心用法与原理(上)

【SpringCloud】Nacos注册中心、配置中心用法与原理&#xff08;上&#xff09; 一、Nacos注册中心 1. 安装Nacos 【BUG】请注意Nacos2.0版本与1.0版本是有差别的&#xff01; 2. Nacos的服务注册使用样例 &#xff08;1&#xff09;引入依赖 &#xff08;2&#xff09;配…

PowerShell 学习笔记

一、PowerShell 强大之处使用方便面向对象使用.net平台功能兼容性好&#xff08;兼容CMD等&#xff09;可扩展性好二、快捷键Tab&#xff1a;自动补齐命令或者文件名三、管道和重定向管道&#xff1a;上条命令的输出作为下一条命令的输入举例如下&#xff1a;列出当前目录ls结果…

FPGA 20个例程篇:19.OV7725摄像头实时采集送HDMI显示(四)

第七章 实战项目提升&#xff0c;完善简历 19.OV7725摄像头实时采集送HDMI显示&#xff08;四&#xff09; 在介绍完OV7725初始化配置和视频采集模块后&#xff0c;就到了整个项目的核心部分即DDR3乒乓存储图像模块&#xff0c;为了实现整个FPGA项目工程当中良好的实时性&…

会议论文分享-FSE20-基于学习的状态修剪策略

基于学习的状态修剪策略1.引言2.Homi算法2.1.概率修剪策略2.2.Homi2.2.1.Collecting Promising Data2.2.2.Generating Features.2.2.3.Learning Distribution2.2.4.Sampling Values2.2.5.总结3.实验3.1.实验设置3.2.有效性3.3.候选状态数量3.4.与朴素方法的对比4.代码实操4.1.代…

5、IDEA详细配置

文章目录5、IDEA详细配置5.1 如果打开详细设置界面5.2 系统设置1 默认启动项目配置2 取消自动更新5.3 设置整体主题1 选择主题2 设置菜单和窗口字体大小3 设置IDEA背景图5.4 设置编辑器主题样式1 编辑器主题2 字体大小3 注释的字体颜色5.5 显示行号与方法分隔符5.6 代码智能提示…

2023mac电脑系统第三方清理软件CleanMyMacX

认识 CleanMyMac X 4.12.2! 此版本有哪些亮眼的更新&#xff1f; “更新程序”模块之前曾出现重复推送已安装更新的情况。 小的可用性问题。 任何一部电子设备在使用多年之后都会出现性能下降的问题&#xff0c;苹果的Mac计算机自然也不例外。当你发现Mac运行缓慢&#xff…

Tkinter的Menu与Messagebox

菜单控件Menu 在当前界面的左上角创建一个菜单&#xff0c;可创建二级菜单、三级菜单等等 具体语法&#xff1a; 创建一个菜单&#xff0c;并命名为menu menutkinter.Menu(root) 创建menu菜单的列表&#xff0c;并命名为filemenu filemenutkinter.Menu(menu,tearoff0) 在…

jar包导入失败/配置国内源的方法

注意:idea 的settings分为当前项目设置和新建项目设置两个部分&#xff0c;注意一些设置这两个地方都要调整。下面的步骤 修改好settings后&#xff0c;new projects 里的settings也要同样方法进行修改Maven中央仓库默认使用的是国外的&#xff0c;而国外访问速度很慢&#xff…

SpringBoot基础入门

简介 官网&#xff1a;https://spring.io/projects/spring-boot Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化Spring应用的初始搭建以及开发过程。 SpringBoot不是Spring官方的框架模式&#xff0c;而是一个团队在Spring4.0版本上二次开发并开源…

《深入浅出计算机组成原理》学习笔记 Day8

加法器1. 异或门与半加器2. 全加器3. 总结参考1. 异或门与半加器 异或门就是实现一个最简单的整数加法所需要使用的基本门电路。 当输入两位都是 11 的时候&#xff0c;还需要向更左侧的一位进行进位。这个就对应一个与门&#xff0c;也就是当加数和被加数都是 1 的时候&…

【数据结构之二叉树系列】二叉树的基本知识

目录前言一、树1、树的相关概念&#xff08;1&#xff09;结点&#xff08;2&#xff09;结点的度&#xff08;3&#xff09;叶结点&#xff08;4&#xff09;分支结点&#xff08;5&#xff09;父亲结点&#xff08;6&#xff09;子节点&#xff08;7&#xff09;树的度&#…

Java标准输入输出流(in/out)

文章目录前言标准输入流标准输出流前言 Java通过系统类System实现标准输入/输出的功能&#xff0c;在Java中输入输出数据一般&#xff08;图形化界面例外&#xff09;要用到标准输入输出流 System.in 标准输入&#xff1a;通常指从键盘输入数据System.out 标准输出&#xff1…

VueJs中如何使用Teleport组件

前言在DOM结构相对比较复杂,层级嵌套比较深的组件内,需要根据相对应的模块业务处理一些逻辑,该逻辑属于当前组件但是从整个页面应用的视图上看,它在DOM中应该被渲染在整个vue应用外部的其他地方,不能影响组件的结构比较常见的应用场景:就是全屏的模态框,控制元素的位置,也是可以…

分享124个ASP源码,总有一款适合您

ASP源码 分享124个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 124个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1MLzeod5DblXbW3sZAeTsEQ?pwd9jfe ​​​​​​​…

连续系统的数字PID控制仿真-2

被控对象为三阶传递函数&#xff0c;采用Simulink模块与M函数相结合的形式&#xff0c;利用ODE45的方法求解连续对象方程&#xff0c;主程序由Simulink模块实现&#xff0c;控制器由M函数实现。输入指令信号为正弦信号0.05sin(2Tt)。采用PID方法设计控制器&#xff0c;其中kp …

casbin权限和配置文件的理解

官方文档 基础权限模型 下图为我基于个人理解画出来的(关于多租户RBAC模型可能有误) 发现一篇博客讲的还行Casbin权限模型&#xff0c;看他的权限系统设计模型分析部分 casbin配置文件内容的结构解释 注意matchers可以设置多个。我在知道这个之前一直疑惑为什么需要policy_…

【设计模式】我终于读懂了外观模式。。。

今天是正月初二&#xff0c;在这里给大家拜年啦&#xff01; 祝愿大家&#xff1a;兔年大吉大利&#xff0c;兔年顺顺利利&#xff0c;兔年快快乐乐&#xff0c;兔气十足十足&#xff0c;兔年富富满堂&#xff0c;兔年财源广进&#xff0c;兔年步步高升&#xff0c;兔年梦想成真…

【大数据hive】hive ddl语法使用详解

一、前言 DDL&#xff0c;也叫数据定义语言 (Data Definition Language, DDL)&#xff0c;是SQL语言集中对数据库内部的对象结构进行创建&#xff0c;删除&#xff0c;修改等的操作语言&#xff0c;这些数据库对象包括database、table等&#xff0c;使用过mysql的同学应该对此很…