当前位置:   article > 正文

Python数据结构与算法——数据结构(链表、哈希表、树)_python遍历链表

python遍历链表

目录

链表

    链表介绍

    创建和遍历链表

    链表节点插入和删除

    双链表

    链表总结——复杂度分析

哈希表(散列表)

哈希表介绍

哈希冲突

哈希表实现

哈希表应用

树的示例——模拟文件系统

二叉树

二叉树的链式存储

 二叉树的遍历

二叉搜索树

插入

查询

删除

AVL树

旋转

插入


链表

    链表介绍

        链表是由一系列节点组成的元素集合,每个节点包含两部分,数据域item和指向下一个节点的指针next。通过节点之间的相互连接,最终串联成一个链表。

节点定义:

  1. class Node(object):
  2. def __init__(self, item):
  3. self.item = item
  4. self.next = None

    创建和遍历链表

        头插法/尾插法

  1. class Node:
  2. def __init__(self, item):
  3. self.item = item
  4. self.next = None
  5. # 创建链表(头插法)
  6. def crweate_linklist_head(li):
  7. head = Node(li[0])
  8. for element in li[1:]:
  9. node = Node(element)
  10. node.next = head
  11. head = node
  12. return head
  13. # 创建链表(尾插法)
  14. def crweate_linklist_tail(li):
  15. head = Node(li[0])
  16. tail = head
  17. for element in li[1:]:
  18. node = Node(element)
  19. tail.next = node
  20. tail = node
  21. return head
  22. # 打印链表(遍历)
  23. def print_linklist(lk):
  24. while lk:
  25. print(lk.item, end=',')
  26. lk = lk.next
  27. print()

    链表节点插入和删除

        插入是创建一个节点,将该节点指向插入位置的下一个节点,上一个节点指向该节点。

        删除是将上一个节点的next指向下一个节点。

    双链表

        双链表每个节点有两个指针:一个指向后一个节点,另一个指向前一个节点。

节点定义:

  1. class Node(object):
  2. def __init__(self, item=None):
  3. self.item = item
  4. self.next = None
  5. self.prior = None

    链表总结——复杂度分析

  •         按元素值查找:O(n)
  •         按下标查找:O(n)
  •         在某元素后插入:O(1)
  •         删除某元素:O(1)

链表在插入和删除的操作上明显快于顺序表

链表的内存可以更灵活分配

链表这种链式存储的数据结构对树和图的结构有很大启发性

哈希表(散列表)

哈希表介绍

介绍:哈希表是一个通过哈希函数来计算数据存储位置的数据结构,通常支持如下操作:

  • insert(key, value):插入键值对(key, value)
  • get(key):如果存在键为key的键值对,返回其valve,否则返回空值
  • delete(key):删除键为key的键值对

哈希表是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数构成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。

哈希冲突

哈希冲突:由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置上的情况。

1.开放寻址法:如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值。

  • 线性探查:如果位置i被占用,则探查i+1, i+2......
  • 二次探查:如果位置i被占用,则探查i+1^2, i-1^2, i+2^2, i-2^2,......
  • 二度哈希:有n个哈希函数,当使用第一个哈希函数h1发生冲突时,则尝试使用h2,h3......

2.拉链法:哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。

常见哈希函数:

  • 除法哈希法:h(k) = k % m
  • 乘法哈希法:h(k) = floor(m*(A*key%1))
  • 全域哈希法:h_{a,b}(k) = ((a*key + b) mod p)mod m  a,b = 1, 2, ...,p-1

哈希表实现

  1. class LinkList:
  2. # 定义节点类
  3. class Node:
  4. def __init__(self, item=None):
  5. self.item = item # 节点的数据项
  6. self.next = None # 指向下一个节点的指针
  7. # 定义迭代器类
  8. class LinkListIterator:
  9. def __init__(self, node):
  10. self.node = node # 当前节点
  11. def __next__(self):
  12. if self.node: # 如果当前节点存在
  13. cur_node = self.node # 保存当前节点
  14. self.node = cur_node.next # 将当前节点指向下一个节点
  15. return cur_node.item # 返回当前节点的数据项
  16. else:
  17. raise StopIteration # 当遍历结束时抛出StopIteration异常
  18. def __iter__(self):
  19. return self # 返回迭代器自身
  20. # 初始化链表
  21. def __init__(self, iterable=None):
  22. self.head = None # 头节点
  23. self.tail = None # 尾节点
  24. if iterable:
  25. self.extend(iterable) # 如果传入可迭代对象,则调用extend方法进行扩展
  26. # 在链表尾部添加节点
  27. def append(self, obj):
  28. s = LinkList.Node(obj) # 创建新节点
  29. if not self.head: # 如果链表为空
  30. self.head = s # 新节点作为头节点
  31. self.tail = s # 新节点作为尾节点
  32. else:
  33. self.tail.next = s # 将新节点连接到当前尾节点后
  34. self.tail = s # 更新尾节点为新节点
  35. # 扩展链表
  36. def extend(self, iterable):
  37. for obj in iterable:
  38. self.append(obj) # 逐个添加可迭代对象中的元素
  39. # 查找元素是否在链表中
  40. def find(self, obj):
  41. for n in self: # 遍历链表中的每个节点
  42. if n == obj: # 如果找到目标元素
  43. return True # 返回True
  44. else:
  45. return False # 找不到目标元素时返回False
  46. # 返回链表的迭代器
  47. def __iter__(self):
  48. return self.LinkListIterator(self.head)
  49. # 返回链表的字符串表示
  50. def __repr__(self):
  51. return "<<" + ", ".join(map(str, self)) + ">>"
  52. # 类似于集合的结构
  53. class HashTable:
  54. def __init__(self, size = 101):
  55. self.size = size
  56. self.T = [LinkList() for _ in range(self.size)] # 创建域
  57. # 哈希函数
  58. def h(self, k):
  59. return k % self.size
  60. # 插入
  61. def insert(self, k):
  62. i = self.h(k)
  63. if self.find(k): # 如果有
  64. print("Duplicated Insert.")
  65. else:
  66. self.T[i].append(k)
  67. # 查找
  68. def find(self, k):
  69. i = self.h(k)
  70. return self.T[i].find(k)
  71. ht = HashTable()
  72. for i in range(200):
  73. ht.insert(i)
  74. # print(",".join(map(str, ht.T)))
  75. print(ht.find(203))

哈希表应用

        python的字典,集合都是哈希表

        md5算法

介绍:树是一种数据结构

  • 树是一种可以递归定义的数据结构
  • 树是由n个结点组成的集合
  • 如果n=0,那这是一棵空树
  • 如果n>0,那存在一个结点作为树的根结点,其他结点可以分为m个集合,每个集合本身又是一棵树

一些概念:

  • 根结点、叶子结点
  • 树的深度/高度
  • 树的度
  • 孩子结点、父结点
  • 子树

树的示例——模拟文件系统

  1. # 节点
  2. class Node:
  3. def __init__(self, name, type = 'dir'):
  4. self.name = name
  5. self.type = type # 'dir' or 'file'
  6. self.children = [] # 存儿子节点
  7. self.parent = None # 指向父节点
  8. def __repr__(self):
  9. return self.name
  10. # 树类
  11. class FileSystemTree:
  12. def __init__(self):
  13. self.root = Node('/')
  14. self.now = self.root
  15. # 创建目录
  16. def mkdir(self, name):
  17. # name得是一个目录,以'/'结尾
  18. if name[-1] != '/':
  19. name += '/'
  20. node = Node(name)
  21. self.now.children.append(node)
  22. node.parent = self.now
  23. # 展示所有目录
  24. def ls(self):
  25. return self.now.children
  26. # 切换目录
  27. def cd(self, name):
  28. # 只支持往下走一层
  29. if name[-1] != '/':
  30. name += '/'
  31. if name == '../':
  32. self.now = self.now.parent
  33. return
  34. for child in self.now.children:
  35. if child.name == name:
  36. self.now = child
  37. return
  38. raise ValueError("invalid dir")
  39. tree = FileSystemTree()
  40. tree.mkdir("var/")
  41. tree.mkdir("bin/")
  42. tree.mkdir("usr/")
  43. print(tree.ls())
  44. tree.cd("bin")
  45. tree.mkdir("python")
  46. print(tree.ls())
  47. tree.cd("../")
  48. print(tree.ls())

二叉树

二叉树就是度不超过2的树

  • 每个结点最多有两个孩子结点
  • 两个孩子结点被区分为左孩子结点和右孩子结点

完全二叉树

  • 满二叉树:一个二叉树,如果一个层的节点数都达到最大值,则这个二叉树就是满二叉树
  • 完全二叉树:叶子节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

二叉树的存储方式(表示方式)

  • 链式存储方式
  • 顺序存储方式(堆排序用这个)——用列表来存
  • 父结点和左孩子结点的编号下标的关系

                0-1 1-3 2-5 3-7 4-9

                i -> 2i+1

  • 父结点和右孩子结点的编号下标的关系

                0-2 1-4 2-6 3-8 4-10

                i -> 2i+2

之前学过线性存储,这里是链式存储

二叉树的链式存储

将二叉树的节点定义为一个对象,节点之间通过类似链表的链接方式来连接。

节点定义:

  1. class BiTreeNode:
  2. def __init__(self, data):
  3. self.data = data
  4. self.lchild = None
  5. self.rchild = None

 二叉树的遍历

二叉树的遍历方式:

  • 前序遍历:如果不空,先访问根,再访问左子树,最后访问右子树。
  • 中序遍历:如果不空,先访问左子树,再访问根,最后访问右子树。
  • 后序遍历:如果不空,先访问左子树,再访问右子树,最后访问根。
  • 层次遍历:一层一层访问。
  1. # 前序遍历
  2. def pre_order(root):
  3. if root:
  4. print(root.data, end=',')
  5. pre_order(root.lchild)
  6. pre_order(root.rchild)
  7. # 中序遍历
  8. def in_order(root):
  9. if root:
  10. in_order(root.lchild)
  11. print(root.data, end=',')
  12. in_order(root.rchild)
  13. # 后序遍历
  14. def post_order(root):
  15. if root:
  16. post_order(root.lchild)
  17. post_order(root.rchild)
  18. print(root.data, end=",")
  19. # 层次遍历
  20. from compileall import queue
  21. def level_order():
  22. queue = deque()
  23. queue.append(root)
  24. while len(queue) > 0: # 只要队不空
  25. node = queue.popleft()
  26. print(node.data, end=',')
  27. if node.lchild:
  28. queue.append(node.lchild)
  29. if node.rchild:
  30. queue.append(node.rchild)

二叉搜索树

是一颗二叉树,并且满足左子树的根比根小,右子树的根比根大。

二叉搜索树的操作:查询、插入、删除

二叉搜索树的效率:

  • 平均情况下,二叉搜索树进行搜索的时间复杂度为O(logn)
  • 最坏情况下,二叉搜索树可能非常偏斜

解决方案:

  • 随机化插入
  • AVL树

插入

两种方式:递归和非递归

  1. class BiTreeNode:
  2. def __init__(self, data):
  3. self.data = data
  4. self.lchild = None # 左孩子
  5. self.rchild = None # 右孩子
  6. self.parent = None
  7. class BST:
  8. def __init__(self, li=None):
  9. self.root = None
  10. if li:
  11. for val in li:
  12. self.insert_no_rec(val) # 根据列表构造树
  13. # 插入(递归)
  14. def insert(self, node, val):
  15. if not node:
  16. node = BiTreeNode(val)
  17. elif val < node.data:
  18. node.lchild = self.insert(node.lchild, val)
  19. node.lchild.parent = node
  20. elif val > node.data:
  21. node.rchild = self.insert(node.lchild, val)
  22. node.rchild.parent = node
  23. return node
  24. # 插入(非递归)
  25. def insert_no_rec(self, val):
  26. p = self.root
  27. if not p: # 空树时
  28. self.root = BiTreeNode(val)
  29. return
  30. while True:
  31. if val < p.data:
  32. if p.lchild:
  33. p = p.lchild
  34. else: # 左孩子不存在
  35. p.lchild = BiTreeNode(val)
  36. p.lchild.parent = p
  37. return
  38. elif val > p.data:
  39. if p.rchild:
  40. p = p.rchild
  41. else:
  42. p.rchild = BiTreeNode(val)
  43. p.rchild.parent = p
  44. return
  45. else:
  46. return

查询

两种方式:递归和非递归

  1. # 查询(递归)
  2. def query(self, node, val):
  3. if not node:
  4. return None
  5. if node.data < val:
  6. return self.query(node.rchild, val)
  7. elif node.data > val:
  8. return self.query(node.lchild, val)
  9. else:
  10. return node
  11. # 查询(非递归)
  12. def query_no_rec(self, val):
  13. p = self.root
  14. while p:
  15. if p.data < val:
  16. p = p.rchild
  17. elif p.data > val:
  18. p = p.lchild
  19. else:
  20. return p
  21. return None

删除

三种情况:

  • 叶子节点——直接删除
  • 要删除的节点只有一个孩子——将此节点的父亲与孩子连接,然后删除该节点
  • 要删除的节点有两个孩子——将其右子树的最小节点删除,并替换当前节点
  1. # 情况1:node是叶子节点
  2. def __remove_node_1(self, node):
  3. if not node.parent: # 如果是根节点
  4. self.root = None
  5. elif node == node.parent.lchild: # node是它父亲的左孩子
  6. node.parent.lchild = None
  7. else: # 是右孩子
  8. node.parent.rchild = None
  9. # 情况2.1:node只有一个左孩子
  10. def __remove_node_21(self, node):
  11. if not node.parent: # 如果是根节点
  12. self.root = node.lchild
  13. node.lchild.parent = None
  14. elif node == node.parent.lchild: # 是它父亲的左孩子
  15. node.parent.lchild = node.lchild
  16. node.lchild.parent = node.parent
  17. else: # 是它父亲的右孩子
  18. node.parent.rchild = node.lchild
  19. node.lchild.parent = node.parent
  20. # 情况2.2:node只有一个右孩子
  21. def __remove_node_22(self, node):
  22. if not node.parent:
  23. self.root = node.rchild
  24. node.rchild.parent = None
  25. elif node == node.parent.lchild: # 是它父亲的左孩子
  26. node.parent.lchild = node.rchild
  27. node.rchild.parent = node.parent
  28. else: # 是它父亲的右孩子
  29. node.parent.rchild = node.rchild
  30. node.rchild.parent = node.parent
  31. # 情况3:左右节点都有
  32. def delete(self, val):
  33. if self.root: # 不是空树再删
  34. node = self.query_no_rec(val)
  35. if not node: # 如果node不存在
  36. return False
  37. if not node.lchild and not node.rchild: # 1删除叶子节点的情况
  38. self.__remove_node_1(node)
  39. elif not node.rchild: # 2.1只有一个左孩子
  40. self.__remove_node_21(node)
  41. elif not node.lchild: # 2.2只有一个右孩子
  42. self.__remove_node_22(node)
  43. else: # 3两个孩子都有
  44. min_node = node.rchild
  45. while min_node.lchild:
  46. min_node = min_node.lchild
  47. node.data = min_node.data
  48. # 然后删除min_node
  49. if min_node.rchild:
  50. self.__remove_node_22(min_node)
  51. else:
  52. self.__remove_node_21(min_node)

AVL树

AVL树:AVL树是一棵自平衡的二叉搜索树

AVL树具有以下性质:

  • 根的左右子树的高度之差的绝对值不能超过1
  • 根的左右子树都是平衡二叉树

旋转

旋转是修正出现不平衡的树的操作

这里的旋转是服务于插入的

插入一个节点后,只有从插入节点到根节点的路径上的节点的平衡可能被改变。我们需要找出第一个破坏了平衡条件的节点,称之为K——K的两棵子树的高度差2.

插入不平衡的出现可能有四种情况:

  • 不平衡是由于对K的右孩子的右子树插入导致的:左旋
  • 不平衡是由于对K的左孩子的左子树插入导致的:右旋
  • 不平衡是由于对K的右孩子的左子树插入导致的:右旋-左旋
  • 不平衡是由于对K的左孩子的右子树插入导致的:左旋-右旋
  1. from bst import BiTreeNode, BST
  2. class AVLNode(BiTreeNode): # 继承
  3. def __init__(self):
  4. BiTreeNode.__init__(self, data)
  5. self.bf = 0
  6. class AVLTree(BST):
  7. def __init__(self, li=None):
  8. BST.__init__(self, li)
  9. # 左旋
  10. def rotate_left(self, p, c):
  11. s2 = c.lchild
  12. p.rchild = s2
  13. if s2:
  14. s2.parent = p
  15. c.lchild = p
  16. p.parent = c
  17. p.bf = 0
  18. c.bf = 0
  19. # 右旋
  20. def rotate_right(self, p, c):
  21. s2 = c.rchild
  22. p.lchild = s2
  23. if s2:
  24. s2.parent = p
  25. c.rchild = p
  26. p.parent = c
  27. p.bf = 0
  28. c.bf = 0
  29. # 右旋左旋
  30. def rotate_right_left(self, p, c):
  31. g = c.lchild
  32. s3 = g.rchild
  33. c.lchild = s3
  34. if s3:
  35. s3.parent = c
  36. g.rchild = c
  37. c.parent = g
  38. s2 = g.lchild
  39. p.rchild = s2
  40. if s2:
  41. s2.parent = p
  42. g.lchild = p
  43. p.parent = g
  44. # 更新bf
  45. if g.bf > 0:
  46. p.bf = -1
  47. c.bf = 0
  48. elif g.bf < 0:
  49. p.bf = 0
  50. c.bf = 1
  51. else:
  52. p.bf = 0
  53. c.bf = 0
  54. # 左旋右旋
  55. def rotate_left_right(self, p, c):
  56. g = c.rchild
  57. s2 = g.lchild
  58. c.rchild = s2
  59. if s2:
  60. s2.parent = c
  61. g.lchild = c
  62. c.parent = g
  63. s3 = g.rchild
  64. p.lchild = s3
  65. if s3:
  66. s3.parent = p
  67. g.rchild = p
  68. p.parent = g
  69. # 更新bf
  70. if g.bf < 0:
  71. p.bf = 1
  72. c.bf = 0
  73. elif g.bf > 0:
  74. p.bf = 0
  75. c.bf = -1
  76. else:
  77. p.bf = 0
  78. c.bf = 0

插入

插入一个节点可能会破坏AVL树的平衡,可以通过旋转操作来进行修正。

  1. def insert_no_rec(self, val): # 重新定义,覆盖原先的方法
  2. # 第一步:和BST一样,先插入
  3. p = self.root
  4. if not p: # 空树时
  5. self.root = BiTreeNode(val)
  6. return
  7. while True:
  8. if val < p.data:
  9. if p.lchild:
  10. p = p.lchild
  11. else: # 左孩子不存在
  12. p.lchild = BiTreeNode(val)
  13. p.lchild.parent = p
  14. node = p.lchild # node存储的就是插入的节点
  15. break
  16. elif val > p.data:
  17. if p.rchild:
  18. p = p.rchild
  19. else:
  20. p.rchild = BiTreeNode(val)
  21. p.rchild.parent = p
  22. node = p.rchild
  23. break
  24. else: # val == p.data同样的元素只保留一个
  25. return
  26. # 第二步:更新balance factor
  27. while node.parent: # 保证node.parent不空
  28. if node.parent.lchild == node: # 传递是从左子树来的,左子树更沉了
  29. # 更新node.parent 的 bf -= 1
  30. if node.parent.bf < 0: # 原来node.parent.bf == -1,更新后变为-2
  31. # 做旋转
  32. # 看node哪边沉
  33. g = node.parent.parent # 为了连接旋转之后的子树
  34. x = node.parent # 旋转前的子树的根
  35. if node.bf > 0:
  36. n = self.rotate_left_right(node.parent, node)
  37. else:
  38. n = self.rotate_right(node.parent, node)
  39. # 最后将n 和 g 连接起来
  40. elif node.parent.bf > 0: # 原来node.parent.bf == 1, 更新后变成0
  41. node.parent.bf = 0
  42. break
  43. else: # 原来node.parent.bf == 0, 更新后变成-1
  44. node.parent.bf = -1
  45. node = node.parent
  46. continue
  47. else: # 传递是从右子树来的,右子树更沉了
  48. # 更新node.parent 的 bf += 1
  49. if node.parent.bf > 0: # 原来node.parent.bf == 1,更新后变为2
  50. # 做旋转
  51. # 看node哪边沉
  52. g = node.parent.parent # 为了连接旋转之后的子树
  53. x = node.parent # 旋转前的子树的根
  54. if node.bf < 0:
  55. n = self.rotate_right_left(node.parent, node)
  56. else:
  57. n = self.rotate_left(node.parent, node)
  58. # 记得连起来
  59. elif node.parent.bf < 0: # 原来node.parent.bf == -1, 更新后变成0
  60. node.parent.bf = 0
  61. break
  62. else: # 原来node.parent.bf == 0, 更新后变成1
  63. node.parent.bf = 1
  64. node = node.parent
  65. continue
  66. # 连接旋转后的子树
  67. n.parent = g
  68. if g: # 看g是否存在
  69. if x == g.lchild:
  70. g.lchild = n
  71. else:
  72. g.rchild = n
  73. break
  74. else:
  75. self.root = n
  76. break

代码自己手动敲一遍理解更深哦!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/923542
推荐阅读
相关标签
  

闽ICP备14008679号