python数据结构学习笔记

链表定义:

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

生成一个无头链表head:

cur = Lnode(1)
head = cur 
i = 2
while i<8 :
    tmp = Lnode(i)
    tmp.next = None
    cur.next = tmp
    cur = cur.next
    i+=1

链表逆序(带头节点):
法一(有无头节点都一样):就地逆序,复杂

def Reverse(head):
    if head==None or head.next==None or head.next.next==None:
        return
    pre=None
    cur=None
    next=None
    cur=head.next
    next=cur.next
    cur.next=None
    pre=cur
    cur=next
    while cur.next !=None:
        next=cur.next
        cur.next=pre
        pre=cur
        cur=next
    cur.next=pre
    head.next=cur

if __name__ == "__main__":
    i=1
    head=LNode(7)
    head.next=None
    cur=head
    tmp=None
    while i<8:
        tmp=LNode(i)
        tmp.next=None
        cur.next=tmp
        cur=cur.next
        i+=1
    print("逆序前:")
    cur=head.next
    while cur!=None:
        print(cur.data)
        cur=cur.next
    Reverse(head)
    print("逆序后:")
    cur=head.next
    while cur!=None:
        print(cur.data)
        cur=cur.next

法二:递归法

class Lnode:
    def __init__(self,x):
        self.data = x
        self.next=None
    
def RecursiveReverse(head):
    if head==None or head.next==None:
        return head
    else:
        newhead = RecursiveReverse(head.next)
        head.next.next=head
        head.next=None
    return newhead

def Reverse(head):
    if head == None or head.next==None:
        return
    else:
        newhead = RecursiveReverse(head.next)
    head.next=newhead
    return head

法三:插入法

class Lnode:
    def __init__(self,x):
        self.data = x
        self.next = None
  
def Reverse(head):
    if head == None or head.next == None or head.next.next == None:
        return 
    else:
        cur = head.next.next
        post = None
        head.next.next = None
        while cur != None:
            post = cur.next
            cur.next = head.next
            head.next = cur
            cur = post
    return head

算法分析:
三种方法的复杂度都是O(N),法一需要3个变量来保存前驱结点、当前结点、后继结点,法二和法三只需要保存当前结点、后继结点,不需要保存前驱节点,法二需要不断调用自己,需要额外的压栈和弹栈操作,与法一和法三相比性能会有所下降。
链表逆序(不带头节点):
递归法:

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

def RecursiveReversePrint(head):
    if head.next==None:
        print(head.data)
    else:
       RecursiveReversePrint(head.next)
       print(head.data)

逆序输出列表:
法一:改变链表结构,直接将列表逆序,再顺序输出

法二:用新的链表逆序存储,再顺序输出

法三:递归输出

def RecursiveReversePrint(head):
    if head.next==None:
        print(head.data)
    else:
       RecursiveReversePrint(head.next)
       print(head.data)

算法分析:
三种方法时间复杂度都为O(N),法一的缺点是改变了链表的结构,法二的缺点是需要额外的存储空间,法三的缺点是需要不段调用自己,需要额外的压栈和弹栈操作。

从无序列表中删除重复项
法一:顺序法

def DeleteDuplicate(head):
    if head ==None :
        return
    else:
        outernode = head.next
        innernode = None
        innerprenode = None
        while outernode != None:
            innerprenode = outernode
            innernode = outernode.next
            while innernode != None:
                if innernode.data == outernode.data:
                    innerprenode.next = innernode.next
                    innernode = innernode.next
                else:
                    innerprenode = innernode
                    innernode = innernode.next
            outernode = outernode.next

法二:`

def DeleteDupRecussion(head):
    if head == None or head.next == None:
        return head
    head.next = DeleteDupRecussion(head.next)
    pre = head
    while pre.next != None:
        if head.data == pre.next.data:
            pre.next = pre.next.next
        else:
            pre = pre.next
    return head

法一法二思路差不多,只不过一个是按顺序删除,一个是逆序删除,时间复杂度都是O(N),法二用了递归,需要不断调用自己,性能相对法一低一些。

从有序列表中删除重复项:

def DeleteDupordered(head):
    if head == None or head.next == None:
        return head
    cur = head
    if cur.data == cur.next.data:
        
        cur.next = cur.next.next
    else:
        cur = cur.next
    return head

算法分析:
时间复杂度为O(N)

两数相加,个位数在表头

def AddTwoNum(list1,list2):
    if list1 == None and list2 == None:
        return 
    fnode = list1
    snode = list2
    addnode = Lnode(1)
    head = addnode
    carrynode = 0
    numnode = 0
    while fnode != None or snode != None or carrynode != 0:
       carrynode, numnode = np.divmod((fnode.data if fnode else 0) + (snode.data if snode else 0) + carrynode,10)
       addnode.next = Lnode(numnode)
       addnode = addnode.next
       
       fnode = fnode.next
       snode = snode.next
    return head.next

算法分析:
时间复杂度为O(N),空间复杂度为O(N)。三个注意要点:1)两个链表的长度不一;2)要记录进位;3)遍历完链表后,还要考虑是否有进位。

对链表进行特殊排序:
L1 → \rightarrow L2 → \rightarrow L3 → \rightarrow → \rightarrow Ln 变为 L1 → \rightarrow Ln → \rightarrow L2 → \rightarrow Ln-1 → \rightarrow

def FindMidNode(head):
    if head == None or head.next == None or head.next.next == None:
        return
    long = head
    short = head
    while  long.next !=None:
        if long.next.next == None:
            break
        long = long.next.next
        short = short.next
    list2 = short.next
    short.next = None
    list1 = head
    return list1, list2

将链表从中间切开,后半段结点数大于等于前半段的结点数,返回两段链表的首结点,long和short两个结点指针从首结点开始走,long每次走两步,short每次走一步,当long后面只剩一个结点或没有结点时,long停止走,此时short指向的结点为中间偏前的结点。容易犯错的地方:判断long后面只剩一个结点或没有结点,不能用条件
while long.next.next !=None:,因为当后面没有结点时long.next.next不存在。

def Reversehalf(head):
    if head.next == None:
        return
    cur = head
    next = cur.next
    cur.next = None
    pre = cur
    cur = next
    while cur.next != None:
        next = cur.next
        cur.next = pre
        pre = cur
        cur = next  
    cur.next = pre
    return cur

对后半链表原地逆序

def ConnectTwo(list1, list2):
    cur1 = list1
    next1 = cur1
    cur2 = list2
    next2 = cur2
    while cur1.next != None:
        next1 = cur1.next
        cur1.next = cur2
        cur1 = next1
        next2 = cur2.next
        cur2.next = cur1
        cur2 = next2
    if cur1.next == None:
        cur1.next = cur2
    return list1

将后半链表的元素按顺序依次插入前半段链表相邻的两元素之间

找出没有头节点的单链表的倒数第k个元素:
思路:两个指针均指向首结点,其中一个指针先走k-1步,然后两个指针同时走,直到快指针所指向的元素的下一个元素为空,此时慢指针指向的元素即为倒数第k个元素。

def ReverseNumk (head,k):
    
    flist = head
    slist = head
    i = 1
    while i < k and flist.next != None:
        flist = flist.next
        i += 1
    if i < k:
        return None
    while flist.next != None:
        flist = flist.next
        slist = slist.next
    return slist.data

if __name__ == '__main__':
    head = LNode(1)
    cur = head
    tmp = None
    i = 2
    while i < 8:
        tmp = LNode(i)
        cur.next = tmp
        cur = cur.next
        i += 1
    cur = head
    print("原列表为:", end=' ')
    while cur != None:
        print(cur.data, end= ' ')
        cur = cur.next
    k = input("input:")
    k = int(k)
    num = ReverseNumk (head,k)
    print("列表的倒数第%d个元素为:" %k,end=' ')
    if num == None:
        print("找不到")
    else:
        
        print(num)
#    没有注意到的细节:对列表长度是否达到k的判断

算法分析:
链表只用遍历一次,故时间复杂度为O(N),只用到了两个指针,故空间复杂度为O(1)。

检测一个单链表是否有环,并找出环的入口点位置
若一个单链表有环,则形状应该像一个顺时针旋转90度得数字9
现有用两个指针,快指针f,慢指针s,快指针每次走两步,慢指针每次走一步
若把两个把这两个指针放在一个圆环里走,且起点相同,则两指针首次相遇的时候,快指针刚好比慢指针多走一圈。
设单链表总长度为L,环长度为r,快慢指针开始均指向单链表的首结点,则当慢指针走L-r步时刚好到环的入口,此时快指针比慢指针多走L-r步,快指针在慢指针的前(L-r)%r个结点处,因此假设慢指针再走x步两针相遇,则根据快慢指针在圆环里相遇时的步数规律有
( L − r ) % r + x = r (L-r)\%r + x=r (Lr)%r+x=r

L − r + x = n r L-r+x=nr Lr+x=nr
n为正整数, L − r + x L-r+x Lr+x是慢指针走的步数。
判断单链表是否有环:
用两个指针,快指针f,慢指针s,两指针同时指向起点,快指针每次走两步,慢指针每次走一步,每次判断两指针是否相等,如果相等则证明单链表有环
找环的入口点:
变换一下上式,可以得出另外一个规律
L − r = ( n − 1 ) r + ( r − x ) L-r = (n-1)r+(r-x) Lr=(n1)r+(rx)
L − r L-r Lr是起点走到入口点的步数, r − x r-x rx是相遇点沿着环向前走到入口点的步数
由此可得出若一个指针指向起点,一个指针指向相遇点,两指针同时向前走,每次走一步,则首次相遇于入口点,由此可找出入口点的位置

class LNode:
   def __init__(self,x):
       self.data = x
       self.next = None
def FindCircle(head):
    slow = head
    fast = head
    if fast == None or fast.next == None:
        return False, 1, 2
    while fast.next != None:
        if fast.next.next == None:
            return False, 1, 2
        else:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                return True, head, fast
def FindCircledoor(startpoint, meetpoint):
    if startpoint == None or meetpoint == None:
        return None
    while startpoint != meetpoint:
        startpoint = startpoint.next
        meetpoint = meetpoint.next
    return startpoint
    
       
if __name__ == "__main__":
    circledoor = None
    head = LNode(1)
    i = 2
    cur = head
    tmp = None
    while i<8:
        tmp = LNode(i)
        cur.next = tmp
        if i==4:
            circledoor = cur
        cur = cur.next
        i +=1
    cur.next = circledoor
    ret, startpoint, meetpoint = FindCircle(head)
    if ret == 0:
        print('there is no circle')
    else:
        print('there is a circle,', end=' ')
        meetpoint = FindCircledoor(startpoint, meetpoint)
        if meetpoint != None:
            md = meetpoint.data
            print('the circle entry is %d :'%md)

性能分析:该方法对链表只遍历一次,时间复杂度为O(n),只用几个指针保存结点的地址,空间复杂度为O(1)
错点:不可以同时返回两个以上None。

排序

快速排序

def partion(arr, low, high):
    pivot = arr[high]
    i = low-1
    for j in range(low, high):
        if arr[j] < pivot:
            i +=1
            arr[i], arr[j] = arr[j], arr[i]
    arr[i+1], arr[high] = arr[high], arr[i+1]
    return(i+1)


  
 
# arr[] --> 排序数组
# low  --> 起始索引
# high  --> 结束索引
  
# 快速排序函数
def quickSort(arr,low,high): 
    if low < high: 
  
        pi = partion(arr,low,high) 
  
        quickSort(arr, low, pi-1) 
        quickSort(arr, pi+1, high) 
  
arr = [1, 7, 8, 9, 1, 5] 
n = len(arr) 
quickSort(arr,0,n-1) 
print ("排序后的数组:") 
for i in range(n): 
    print ("%d" %arr[i])

每次排序取列表最后一个数字作为参考,比它小的放在左边,比它大的放在右边,最后把它放中间【两个指针,一个指针遍历列表至倒数第二个元素,前面的指针判断指向的值是否和参考值相等,当小于参考值时后面的指针也跟着走一步,否则前面的指针走后面的指针不走,这样保证在前一个指针(包括它所指向的元素)一定比参考值小,后面再出现比参考值小的元素就把它放在后面指针指向元素的后面,再让后面指针往前走一步,遍历完之后把参考值和后面指针指向元素的后一个元素交换】让后对参考值左边和右边的列表(均不包括参考值)递归进行同样的操作。
平均事件复杂度为O(nlogn)

选择排序

def select_sort(arr):
    if len(arr) <= 1:
        return(arr)
    else:
        for i in range(len(arr)-1):
            for j in range(i+1, len(arr)):
                if arr[j] < arr[i]:
                    arr[j], arr[i] = arr[i], arr[j]
    return arr

保证每个元素后面的元素都比它小
时间复杂度为O( n 2 n^2 n2)

归并排序

import math
def merge(left, right):
    i,j = 0, 0
    result = []
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j +=1
        
    result += (left[i:])
    result += (right[j:])
    return result

def merge_sort(arr):
    arr = list(map(int,arr))
    if len(arr)==1:
        return arr
    num = math.floor(len(arr)/2)-1
    left = merge_sort(arr[:num+1])
    right = merge_sort(arr[num+1:])
    return merge(left, right)

if __name__ == "__main__":
    arr = [1,3, 6,2, 4, 8]
    print(merge_sort(arr))

画二叉树进行理解,时间复杂度O(nlogn)

import math
def adjust_heap(lists, i, size):
    lchild = 2*i+1
    rchild = 2*i +2
    maxs = i
    if i < math.floor(size/2):
        if lists[lchild] >lists[maxs] and lchild<size:
            maxs = lchild
        if lists[rchild] > lists[maxs] and rchild<size:
            maxs = rchild
        if i != maxs:
            lists[maxs],lists[i] = lists[i], lists[maxs]
            adjust_heap(lists, maxs,size)
def build_heap(lists, size):
    for i in range(math.floor(size/2))[::-1]:
        adjust_heap(lists, i, size)
def heap_sort(lists):
    size = len(lists)
    build_heap(lists,size)
    for i in range(size)[::-1]:
        lists[0],lists[i] = lists[i],lists[0]
        adjust_heap(lists,0,i)     
 if __name__ == '__main__':
    lists=[3,4,2,8,9,5,1]
    print('排序前:',end='')
    print(lists)
    heap_sort(lists)
    print('排序后:',end='')
    print(lists)                      

基数排序

def radix_sort(s):
    maxnum = max(s)
    le = len(str(maxnum))#求最大迭代次数,对每个位数迭代一次
    for i in range(le):
        bucket = [[] for k in range(10)]#第k次迭代要用的篮子,第i+1位数是几就放入第几个篮子
        for j in s:
            bucket[(j//10**i)%10].append(j)#计算要放入的第几个篮子
        s.clear()
        for x in bucket:#重新把数据从篮子里取出,放到列表中存储
            for y in x:
                s.append(y)

if __name__ == "__main__":
    a = [73, 22, 93, 43, 55, 14, 28, 110, 39, 81]
    radix_sort(a)
    print(a)

希尔排序
希尔排序是对插入排序的改进,插入排序适合基本有序和小规模数据集希尔排序就次对插入排序进行改进

def shell_sort(lists):
    gap = len(lists)//2
    while gap >=1:
        for i in range(len(lists)):
            j = i
            while lists[j] < lists[j-gap] and j >= gap:
                lists[j], lists[j-gap] = lists[j-gap], lists[j]
                j -=gap
        gap = gap//2
        
import random, copy, operator

k = 0
res = 0
t = 1000

while(k < t):
    A = [random.randint(-5,5) for x in range(100)]
    B = copy.copy(A)

    shell_sort(A)
    B.sort()

    res += int(operator.eq(A,B))    
    k += 1

if res == t:
    print("Pass!")
else:
    print("Wrong!")    

堆排序
把列表看成是一颗完全二叉树,保证每次树的头结点都比其所有子结点大,然后取出头结点,和树的最后一个结点交换,对去掉取出的结点剩下的树进行同样的操作

import math
def adjust_heap(lists, i, size):#保证父节点比它所有的子节点都大
    lchild = 2*i+1
    rchild = 2*i +2
    maxs = i
    if i < math.floor(size/2):#保证i是非叶子结点
        if lists[lchild] >lists[maxs] and lchild<size:#lchild<size保证左子树是存在的
            maxs = lchild
        if lists[rchild] > lists[maxs] and rchild<size:
            maxs = rchild
        if i != maxs:
            lists[maxs],lists[i] = lists[i], lists[maxs]
            adjust_heap(lists, maxs,size)
def build_heap(lists, size):#保证所有父节点都比其子节点大
    for i in range(math.floor(size/2))[::-1]:
        adjust_heap(lists, i, size)
           
def heap_sort(lists):
    size = len(lists)
    build_heap(lists,size)
    for i in range(size)[::-1]:
        lists[0],lists[i] = lists[i],lists[0]#把最大的结点拿出来
        adjust_heap(lists,0,i)     #对除最大结点之外的结点重新建堆,是最大结点放在第一个位置
if __name__ == '__main__':
    lists=[3,4,2,8,9,5,1]
    print('排序前:',end='')
    print(lists)
    heap_sort(lists)
    print('排序后:',end='')
    print(lists)      
                
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值