input: Write a python function to perform A* pathfinding algorithm in a maze.
  assert astar([[0, 1, 0, 0, 0],
                [0, 1, 0, 1, 0],
                [0, 0, 0, 1, 0],
                [0, 1, 0, 0, 0],
                [0, 0, 0, 1, 0]], (0, 0), (4, 4)) == [(0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (3, 2), (4, 2), (4, 3), (4, 4)]
  assert astar([[0, 1, 0],
                [0, 1, 0],
                [0, 0, 0]], (0, 0), (2, 2)) == [(0, 0), (1, 0), (2, 0), (2, 1), (2, 2)]
  assert astar([[0, 0, 0],
                [1, 1, 0],
                [0, 0, 0]], (0, 0), (2, 2)) == [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)]
output: def astar(maze, start, end):
      from heapq import heappop, heappush
  
      def heuristic(a, b):
          return abs(a[0] - b[0]) + abs(a[1] - b[1])
  
      rows, cols = len(maze), len(maze[0])
      open_set = []
      heappush(open_set, (0, start))
      came_from = {}
      g_score = {start: 0}
      f_score = {start: heuristic(start, end)}
  
      while open_set:
          _, current = heappop(open_set)
  
          if current == end:
              path = []
              while current in came_from:
                  path.append(current)
                  current = came_from[current]
              path.append(start)
              return path[::-1]
  
          for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
              neighbor = (current[0] + dx, current[1] + dy)
              if 0 <= neighbor[0] < rows and 0 <= neighbor[1] < cols and maze[neighbor[0]][neighbor[1]] == 0:
                  tentative_g_score = g_score[current] + 1
                  if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
                      came_from[neighbor] = current
                      g_score[neighbor] = tentative_g_score
                      f_score[neighbor] = tentative_g_score + heuristic(neighbor, end)
                      heappush(open_set, (f_score[neighbor], neighbor))
  
      return None