数据结构

买了88元的数据结构课程:

1、别人敲代码的速度

2、别人写代码的思维逻辑

 

脚本语言:解释语言

类(class)=数据+方法

class中各种def函数

 

时间复杂度:

计算出循环的次数,然后取最高次幂对应的多项式即为时间复杂度(因此时间复杂度只是执行次数的一个大概的估算)

 

线性结构:内存连续,下标访问

array(很少用到),list

list的方法

append,pop(只操作了一个元素)时间复杂度为o(1)

insert,remove (操作多个元素),时间复杂度为o(n)

链式结构:不连续(一个指一个),无下标访问

 

链表

头指针的作用:为了使用方便,自己理解是为了更好找到链表的开头

为了使用方便,通常会加一个链表头指针,其数据字段不存放任何数据。如环形链表中头指针只是一个空的节点,其左指针指向链表的最后一个节点,右指针指向第一个节点

 

指针指向下一个节点的内存地址
链表头指针和第一个节点是有区别的

 

链表节点定义:

class Node():#这里的名字Node根据节点的真实含义取名,这里就是定义的节点的结构
    def __init__(self):
        self.val=None
        self.next= None

head=Node()#建立链表头部。因为初始化中值为None,所以头节点中没有值,指向None
head.next= None

#添加一个指针ptr
ptr=head

#链表尾部添加元素(将ptr向后指)
new_data=Node()
'''加入新元素的值,这里需要自己输入数据字段的值,这与下面的遍历直接取值是不一样的'''
ptr.next=new_data
new_data.next=None

#将存储指针的位置向前移动一位
ptr=ptr.next

#遍历链表,加入while循环
while ptr!=None:
    sum=sum+ptr.val#注意取出当前节点的值
    '''当然这里也能统计已经遍历的节点个数 num+=1'''
    ptr=ptr.next

 

链表头部,尾部,中间添加元素

#发现都是先考虑向后指
#链表头部添加元素,新节点的指针指向头指针first指向的内容,头指针first向前移动一位
newnode.next=head
head=newnode

#尾部添加元素
newnode.next=None
#之前尾部节点指向newnode,这个自己不知道怎么表示?用ptr
ptr.next=newnode

#中间添加元素
newnode.next=ptr.next
ptr.next=newnode

 将上面三种情况综合如下:

#这里自己有个疑问,在l链表中间插入值时,ptr此时所指位置怎么确定?
#当插入元素时,肯定会指定插入在那个元素的后面,这个时候就要把那个元素的位置找出来。通过什么找?通过节点中的数据字段找,如编号
def findnode(head,position):#找节点时需要把头节点和插入位置传进来
    ptr=head
    while ptr!=None:#错写为if ptr!=None:
        if ptr.val==position:
            return ptr
        ptr=ptr.next#ptr向后移,ptr在前ptr.next在后
    return ptr
position=int(input())
ptr=findnode(head,position)
#中间添加元素 
newnode.next=ptr.next 
ptr.next=newnode   按照书本补充完整

删除链表中的元素:

#删除头节点,引入ptr
head=Node()
ptr=head
ptr=ptr.next


#删除尾节点,这里需要找到删除节点的前一个节点
ptr.next=tail#tail需要定义
ptr.next = None


#删除中间元素(主要注意此情况),同样通过数据字段,如员工编号来确定删除节点的位置
remove=ptr.next#给中间元素添加个指针,这里引入中间元素remove
ptr.next=remove.next#再把ptr指向后面

将上面三种综合如下:

def delete_node(head,del_ptr):#传入要删除节点的指针
    ptr=head#引入一个新的移动指针
    if del_ptr.val==head.val:#如果确定要删除的节点的数据字段和头节点的数据字段相同,说明要删除头节点
        ptr=ptr.next
    else:
        while ptr.next !=del_ptr:#错写成while ptr.next!=None:,这个while是要找出删除的节点的前一个节点ptr
            ptr=ptr.next#不是删除头节点,就把移动指针向后移,直到找到要删除的节点
        if del_ptr.next== None:
            ptr.next=None
        else:
            ptr.next=del_ptr.next
    return head#返回链表只需要返回链表头,这也是传进来的参数


#确定删除节点位置
while True:
    delete_num=int(input())
    ptr=head
    find=0
    while ptr!=None:    #这一行循环写时漏掉
        if ptr.val==delete_num:
            head=delete_node(head,ptr)#这里传入的参数就是要删除的节点位置,返回值链表头直接给链表头,根据这个链表头,可以在下面打印出整个链表
            find+=1
        ptr=ptr.next#一直往后找,没找到,移动指针向后移动一位
    if find==0:#说明一遍走完都没找到要删除的元素对应的节点
        print("没有找到")

代码实现链表的反转

temp=temp.next是使temp指向下一结点。
temp.next=temp是使temp本身的next指针指向自己

反转链表注意以下三点:

三个指针last,ptr,ptr_next 

每次只考虑当前指针指向前一个节点

新位置赋值给要移动的指针,将指针移动到新位置,如:last = ptr

class Solution(object):
    def reverseLinkedList(self, head):
        if head == None:#head表示原链表的头节点。如果没有头节点,反转仍为空,返回空
            return []
        last = None  #添加一个指针,初始化为空,用于反转后的最后一个节点。同时会在while中不断后移
        ptr= head             #添加另一个指针
        while (temp != None):
            ptr_next = ptr.next#取出头节点的下一个节点
            ptr.next = last
            last = ptr
            ptr= ptr_next#指向头节点的指针现在指向第二个节点

        return last

①head == None: #如果头节点就是空的

 

判断是链表是否存在环

链表本身可能不只是一个单纯的环状结构

所在位置相等来判断是否走到相同位置:if slowPtr == fastPtr

#定义一个链表
class Node():
    def __init__(self, val=None):
        self.val = val
        self.next = next

def findbeginofloop(head):  # 判断是否为环结构并且查找环结构的入口节点
    slowPtr = head  # 将头节点赋予slowPtr
    fastPtr = head  # 将头节点赋予fastPtr
    loopExist = False  # 默认环不存在,为False
    if head == None:  # 如果头节点就是空的,那肯定就不存在环结构
        return False
    while fastPtr.next != None and fastPtr.next.next != None:  # fastPtr的下一个节点和下下个节点都不为空
        slowPtr = slowPtr.next  # slowPtr每次移动一个节点
        fastPtr = fastPtr.next.next  # fastPtr每次移动两个节点
        '''
        计算环长时
        slowstepnum += 1
        faststepnum += 2
        '''
        if slowPtr == fastPtr:  # 当fastPtr和slowPtr的节点相同时,也就是两个指针相遇了
            loopExist = True
            print("存在环结构")
            break
    # 环长 = fast走的步长 - slow走的步长

    if loopExist == True:
        slowPtr = head
        while slowPtr != fastPtr:
            fastPtr = fastPtr.next
            slowPtr = slowPtr.next
        return slowPtr#返回环的入口点

    print("不是环结构")
    return False
#请记完整程序
if __name__ == "__main__":
     node1 = Node(1)
     node2 = Node(2)
     node3 = Node(3)
     node4 = Node(4)
     node5 = Node(5)
     node1.next = node2
     node2.next = node3
     node3.next = node4
     node4.next = node5
     node5.next = node2
     print(findbeginofloop(node1).val)

输出结果:

扩展:怎么在环状入口添加新节点

①首先需要找到入口节点,根据上面程序找(书本都是给出了环形链表的头节点,因为都是纯粹的环状。如果不是纯粹的环状也要知道怎么去找头节点)

②在入口节点前添加节点:

新节点指向环状链表的头节点

环状链表的尾节点指向新节点,尾节点需要遍历

头指针指向新节点

newnode.next=slow#slow为找出的头节点
#找尾节点
ptr=slow
while ptr.next!=slow:#错写成while ptr.next!=None:
    ptr=ptr.next
#能跳出while循环,说明已经找到了尾节点。此时只需要将尾节点指向新加入的节点
ptr.next=newnode
#最后将头指针指向新节点(注意是将新位置赋值给头指针,左右不要写反了)
head=newnode

链表

1、单链表:火车,节点由两个元素组成,数据value(数据值可能多个,一个框多个数据值)+指针next,指针会指向下一个元素的内存地址

只能单向遍历

链表的方法:

append  appendleft时间复杂度为o(1)

find  remove时间复杂度为o(n)

 

向尾部添加节点步骤:

①给新节点分配内存空间(类名对象的创建new_data=student(),类中value值定义,包括姓名和分数)

class student:

  def  ___init__(self):

        self.name=""

        self.score=0

        self.next=None

②链表尾部的指针next指向新元素所在的内存位置

③新节点的指针指向None

注:向单链表的头节点前插入新节点会多一个步骤:将原链表的头指针指向新的节点

遍历单向列表

定义一个结构指针ptr指向链表头部

ptr不断指向下一个节点,直到指向None结束(ptr=ptr.next)

 

2、双链表(烽火科技问到怎么向双端链表尾部插入元素):

既然是双向链表,左右都有None

为什么要使用双向链表:单向链表和环形链表只能单向遍历,如果某处断裂,后面的数据将无法获取。而双向链表左边的链断了就可以从右边开始遍历

 

优化find  remove时间复杂度为o(n)的问题,优化为o(1)问题

如果一节点的链接断开,可由反向链表进行遍历,从而可重建出完整的链表

无论是添加还是删除操作,都是从左指针/右指针开始指

 

数据value+2个指针(关键点就是多了一个指针,右指针指向后面的节点,左指针指向前面的节点),多了一个prevalue值,这个值可以向前指

双端链表尾部插入元素步骤:

①将链表最后一个节点的右指针指向新节点

②新节点左指针指向链表最后一个节点,新节点右指针指向None

双端链表任意位置插入元素步骤:

①将新节点加入链表ptr节点后

②ptr右指针指向新节点,新节点左指针指向ptr节点;新节点右指针指向ptr下一个节点,ptr下一个节点左指针指向新节点

双端链表头部插入元素步骤:原链表的头指针指向新节点

新节点右指针指向原链表的第一个节点,第一个节点左指针指向新节点

注:将新节点加入到双链表的第一个节点前会多一个步骤:将原链表的头指针指向新节点

添加节点

ptr=head

#左指针引入的是llink,右指针是rlink
#双端链表头加入节点
head.llink=newnode
newnode.rlink=head
head=newnode#新位置赋值给头指针

#双端链表尾加入节点,假设尾节点对应的指针目前为ptr
ptr.rlink=newnode
newnode.llink=ptr
newnode.rlink=None

#双端链表中加入节点,假设加入的位置已知为ptr
ptr.next=newnode
newnode.llink=ptr

newnode.rlink=ptr.next
ptr.rlink.llink=newnode

删除节点

head=head.rlink
#注意此时head的位置已经改变
head.llink=None

#删除尾节点,假设删除节点位置为ptr
ptr.llink.rlink=None

#删除中间节点,假设删除节点位置为ptr
ptr.llink.rlink=ptr.rlink
ptr.rlink.llink=ptr.llink

3、环形链表:摩天轮,可以从任何一个节点遍历链表上的所有节点,需要多一个指正空间,增删节点慢,因为每个节点必须处理两个指针

头指针,头节点区别:头指针指向头节点

如果链表头指针被破坏或遗失,整个链表就会遗失,并且浪费整个链表内存空间,因此考虑使用环形链表

把链表的最后一个节点的指针指向链表头部,而不是指向None,从而不用担心链表头指针遗失问题,因为每一个节点都可以是链表头部

判断一个链表是否为环形(Python方法)

 

堆栈:一个指针top            

#这里使用列表List实现的堆栈
max=100#设置堆栈的大小,表示可以存储100个值的堆栈
global stack#将堆栈初始化为全局变量
stack=[0]*max#构造列表,通过列表来实现的堆栈
top=-1#top被初始化为1

def isEmpty():#判断堆栈是否为空:通过top指针来判断
    if top==-1:
        return True
    else:
        return False    

#将数据压入堆栈
def push(data):#push需要传入数据
    global top
    global max
    global stack
    if top>=max-1:#索引是从0开始的,这里必须判断一下还能否加入数据
        print("堆栈已满")
    else:
        top=top+1
        stack[top]=data#存入数据
        
def pop():#pop不需要传入数据
    global top
    global max
    global stack
    if isEmpty():#这里必须判断一下堆栈是否为空,否则无法弹出数据
        print("堆栈是空")
    else:
        val=stack[top]
        top=top-1

同样也可以用链表来实现堆栈:算法复杂,但可以动态改变链表长度

列表实现堆栈:简单,但是必须声明列表的大小。如果堆栈本身大小是变动的,而列表声明过大时会浪费空间

 

  top指针指向堆栈顶端,空的堆栈顶端top指向-1,向其中push元素后top自加                                                                                                                               

设有编号1,2,3的三辆火车,顺序开入栈式结构的站台,则可能出栈序列有多少种?5种

总共有6种情况,123,132,213,231,312,321

而312的出栈方式不可能,3先出栈,则1,2必须一直在栈中,1,2顺序入栈后的出栈顺序必须是2先出栈,1后出栈

如132出栈过程:1入栈1出栈,23同时入栈,32出栈 ,简单画个图即可                                                                                                                            

TOP(PUSH(i,s))将元素i压如堆栈中,再返回堆栈租顶端的元素

堆栈的特殊应用:

结合堆栈计算算术表达式的值(包含赋值的字母,括号,运算符号,数字)

汉诺塔问题:堆栈的应用,移动n个盘子所需的最小移动次数为2^n-1

递归算法:堆栈的应用,程序如下:

def dif2(x):
    if x:
        dif1(x)

def dif1(y):
    if y>0:
        dif2(y-3)
    print(y,end=" ")
dif1(21)
print()输出结果为:3 6 9 12 15 18 21 

结果解释:每调一次dif1都会打印一次y值,递归后最先打印的是最后面的元素

 

 

队列:两个指针front和rear                                                                                 

同样也可以用列表和链表实现队列

与堆栈只需要一个top指针指向堆栈顶端不同的是:队列必须使用两个指针front和rear(队列尾)分别指向队列的前端和末尾

#这里使用列表List实现的队列
max=100#表示可以存储100个值的队列
queue=[None]*max#设置队列的大小
front=-1
rear=-1

def enqueue(data):
    global max
    global front
    global rear
    if rear==max-1: #在队列中加入数据是在rear后加入
        print("队列已满")
    else:
        rear+=1
        queue[rear]=data

def dequeue():
    global max
    global front
    global rear
    if front==rear:#通过两端指针是否指向同一个位置判断队列是否为空
        print("队列为空")
    else:
        front+=1
        val=queue[front]#取出队首元素

双端队列:队列的任意一端都能加入或者删除元素

优先队列:不必要遵守队列先进先出的特性。其中的每一个元素都赋予一个优先级,最高优先级的元素最先输出

当各个元素按输入先后次序为优先级时(优先级大的先输入队列),就是一般的队列;如果按输入先后次序的倒序作为优先级(低优先级先输入队列),此优先队列为一个堆栈

 

 

图(G(V,E))的遍历,从某一个顶点V1开始,遍历访问G中的其它顶点,直到全部的顶点遍历完毕

如V表示所有顶点的组合V={A,B,C,D,E},E表示所有边的组合E={(A,B)(A,E)(B,C)(C,D)(C,E)(D,E)},注意E表示的是边组合

图遍历方法:深度优先搜索/遍历(DFS,deep-first search)和广度优先遍历(BFS,breadth-first search)

①深度优先遍历:从图的某一顶点开始遍历,被访问的顶点做上已 访问记号,接着遍历此顶点相连且未访问过的顶点中的任意一个顶点,继续做上标记

用到的数据结构:

堆栈,取栈顶元素,弹出的点即为标记点

递归

算法实现:书本231,注意两个遍历流程图区别

②广度优先遍历

用到的数据结构:

队列,取队列头的元素,取出的元素即为标记点

递归

DFS:类似前序遍历,将与根节点相连的节点压入堆栈,弹出栈顶元素(从上图横条右边取),并把与弹出元素相连且未访问的元素压入栈中

BFS:将与根相连的节点加入到队列中,取出队首元素(从上图横条左边取),并把与对手元素相连且未访问的元素压加入到队列中

DFS的另一种结果:1354276(是否正确?)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值