用flutter实现五种寻路算法的可视化效果,快来看看!

简介: 半年前我写了一篇有关排序算法可视化的文章,挺有意思,还被张风捷特烈-张老师收录进了FlutterUnit,今天让我们再来做一个有关寻路算法的可视化效果吧!

前言

半年前我写了一篇《十几种排序算法的可视化效果,快来看看!👀,还是很有意思的。这篇文章中的内容还被张风捷特烈张老师收录进了FlutterUnit:《FlutterUnit 周边 | 收录排序算法可视化》。今天让我们再来做一个有关寻路算法的可视化效果吧!

效果图:

BFS算法

DFS算法

AStar算法

BestFS算法

Dijkstra算法

代码下载:https://www.alipan.com/s/3kvCHKTgaon

迷宫地图的绘制

迷宫地图的生成有很多很多方法,常见的有随机生成算法、递归分割算法、深度优先搜索算法等,都可以用于迷宫地图的生成。这次我们使用深度优先遍历+广度优先遍历+随机队列去制作迷宫地图,这样会让地图有更多的随机性。

定义迷宫地图的数据类及初始化方法:

定义BlockModel类:

class BlockModel{
  int? rowSize;
  int? columnSize;
  int? startX;
  int? startY;
  int? endX;
  int? endY;
  int road = 1; //路
  int wall = 0; //墙
  //路径是否被访问过
  List<List<bool>> visitedList = [];
  //迷宫地图
  List<List<int>> blockList = [];
  //寻找到的正确路线
  List<List<bool>> pathList = [];
  //方向
  List<List<int>> direction = [
    [-1, 0],
    [0, -1],
    [1, 0],
    [0, 1]
  ];
}

初始化基本的地图数据:

设置迷宫的起点和终点位置,并给所有行坐标为奇数且列坐标为奇数的区块设置为路。其余位置设置为墙。

BlockModel(int r, int c) {
  if (r % 2 == 0 || c % 2 == 0) {
    throw "地图行数和列数不能为偶数";
  }
  
  rowSize = r;
  columnSize = c;
  startX = 1;
  startY = 0;
  endX = r - 2;
  endY = c - 1;
  blockList = [];
  visitedList = [];
  pathList = [];
  
  //初始化迷宫遍历的方向(上、左、右、下)顺序(迷宫趋势)
  //随机遍历顺序,提高迷宫生成的随机性(共12种可能性)
  List.generate(direction.length, (index) {
    int random = Random().nextInt(direction.length);
    List<int> temp = direction[random];
    direction[random] = direction[index];
    direction[index] = temp;
  });
  List.generate(r, (i) {
    List<bool> tempVisited = [];
    List<int> tempMazeList = [];
    List<bool> tempPath = [];
    List.generate(c, (j) {
      //行和列为奇数都设置为路,否则设置为墙
      if (i % 2 == 1 && j % 2 == 1) {
        tempMazeList.add(1); //设置为路
      } else {
        tempMazeList.add(0); //设置为墙
      }
      //初始化访问过的区块,现在所有都没有访问过
      tempVisited.add(false);
      tempPath.add(false);
    });
    visitedList.add(tempVisited);
    blockList.add(tempMazeList);
    pathList.add(tempPath);
  });
  blockList[startX!][startY!] = 1;
  blockList[endX!][endY!] = 1;
}

地图的生成

地图的创建需要一个随机队列的帮助,那让我们先来实现一个随机队列:

借助dart的LinkedList链表结合随机数来决定插入和移除元素的位置,从而实现随机队列。

class RandomQueue {
  LinkedList<Position> _queue = LinkedList<Position>();
  RandomQueue() {
    _queue = LinkedList<Position>();
  }
  void addRandomQueue(Position position) {
    if (Random().nextInt(100) < 50) {
      _queue.addFirst(position);// 插入到队列头部
    } else {
      _queue.add(position);// 插入到队列尾部
    }
  }
  ///返回随机队列中的一个元素
  Position removeRandomQueue() {
    if (_queue.isEmpty) {
      throw "数组为空";
    } else {
      if (Random().nextInt(100) < 50) {
        Position position = _queue.first;
        _queue.remove(position);
        return position;
      } else {
        Position position = _queue.last;
        _queue.remove(position);
        return position;
      }
    }
  }
  //返回随机队列元素数量
  int getSize() {
    return _queue.length;
  }
  //判断随机队列是否为空
  bool isEmpty() {
    return _queue.isEmpty;
  }
}

创建地图:

使用随机队列去生成地图,从指定的起始位置开始,随机选择下一个要访问的位置(访问的方向在初始化BlockModel时已经随机打乱),并在访问时标记该区块已访问。通过这种方式,可以生成具有一定随机性的地图结构。

/// 创建地图并使用随机队列生成地图结构
void createMap(int startX, int startY) {
  RandomQueue randomQueue = RandomQueue(); // 创建一个随机队列实例
  Position start = Position(startX, startY); // 创建起始位置
  randomQueue.addRandomQueue(start); // 将起始位置加入随机队列
  model.visitedList[startX][startY] = true; // 标记起始位置为已访问
  // 使用随机队列生成地图
  while (randomQueue.getSize() != 0) {
    Position position = randomQueue.removeRandomQueue(); // 移除队列中的一个位置
    // 生成四个方向的新位置
    List.generate(4, (i) {
      int newX = position.x! + model.direction[i][0] * 2;
      int newY = position.y! + model.direction[i][1] * 2;
      // 检查新位置是否在地图内且未被访问过
      if (model.isInMap(newX, newY) && !model.visitedList[newX][newY]) {
        // 将新位置加入随机队列,并记录为已访问
        randomQueue
            .addRandomQueue(Position(newX, newY, prePosition: position));
        model.visitedList[newX][newY] = true;
        // 设置新位置与当前位置之间的路径或道路
        setWithRoad(position.x! + model.direction[i][0],
            position.y! + model.direction[i][1]);
      }
    });
  }
  // 把visitedList全部设置为没有访问
  model.visitedList = model.visitedList
      .map((innerList) => innerList.map((element) => false).toList())
      .toList();
}

flutter布局实现

布局非常简单,通过Column结合Row,通过两层List.generate遍历地图数据,即可渲染完布局:

Widget board() {
  mapHeight = screenWidth.floor();
  cellSize = screenWidth.floor() ~/ rowSize;
  List<Row> rowList = [];
  List.generate(model.blockList.length, (i) {
    List<Container> columnList = [];
    List.generate(model.blockList[i].length, (j) {
      columnList.add(Container(
        width: cellSize.toDouble(),
        height: cellSize.toDouble(),
        decoration: BoxDecoration(
          color: getBoxColor(i, j),
        ),
      ));
    });
    rowList.add(Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: columnList,
    ));
  });
  return SizedBox(
    width: mapHeight.toDouble(),
    height: mapHeight.toDouble(),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: rowList,
    ),
  );
}

算法的实现

辅助函数

为了让算法更好的融入我们的地图寻路需求,还需要几个辅助函数:

///设置该区块为路
void setWithRoad(int x, int y) {
  setState(() {
    model.blockList[x][y] = model.road;
  });
}
///设置该区块为正确的路径
void setModelWithPath(int x, int y, bool isPath) {
  setState(() {
    if (model.isInMap(x, y)) {
      model.pathList[x][y] = isPath;
    }
  });
}

接下来让我们正式开始算法的实现!

算法的详细细节就不过多展开了,想要学习的朋友可以自行搜索,相关资料很多

BFS(广度优先搜索

广度优先搜索是图的一种遍历策略。因为它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较广的区域,因此得名。一般可以用它做什么呢?一个最直观经典的例子就是走迷宫,我们从起点开始,找出到终点的最短路程,很多最短路径算法就是基于广度优先的思想成立的。

/// 广度优先搜索(BFS)寻找路径
  Future<bool> searchPathBFS(int startX, int startY) async {
    // 创建队列并添加起始位置
    Queue<List<int>> queue = Queue();
    queue.add([startX, startY]);
    // 初始化访问标记、路径记录和父节点记录
    model.visitedList = List.generate(
        model.columnSize!, (_) => List.filled(model.rowSize!, false));
    model.pathList = List.generate(
        model.columnSize!, (_) => List.filled(model.rowSize!, false));
    List<List<List<int>>> parent = List.generate(model.columnSize!,
        (_) => List.generate(model.rowSize!, (_) => [-1, -1]));
    // 开始广度优先搜索
    while (queue.isNotEmpty) {
      List<int> current = queue.removeFirst();
      int x = current[0];
      int y = current[1];
      // 检查当前位置是否越界
      if (!model.isInMap(x, y)) {
        throw "坐标越界";
      }
      // 如果已经访问过当前位置,则继续下一个循环
      if (model.visitedList[x][y]) continue;
      model.visitedList[x][y] = true; // 标记当前位置为已访问
      setState(() {});
      // 检查是否到达终点
      if (x == model.endX && y == model.endY) {
        print("找到路径了");
        // 回溯路径并记录到 path 列表中
        List<List<int>> path = [];
        int curX = x;
        int curY = y;
        while (curX != -1 && curY != -1) {
          path.add([curX, curY]);
          List<int> prev = parent[curX][curY];
          curX = prev[0];
          curY = prev[1];
        }
        // 清空原路径记录,并设置新的路径记录为找到的路径
        model.pathList = List.generate(
            model.columnSize!, (_) => List.filled(model.rowSize!, false));
        for (var point in path) {
          setModelWithPath(point[0], point[1], true); // 设置路径标记
          await Future.delayed(const Duration(
              milliseconds: 50));
        }
        return true; // 返回找到路径的结果
      }
      // 遍历当前位置的四个方向
      for (int i = 0; i < 4; i++) {
        int newX = x + model.direction[i][0];
        int newY = y + model.direction[i][1];
        // 检查新位置是否在地图范围内,并且是可通过的道路,并且未访问过
        if (model.isInMap(newX, newY) &&
            model.blockList[newX][newY] == model.road &&
            !model.visitedList[newX][newY]) {
          queue.add([newX, newY]); // 将新位置添加到队列中
          parent[newX][newY] = [x, y]; // 记录新位置的父节点
          setState(() {}); 
          await Future.delayed(const Duration(
              milliseconds: 50)); // 等待一段时间,以便显示搜索过程(如果在 Flutter 中)
        }
      }
    }
    return false; // 如果队列为空仍未找到路径,则返回 false
  }

DFS(深度优先搜索)

DFS 是一种递归或栈(堆栈)数据结构的算法,用于图的遍历。

从一个起始节点开始,尽可能深入图的分支,直到无法继续深入,然后回溯并探索其他分支。

通过标记已访问的节点来避免重复访问。

DFS最有意思的就是回溯这一步,回溯就是在搜索尝试过程中寻找问题的解,当发现不满足条件时,就回溯,尝试别的路径。强调的是此路不通,另寻他路。先打标记,记录路径;然后下一层,回到上一层,清除标记。

/// 深度优先搜索(DFS)寻找路径
Future<bool> searchPathDFS(int startX, int startY) async {
  // 初始化访问标记、路径记录和父节点记录
  model.visitedList = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, false));
  model.pathList = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, false));
  List<List<List<int>>> parent = List.generate(model.columnSize!,
          (_) => List.generate(model.rowSize!, (_) => [-1, -1]));
  // 定义DFS递归函数
  Future<bool> dfs(int x, int y) async {
    // 检查当前位置是否越界
    if (!model.isInMap(x, y)) {
      throw "坐标越界";
    }
    // 如果已经访问过当前位置,则返回 false
    if (model.visitedList[x][y]) return false;
    model.visitedList[x][y] = true; // 标记当前位置为已访问
    setState(() {}); // 更新界面状态(如果在 Flutter 中)
    // 检查是否到达终点
    if (x == model.endX && y == model.endY) {
      print("找到路径了");
      // 回溯路径并记录到 path 列表中
      List<List<int>> path = [];
      int curX = x;
      int curY = y;
      while (curX != -1 && curY != -1) {
        path.add([curX, curY]);
        List<int> prev = parent[curX][curY];
        curX = prev[0];
        curY = prev[1];
      }
      // 清空原路径记录,并设置新的路径记录为找到的路径
      model.pathList = List.generate(
          model.columnSize!, (_) => List.filled(model.rowSize!, false));
      for (var point in path) {
        setModelWithPath(point[0], point[1], true); // 设置路径标记
        await Future.delayed(const Duration(milliseconds: 50)); 
      }
      return true; // 返回找到路径的结果
    }
    // 遍历当前位置的四个方向
    for (int i = 0; i < 4; i++) {
      int newX = x + model.direction[i][0];
      int newY = y + model.direction[i][1];
      // 检查新位置是否在地图范围内,并且是可通过的道路,并且未访问过
      if (model.isInMap(newX, newY) &&
          model.blockList[newX][newY] == model.road &&
          !model.visitedList[newX][newY]) {
        parent[newX][newY] = [x, y]; // 记录新位置的父节点
        setState(() {}); // 更新界面状态(如果在 Flutter 中)
        await Future.delayed(const Duration(milliseconds: 50)); 
        if (await dfs(newX, newY)) {
          return true; // 如果找到路径,则返回 true
        }
      }
    }
    return false; // 如果当前位置无法继续搜索,则返回 false
  }
  // 调用DFS函数并返回最终结果
  return await dfs(startX, startY);
}

AStar算法(A*)

A*(AStar)算法是一种静态路网中求解最短路最有效的方法,俗称A星算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或线上游戏的BOT的移动计算上。

  • 和Dijkstra一样,AStar能用于搜索最短路径。
  • 和BFS一样,AStar能用启发式函数引导它自己。

左图为Astar算法效果图,右图为Dijkstra算法效果图。Astar算法与Dijkstra算法的效果差不多,但Astar算法访问的节点数明显比Dijkstra算法少得多,代表其速度更快,运行时间更短。


(图片来源于网络)

实现这个算法我们需要编写一个Node类:

Node 类表示了AStar搜索算法中的节点。节点在A算法中的作用是记录搜索过程中的状态信息,包括当前位置的坐标、从起始节点到当前节点的路径长度(g值)、以及从当前节点到目标节点的估计距离(启发值h)。

class Node {
  int x, y, g; // 节点的坐标和当前路径长度g值
  double h; // 启发值h
  Node? parent; // 父节点
  Node(this.x, this.y, this.g, this.h, [this.parent]);
  double get f => g + h; // 计算节点的总代价f值
}
class PriorityQueue {
  final List<Node> _queue = []; // 优先队列使用的列表
  bool get isEmpty => _queue.isEmpty; // 判断队列是否为空
  void add(Node node) {
    _queue.add(node); // 添加节点到队列
    _queue.sort((a, b) => a.f.compareTo(b.f)); // 根据f值排序,f值小的在前面
  }
  Node removeFirst() {
    return _queue.removeAt(0); // 移除并返回队列中f值最小的节点
  }
}

编写AStar算法:

节点的估值算法我们使用曼哈顿距离用于估值,曼哈顿距离是平面上两点之间的距离,定义为从一个点沿着网格状的路径到另一个点的距离,沿路径只能朝着水平或垂直方向移动,而不能斜向移动。就是横向和纵向的距离之和。

具体计算方法如下:

heuristic(x,y)=∣x−model.endX∣+∣y−model.endY∣

/// AStar算法
Future<bool> searchPathAStar(int startX, int startY) async {
  // 创建优先队列作为开放列表
  PriorityQueue openList = PriorityQueue();
  openList.add(Node(startX, startY, 0, heuristic(startX, startY)));
  // 初始化路径记录和访问标记
  model.visitedList = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, false));
  model.pathList = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, false));
  while (!openList.isEmpty) {
    // 从开放列表中移除f值最小的节点作为当前节点
    Node current = openList.removeFirst();
    int x = current.x;
    int y = current.y;
    // 检查当前节点是否超出地图范围
    if (!model.isInMap(x, y)) {
      throw "坐标越界";
    }
    // 如果当前节点已经访问过,则继续下一个节点
    if (model.visitedList[x][y]) continue;
    model.visitedList[x][y] = true;
    setState(() {});
    // 检查是否到达终点
    if (x == model.endX && y == model.endY) {
      print("找到路径了");
      // 回溯路径
      List<List<int>> path = [];
      Node? curNode = current;
      while (curNode != null) {
        path.add([curNode.x, curNode.y]);
        curNode = curNode.parent;
      }
      // 将路径标记到pathList中,并显示路径动画
      model.pathList = List.generate(
          model.columnSize!, (_) => List.filled(model.rowSize!, false));
      for (var point in path) {
        setModelWithPath(point[0], point[1], true);
        await Future.delayed(const Duration(milliseconds: 50));
      }
      return true;
    }
    // 遍历当前节点的四个方向
    for (int i = 0; i < 4; i++) {
      int newX = x + model.direction[i][0];
      int newY = y + model.direction[i][1];
      // 如果新节点在地图范围内且是可通行的空地且未被访问过
      if (model.isInMap(newX, newY) &&
          model.blockList[newX][newY] == model.road &&
          !model.visitedList[newX][newY]) {
        int g = current.g + 1; // 更新新节点的g值
        double h = heuristic(newX, newY); // 计算新节点的启发值h
        openList.add(Node(newX, newY, g, h, current)); // 将新节点加入开放列表
        setState(() {});
        await Future.delayed(const Duration(milliseconds: 50)); // 延迟显示效果
      }
    }
  }
  return false; // 开放列表为空,未找到路径,搜索失败
}
// 估价函数(曼哈顿距离)
double heuristic(int x, int y) {
  return ((x - model.endX!).abs() + (y - model.endY!).abs()).toDouble();
}

BestFS算法(最佳优先算法

主要运用贪心算法的思想,优先搜索离终点近的点。

/// BestFS算法
Future<bool> searchPathBestFS(int startX, int startY) async {
  // 初始化优先队列、起始节点和父节点数组
  PriorityQueue priorityQueue = PriorityQueue();
  Node startNode = Node(startX, startY, 0,
      euclideanDistance(startX, startY, model.endX!, model.endY!));
  priorityQueue.add(startNode);
  // 初始化 visitedList、pathList 和 parent
  // 初始化路径记录和访问标记
  model.visitedList = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, false));
  model.pathList = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, false));
  List<List<int>> parent = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, -1));
  while (!priorityQueue.isEmpty) {
    Node currentNode = priorityQueue.removeFirst();
    int x = currentNode.x;
    int y = currentNode.y;
    // 如果当前节点已访问过,则跳过
    if (model.visitedList[x][y]) continue;
    model.visitedList[x][y] = true;
    setState(() {});
    // 检查是否到达终点
    if (x == model.endX && y == model.endY) {
      print("找到路径了");
      // 回溯路径
      List<List<int>> path = [];
      int curX = x;
      int curY = y;
      while (curX != -1 && curY != -1) {
        path.add([curX, curY]);
        int prevX = parent[curX][curY] ~/ model.rowSize!;
        int prevY = parent[curX][curY] % model.rowSize!;
        curX = prevX;
        curY = prevY;
        print("curX:$curX,curY:$curY");
        // 防止出现死循环,检查是否回溯到起点
        if (curX == startX && curY == startY) {
          break;
        }
      }
      // path = path.reversed.toList();
      // 绘制路径到界面上
      model.pathList = List.generate(
          model.columnSize!, (_) => List.filled(model.rowSize!, false));
      for (var point in path) {
        setModelWithPath(point[0], point[1], true);
        await Future.delayed(const Duration(milliseconds: 50));
      }
      return true; // 找到路径并绘制完成,返回true
    }
    // 探索当前节点的邻居节点
    for (int i = 0; i < 4; i++) {
      int newX = x + model.direction[i][0];
      int newY = y + model.direction[i][1];
      // 如果邻居节点在地图范围内且是可通行的道路且未访问过
      if (model.isInMap(newX, newY) &&
          model.blockList[newX][newY] == model.road &&
          !model.visitedList[newX][newY]) {
        Node newNode = Node(newX, newY, currentNode.g + 1,
            euclideanDistance(newX, newY, model.endX!, model.endY!));
        priorityQueue.add(newNode);
        parent[newX][newY] = x * model.rowSize! + y; // 记录父节点
        setState(() {});
        await Future.delayed(const Duration(milliseconds: 50));
      }
    }
  }
  return false; // 未找到路径,返回false
}

Dijkstra算法

最初我学习到这个算法是在计算机网络中,关于网络路由的部分。它非常适合用于找到从起始节点到所有其他节点的最短路径。不过,它仅适用于非负权重的图,因为它依赖于贪婪策略来选择当前最短路径。

/// Dijkstra算法
Future<bool> searchPathDijkstra(int startX, int startY) async {
  // 初始化优先队列、距离数组和父节点数组
  PriorityQueue priorityQueue = PriorityQueue();
  List<List<int>> distance = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, -1));
  List<List<int>> parent = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, -1));
  model.visitedList = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, false));
  model.pathList = List.generate(
      model.columnSize!, (_) => List.filled(model.rowSize!, false));
  // 将起始节点加入优先队列并初始化距离
  priorityQueue.add(Node(startX, startY, 0, 0.0));
  distance[startX][startY] = 0;
  while (!priorityQueue.isEmpty) {
    // 从优先队列中取出当前节点
    Node currentNode = priorityQueue.removeFirst();
    int x = currentNode.x;
    int y = currentNode.y;
    int currentDistance = currentNode.g;
    // 如果当前节点已访问过,则跳过
    if (model.visitedList[x][y]) continue;
    model.visitedList[x][y] = true;
    setState(() {});
    // 检查是否到达终点
    if (x == model.endX && y == model.endY) {
      print("找到路径了");
      // 回溯路径
      List<List<int>> path = [];
      int curX = x;
      int curY = y;
      while (curX != -1 && curY != -1) {
        path.add([curX, curY]);
        int prevX = parent[curX][curY] ~/ model.rowSize!;
        int prevY = parent[curX][curY] % model.rowSize!;
        curX = prevX;
        curY = prevY;
        print("curX:$curX,curY:$curY");
        // 防止出现死循环,检查是否回溯到起点
        if (curX == startX && curY == startY) {
          break;
        }
      }
      // path = path.reversed.toList();
      // 绘制路径到界面上
      model.pathList = List.generate(
          model.columnSize!, (_) => List.filled(model.rowSize!, false));
      for (var point in path) {
        setModelWithPath(point[0], point[1], true);
        await Future.delayed(const Duration(milliseconds: 50));
      }
      return true; // 找到路径并绘制完成,返回true
    }
    // 探索当前节点的邻居节点
    for (int i = 0; i < 4; i++) {
      int newX = x + model.direction[i][0];
      int newY = y + model.direction[i][1];
      // 如果邻居节点在地图范围内且是可通行的道路且未访问过
      if (model.isInMap(newX, newY) &&
          model.blockList[newX][newY] == model.road &&
          !model.visitedList[newX][newY]) {
        int newDistance = currentDistance + 1; // 更新距离
        // 如果是第一次访问或找到更短的路径,则更新距离和父节点,并加入优先队列
        if (distance[newX][newY] == -1 ||
            newDistance < distance[newX][newY]) {
          distance[newX][newY] = newDistance;
          Node newNode =
              Node(newX, newY, newDistance, 0.0); // Dijkstra没有启发式函数,h设为0
          priorityQueue.add(newNode);
          parent[newX][newY] = x * model.rowSize! + y; // 记录父节点
          setState(() {});
          await Future.delayed(const Duration(milliseconds: 50));
        }
      }
    }
  }
  return false; // 未找到路径,返回false
}

相关文章
|
4月前
|
数据采集 机器学习/深度学习 数据可视化
【优秀python web系统毕设】基于python的全国招聘数据分析可视化系统,包括随机森林算法
本文介绍了一个基于Python的全国招聘数据分析可视化系统,该系统利用数据挖掘技术、随机森林算法和数据可视化技术,从招聘网站抓取数据,进行处理、分析和预测,帮助用户洞察招聘市场,为求职者和企业提供决策支持。
157 2
|
4月前
|
搜索推荐 前端开发 数据可视化
【优秀python web毕设案例】基于协同过滤算法的酒店推荐系统,django框架+bootstrap前端+echarts可视化,有后台有爬虫
本文介绍了一个基于Django框架、协同过滤算法、ECharts数据可视化以及Bootstrap前端技术的酒店推荐系统,该系统通过用户行为分析和推荐算法优化,提供个性化的酒店推荐和直观的数据展示,以提升用户体验。
164 1
【优秀python web毕设案例】基于协同过滤算法的酒店推荐系统,django框架+bootstrap前端+echarts可视化,有后台有爬虫
|
2月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
17 0
|
4月前
|
机器学习/深度学习 数据采集 数据可视化
基于python 机器学习算法的二手房房价可视化和预测系统
文章介绍了一个基于Python机器学习算法的二手房房价可视化和预测系统,涵盖了爬虫数据采集、数据处理分析、机器学习预测以及Flask Web部署等模块。
122 2
基于python 机器学习算法的二手房房价可视化和预测系统
|
4月前
|
算法
互动游戏解决遇到问题之基于射线投射寻路算法的问题如何解决
互动游戏解决遇到问题之基于射线投射寻路算法的问题如何解决
|
4月前
|
机器学习/深度学习 算法 数据可视化
基于Python flask的豆瓣电影数据分析可视化系统,功能多,LSTM算法+注意力机制实现情感分析,准确率高达85%
本文介绍了一个基于Python Flask框架的豆瓣电影数据分析可视化系统,该系统集成了LSTM算法和注意力机制进行情感分析,准确率高达85%,提供了多样化的数据分析和情感识别功能,旨在帮助用户深入理解电影市场和观众喜好。
139 0
|
4月前
|
监控 数据可视化 算法
基于朴素贝叶斯算法的微博舆情监控系统,flask后端,可视化丰富
本文介绍了一个基于朴素贝叶斯算法和Python技术栈的微博舆情监控系统,该系统使用Flask作为后端框架,通过数据爬取、清洗、情感分析和可视化等手段,为用户提供丰富的舆情分析和监测功能。
|
6月前
|
算法 数据可视化 Python
【KMeans】Python实现KMeans算法及其可视化
【KMeans】Python实现KMeans算法及其可视化
|
7月前
|
算法 数据可视化 搜索推荐
数据分享|Python用Apriori算法关联规则分析亚马逊购买书籍关联推荐客户和网络图可视化
数据分享|Python用Apriori算法关联规则分析亚马逊购买书籍关联推荐客户和网络图可视化
|
7月前
|
存储 机器学习/深度学习 算法
R语言贝叶斯Metropolis-Hastings采样 MCMC算法理解和应用可视化案例
R语言贝叶斯Metropolis-Hastings采样 MCMC算法理解和应用可视化案例