0
点赞
收藏
分享

微信扫一扫

python算法复习(二)----链表和二叉树

雪域迷影 2022-04-16 阅读 52
# 本节讲的内容是链表和双链表结构,以及和链表相似度很高的二叉树结构
import sys


class LinkNode(object):
    def __init__(self, value=None):
        self.value = value
        self.next = None

    def __repr__(self):
        return str(self.value)


class DoubleLinkNode(object):
    def __init__(self, value=None):
        self.value = value
        self.next = None
        self.last = None

    def __repr__(self):
        return str(self.value)


class TreeNode(object):
    def __init__(self, value=None):
        self.value = value
        self.left = None
        self.right = None

    def __repr__(self):
        return str(self.value)


# 以上便是三个数据结构的基本节点结构,不难发现三者的相似度之高


# 逆序链表
# 单链表的逆序:双指针,一个指向当前节点,一个指向下一个节点。记录p2的下一个节点,让p2的next指向p1。当p2为None意味着p1是最后一个节点,跳出循环。
# 双链表的逆序:双指针,一个指向上一个节点,一个指向当前节点。记录head的下一个节点,让head的next指向p1,last指向下一个节点。当前节点是None意味着p1是最后一个节点。
def inverted_list(head):
    if head.next == None:
        return head
    p1 = head
    p2 = head.next
    while p2 != None:
        t = p2.next
        p2.next = p1
        p1 = p2
        p2 = t
    head.next = None
    return p1


def inverted_Double_list(head):
    if head.next == None:
        return head
    p1 = None
    while head != None:
        p2 = head.next
        head.next = p1
        head.last = p2
        p1 = head
        head = p2
    return p1


# 打印两个有序链表的公共部分
# 给定两个有序链表的头指针head1和head2,打印两个链表的公共部分
# 解题思路:双指针,一个指向head1,一个指向head2.哪个value小就移动谁。
def find_common_part(head1, head2):
    ans = []
    while head1 != None and head2 != None:
        if head1.value < head2.value:
            head1 = head1.next
        elif head1.value > head2.value:
            head2 = head2.next
        else:
            ans.append(head1.value)
            head1 = head1.next
            head2 = head2.next
    return ans


# 判断一个链表是否为回文结构
# 给定一个单链表的头节点head,请判断该链表是否为回文结构。
# 解题思路:设置快慢指针,快指针走两个,慢指针走一个。
#         然后慢指针的next指向None,后面的全部逆序。再从head和fast一起往中间走,直到head指向None,如果中间用一个相等就返回False
def isPalindrome(head):
    fast = head
    slow = head
    while fast.next != None and fast.next.next != None:
        fast = fast.next.next
        slow = slow.next
    if fast.next != None:
        # 偶数的话,慢指针停在中点的前一个
        fast = fast.next
    t = slow.next
    slow.next = None
    while t != None:
        p = t.next
        t.next = slow
        slow = t
        t = p

    while True:
        if head == None:
            return True
        if head.value != fast.value:
            return False
        head = head.next
        fast = fast.next


# 将单链表按照num划分成左边小、中间相等、右边大的形式
# 定义六个指针每两个代表小于、等于、大于的三条链表,遍历一边链表再把三条链表串起来
def list_partition(head, num):
    lessH = LinkNode()
    lessT = LinkNode()
    equalH = LinkNode()
    equalT = LinkNode()
    moreH = LinkNode()
    moreT = LinkNode()
    while True:
        if head == None:
            break
        if head.value < num:
            if lessH.value == None:
                # 如果链表为空,就把头尾都定为这个值,因为是单链表所以不用担心值重复,因为这里是自己指向自己
                lessH = head
                lessT = head
            else:
                # 如果已经有值了,就把尾连向head
                lessT.next = head
                lessT = head
        elif head.value == num:
            if equalH.value == None:
                equalH = head
                equalT = head
            else:
                equalT.next = head
                equalT = head
        elif head.value > num:
            if moreH.value == None:
                moreH = head
                moreT = head
            else:
                moreT.next = head
                moreT = head
    if lessH.value != None:
        lessT.next = equalH
        equalT.next = moreH
        return lessH
    elif equalH.value != None:
        equalT.next = moreH
        return equalH
    else:
        return moreH


# 复制含有随机指针节点的链表
# rand指针是单链表结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向None。请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点
# 解题思路:在原链表的两个节点之间,都放上复制节点,例如123,变成112233.
#          从头遍历节点,每次跳两个保证遍历的是原链表,让next节点(即复制节点)的rand指针指向原链表节点的rand指针的next,保证了复制节点指向的也是复制节点
#          再从头遍历,断开原节点和新节点,让新节点之间的next指针相连,返回复制节点的头
class Rand_list(object):
    def __init__(self, value=None):
        self.value = value
        self.next = None
        self.rand = None

    def __repr__(self):
        return str(self.value)


def copy_randlist(head):
    if head.next == None:
        cp1 = head
        cp1.next = head.next
        cp1.rand = head.rand
        return cp1
    head2 = head
    while head2 != None:
        cur = head2.next
        p = LinkNode(head2.value)
        p.next = head2.next
        head2.next = p
        head2 = cur

    head2 = head
    while head2 != None:
        if head2.rand != None:
            head2.next.rand = head2.rand.next
        else:
            head2.next.rand = None
        head2 = head2.next.next

    cp1 = head.next
    while head.next.next != None:
        head2 = head.next.next
        head.next.next = head.next.next.next
        head = head2
    return cp1


# 两个单链表相交的一系列问题
# 给定两个可能有环可能无环的单链表,头节点head1和head2.实现一个函数,如果两个链表相交,返回相交的第一个节点。如果不相交返回None
# 解题思路:
#   1.首先要判断是否成环:用快慢执政遍历链表,如果快指针到了None则是无环链表。如果快慢指针重合了,将快指针指到head,两个指针一次遍历一个,再重合的位置就是入环点
#   2.如果两个都是无环:如果相交,那么肯定是呈现为"Y"字形式,否则交点就有两个next指针.把两个链表长度相减,长链表先走n步,再两个链表一起走,都走到同一个位置就是交点
#   3.如果一个是有环一个无环:不相交,因为如果相交肯定会有两个next指针
#   4.如果两个都是有环链表:如果相交肯定是公用一个环,否则交点会有两个next指针.
#       如果两个入环点相同,意味着在入环之前结构就像情况2的"Y"字结构一样.按照情况二解决
#       如果入环点不同,从一个入环点往下走,如果在回到这个入环点之前遇到了另外一个入环点,则这就是相交点,否则不相交
def isloop(head):
    p1 = head
    p2 = head
    count = 0
    while True:
        if p2.next == None or p2.next.next == None:
            if p2.next == None:
                count += 1
            else:
                count += 2
            return False, None, count
        p1 = p1.next
        p2 = p2.next.next
        count += 2
        if p1 == p2:
            break
    p2 = head
    while True:
        p1 = p1.next
        p2 = p2.next
        if p1 == p2:
            return True, p1, 0


def isintersect_list(head1, head2):
    isloop1, inloop1, lenlink1 = isloop(head1)
    isloop2, inloop2, lenlink2 = isloop(head2)
    if isloop1 != isloop2:
        return None

    if isloop1 == False:
        n = lenlink2 - lenlink1
        if n > 0:
            cur2 = head2
            cur1 = head1
            for i in range(n):
                cur2 = cur2.next
        else:
            cur1 = head1
            cur2 = head2
            for i in range(-n):
                cur1 = cur1.next
        while cur1 != cur2:
            if cur1 == None or cur2 == None:
                return None
            cur1 = cur1.next
            cur2 = cur2.next
        return cur1

    else:
        if inloop1 == inloop2:
            cur1 = head1
            cur2 = head2
            lenlink1 = 1
            lenlink2 = 1
            while cur1 != inloop1:
                cur1 = cur1.next
                lenlink1 += 1
            while cur2 != inloop2:
                cur2 = cur2.next
                lenlink2 += 1
            n = lenlink2 - lenlink1
            if n > 0:
                cur2 = head2
                cur1 = head1
                for i in range(n):
                    cur2 = cur2.next
            else:
                cur1 = head1
                cur2 = head2
                for i in range(-n):
                    cur1 = cur1.next
            while cur1 != cur2:
                if cur1 == inloop1 or cur2 == inloop2:
                    break
                cur1 = cur1.next
                cur2 = cur2.next
            return cur1
        else:
            cur1 = inloop1
            while True:
                cur1 = cur1.next
                if cur1 == inloop2:
                    return inloop2
                if cur1 == inloop1:
                    return None


'''
    最后的链表相交问题是链表问题的精髓,链表问题实际上就是需要考虑不同情况下的取舍问题.
    下面加入二叉树内容
'''


# 用递归方法是先二叉树的先序\中序\后序遍历
# 先序\中序\后序的意思就是,先\中\后次序遍历树的头节点
# 所以先序:中-->左-->右
#     中序:左-->中-->右
#     后序:左-->右-->中
def recursion_preorder(root, res):
    if root == None:
        return
    res.append(root)
    recursion_preorder(root.left, res)
    recursion_preorder(root.right, res)
    return res


def recursion_inorder(root, res):
    if root == None:
        return
    recursion_inorder(root.left, res)
    res.append(root)
    recursion_inorder(root.right, res)
    return res


def recursion_postorder(root, res):
    if root == None:
        return
    recursion_postorder(root.left, res)
    recursion_postorder(root.right, res)
    res.append(root)
    return res


# 用非递归方法是先二叉树的先序\中序\后序遍历
# 先序遍历:因为是中左右的打印方式,所以按照右左的顺序压入栈中,每次弹出头的时候都执行,就能保证先打印头节点再打印左树再打印右树
# 中序遍历:因为是左中右的打印方式,所以一次性把左边界压入栈中,每次弹出的时候重复以上
# 后序遍历:因为是左右中的打印方式,所以按照左右(因为后面要放到另外一个栈,所以按照预取顺序放入)的顺序压入栈中,然后把弹出的头节点压倒另一个栈里
def preorder(root, res):
    stack = []
    stack.append(root)
    res = []
    while stack != []:
        root = stack.pop()
        res.append(root)
        if root.right != None:
            stack.append(root.right)
        if root.left != None:
            stack.append(root.left)
    return res


def inorder(root, res):
    stack = []
    while root != None:
        stack.append(root)
        root = root.left
    while stack != []:
        root = stack.pop()
        res.append(root)
        if root.right != None:
            root = root.right
            while root != None:
                stack.append(root)
                root = root.left
    return res


def postorder(root, res):
    stack1 = []
    stack2 = []
    stack1.append(root)
    while stack1 != []:
        root = stack1.pop()
        stack2.append(root)
        if root.left != None:
            stack1.append(root.left)
        if root.right != None:
            stack1.append(root.right)
    while stack2 != []:
        res.append(stack2.pop())
    return res


# 总结一下:把中看作为第一轮压栈弹出的东西就很好理解了
#   先序:想得到的顺序是中左右,所以弹出节点的时候先压右在压左就好了.
#   中序:想得到的顺序是左中右,意味着在弹出之前就头节点之前要先得到左孩子,所以就要一次性把所有的左孩子压入栈,这样弹回的顺序就是左中.
#       那么剩下右孩子怎么取到呢,在得到弹出的头节点的时候,把右孩子压入栈.同样的,自然也需要把右孩子的所有的左孩子都要压入栈.
#   后序:因为我们想得到左右中,怎么样才能让最先弹出的反而最后得到呢,那就是栈.
#       所以我们把第一个栈弹出的放到另一个栈里,剩下的预期顺序为先左再右.所以第二个栈里的关系应该是先压右再压左;所以压入第一个栈的顺序就是先压左再压右


# 层次遍历(宽度优先遍历),顺便求宽度
# 因为是一层一层从左往右遍历,所以使用队列
def level(root, res):
    queue = []
    queue.append(root)
    width = []
    expectcount = 1
    nowcount = 1
    nextcount = 0
    while queue != []:
        root = queue.pop(0)
        res.append(root)
        if root.left != None:
            queue.append(root.left)
            nextcount += 1
        if root.right != None:
            queue.append(root.right)
            nextcount += 1

        if nowcount >= expectcount:
            width.append(nowcount)
            expectcount = nextcount
            nowcount = 1
            nextcount = 0
        else:
            nowcount += 1

    return res, width


# 搜索二叉树(Binary Search Tree)
# 搜索二叉树的 left<root<right.注意:右树所有的节点都要大于头节点,左树所有的节点都要小于头节点
# 解题思路: 观察搜索二叉树的大小关系,很容易发现正好是中序遍历的顺序.
def isBST(root):
    stack = []
    while root != None:
        stack.append(root)
        root = root.left
    root = stack.pop()
    criterion = root.value
    while stack != []:
        root = stack.pop()
        if criterion >= root.value:
            return False
        criterion = root.value
        if root.right != None:
            root = root.right
            while root != None:
                stack.append(root)
                root = root.left
    return True


def recursion_isBST(root, themin_by_isBST):
    if root == None:
        return True
    left = recursion_isBST(root.left, themin_by_isBST)
    if left == False:
        return False
    if root.value <= themin_by_isBST:
        return False
    else:
        themin_by_isBST = root.value
    return recursion_isBST(root.right, themin_by_isBST)


# 完全二叉树(complete binary tree)
# 完全二叉树:最后一层是从左到右一次变满的,其他层都是满的.(任意节点有右无左则Flase,如果是叶子节点,则后续所有节点都不能有左右孩子)
def isCBT(root):
    queue = []
    isNotLeaf = True
    queue.append(root)
    while queue != []:
        root = queue.pop(0)
        if isNotLeaf:
            if root.left != None:
                queue.append(root.left)
            if root.right != None:
                if root.left != None:
                    return False
                if root.left == None:
                    queue.append(root.left)
            if root.right == None:
                isNotLeaf = False
        else:
            if root.left != None or root.right != None:
                return False
    return True


''' 
    二叉树套路:左右子树分别获取一个值,对这个值做处理,再判断(树形dp的雏形)
'''


# 满二叉树
# 满二叉树:每一层的节点个数都是满的
def isFBT(root):
    if root == None:
        return True, 0
    left = isFBT(root.left)
    right = isFBT(root.right)
    if left[0] == False or right[0] == False:
        return False, -1
    if left[1] != right[1]:
        return False, -1
    return True, left[1] + right[1] + 1


# 平衡二叉树
# 平衡二叉树左右的高度差不超过1
def isBBT(root):
    if root == None:
        return True, 0
    left = isBBT(root.left)
    right = isBBT(root.right)

    if left[0] and right[0] and abs(left[1] - right[1]) < 2:
        return True, max(left[1], right[1]) + 1
    else:
        return False, -1


# 搜索二叉树
# 左侧是搜索二叉树,右侧也是搜索二叉树,左侧的最大值小于头节点,右侧的最小值大于头节点
def isBST2(root):
    if root == None:
        return None
    left = isBST2(root.left)
    right = isBST2(root.right)

    TORF = True
    themin = root.value
    themax = root.value
    if left != None:
        themin = min(left[1], themin)
        themax = max(left[2], themax)
        if left[0] == False or left[2] >= root.value:
            TORF = False
    if right != None:
        themin = min(right[1], themin)
        themax = max(right[2], themax)
        if right[0] == False or right[1] <= root.value:
            TORF = False
    return [TORF, themin, themax]


# 查找O1、O2的最低公共点
# 解题思路:有两种情况,一个是O1在O2的子树上,一个是不在O2的子树上。从底层出发,如果遇到了的话就带着信息往上走,直到两个都找到
def common_point(root, O1, O2):
    if root == None or root == O1 or root == O2:
        return root
    left = common_point(root.left)
    right = common_point(root.right)

    if left != None and right != None:
        return root
    if left != None:
        return left
    else:
        return right


# 查找一个节点的后继节点(中序遍历的后一个节点),节点拥有父节点信息
# 左中(左中(左中))
# 实际上这道题考的就是为中序遍历的理解,查找一个节点的后续节点,只有两种可能,一个是自己是中,一个是自己是左
# 如果自己有右树,意味着自己是中,返回右树的最左下节点
# 如果自己没有右树且自己是父节点的左节点,意味着是自己是左,返回自己的父节点
# 如果自己没有右树且自己是父节点的右节点,意味着自己是在一棵树的右下位置,这时候这棵树中的其他节点都已经被遍历过了。需要一直往上找,找到一个符合情况二的节点
class father_treenode(object):
    def __init__(self, value, father):
        self.value = value
        self.left = None
        self.right = None
        self.father = father


def find_nextnode(root):
    if root.right != None:
        root = root.right
        while root.left != None:
            root = root.left
        return root
    else:
        while root.father.left != root and root.father == root:
            root = root.father
        if root.father == root:
            return None
        else:
            return root.father


# 二叉树的序列化和反序列化
# 本体所有的可以套用所有的遍历方法,只要序列号和反序列化按照选择同一种遍历方法
def tree_encode(root, res):
    if root == None:
        res.append("#")
        return
    res.append(root.value)
    tree_encode(root.left, res)
    tree_encode(root.right, res)
    return res


def decode(res):
    node = res.pop(0)
    if node == "#":
        return
    node = TreeNode(node)
    left = decode(res)
    right = decode(res)
    node.left = left
    node.right = right
    return node


# 折纸问题
# 把一段纸条竖着放在桌子上,然后从纸条的下边向上对折一次,压出折痕后展开。把这种折痕叫做凹折痕。
# 请回答出从下向上折n次后的折痕
# 解题思路:画图可知,每一次对折实际上就是叶节点的一个分支。每一次分支都是上凹下凸.而且按照从上往下的顺序读取,可以发现正好是中序遍历的顺序
def paper_crease (i,n,isconvex,res):
    if i>n:
        return
    paper_crease(i+1,n,False,res)
    if isconvex==False:
        res.append("凹")
    else:
        res.append("凸")
    paper_crease(i+1,n,True,res)
    return res

举报

相关推荐

0 条评论