C语言经典算法之八数码难题

目录

前言

A.建议

B.简介

一 代码实现

二 时空复杂度

A.时间复杂度分析:

B.空间复杂度分析:


前言

A.建议

1.学习算法最重要的是理解算法的每一步,而不是记住算法。

2.建议读者学习算法的时候,自己手动一步一步地运行算法。

B.简介

八数码难题(也称为滑动拼图游戏或十五拼图游戏的一种变形)是一个经典的组合优化问题,它在一个3x3的网格上进行,网格上有8个数字块和一个空白格子。每个数字块可以在空格相邻的上下左右四个方向上进行移动,目标是通过一系列这样的移动操作,将最初乱序排列的数字块按照特定的顺序排列。

一 代码实现

在C语言中,我们可以用结构体表示状态,比如:

typedef struct {
    int board[3][3]; // 用来存储3x3棋盘的状态
    // 可能还包括其他信息,如父节点、移动步数等
} PuzzleState;

// 假设目标状态:
const int targetBoard[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 0} // 0代表空白格子
};

在实现解决方案时,可以使用多种搜索算法,如广度优先搜索(BFS)、深度优先搜索(DFS)、A*搜索等。以下是使用BFS的基本思路:

  1. 创建队列:用于存放待搜索的状态。
  2. 定义状态转换函数:根据当前状态,生成所有可能的下一状态(即一次移动之后的状态)。
  3. 检查目标状态:每当从队列中取出一个状态时,检查它是否为目标状态,如果是,则找到了解。
  4. 加入新状态:如果不是目标状态,则生成的所有合法的新状态加入队列,等待后续搜索。

对于C语言实现,你可能会有一个核心循环,像这样:

#include <queue>
#include <iostream> // 可能需要用于打印状态

// 假设PuzzleState的定义
struct PuzzleState {
    // 这里定义谜题状态的数据成员,例如board是一个二维数组或类矩阵
    int board[ROWS][COLS];
    // 可能还有记录路径的额外信息,此处省略
};

// 假设Queue是一个基于std::queue的封装类
class Queue {
public:
    std::queue<PuzzleState*> states;

    // 初始化队列
    Queue() {}

    // 添加状态到队列末尾
    void Enqueue(PuzzleState* state) {
        states.push(state);
    }

    // 从队列头部移除并返回状态
    PuzzleState* Dequeue() {
        PuzzleState* front = states.front();
        states.pop();
        return front;
    }

    // 判断队列是否为空
    bool IsEmpty() const {
        return states.empty();
    }

    // 清理队列,这里假定我们需要删除队列中动态分配的状态
    ~Queue() {
        while (!states.empty()) {
            delete states.front();
            states.pop();
        }
    }
};

// 目标状态比较函数
bool IsTargetState(const PuzzleState& currentState, const PuzzleState& targetState) {
    // 实现比较逻辑,确定当前状态是否为目标状态
    // ...
    return /*比较结果*/ true; // 替换为实际比较逻辑
}

// 生成所有可能的下一个状态并加入队列
void GenerateNextStates(const PuzzleState& currentState, Queue* queue) {
    // 根据谜题规则生成currentState的后续状态,并将其加入queue
    // ...
    for (const auto& nextState : generatedStates) {
        PuzzleState* newState = new PuzzleState(nextState);
        queue->Enqueue(newState);
    }
}

int main() {
    // 初始化队列
    Queue queue;

    // 初始化起始状态并加入队列
    PuzzleState initial;
    // ... 设置initial.board...
    // 假设已设置好initial.board
    queue.Enqueue(&initial);

    while (!queue.IsEmpty()) {
        PuzzleState* currentState = queue.Dequeue();

        // 检查是否达到目标状态
        if (IsTargetState(*currentState, targetBoard)) {
            // 打印解题路径或返回解
            std::cout << "找到解!\n";
            //... 打印或处理解
            break;
        }

        // 生成所有可能的下一个状态
        GenerateNextStates(*currentState, &queue);
    }

    // 如果Queue类内部已经实现了析构函数来清理队列中的动态分配的状态,则无需额外清理
    // 如果没有实现自动清理,这里需要手动释放内存,但这通常不建议在现代C++中这样做
    // 因为在上述Queue类设计中,析构函数已经负责了这一工作

    return 0;
}

注意,上述代码只是一个概念上的实现,实际应用中需要根据具体的谜题规则来填充IsTargetStateGenerateNextStates函数的内容,并确保适当管理和释放内存(如果使用了动态分配)。另外,在C++标准库中直接使用std::queue<PuzzleState>而不是自定义Queue类也是可行的,只需稍作调整即可。

 

二 时空复杂度

A.时间复杂度分析:

  • 对于 广度优先搜索(BFS) 来说,如果每一步都均匀分布(即任何合法移动都能到达最终解),则时间复杂度理论上是最优的,约为O(b^\frac{d}{2}),其中 b 是宽度(即每次扩展状态下最多能产生的新状态数量,在八数码问题中最大宽度是4,因为空白格子最多可以移动到相邻的4个位置),d 是解的步数。

  • 若使用 深度优先搜索(DFS) ,在最坏情况下(如果没有剪枝策略,且状态空间巨大时),可能会导致指数级的时间复杂度O(b^d)

  • 对于 A*搜索IDA*搜索 加上了启发式函数,若启发式函数是理想的Admissible(保守)和Consistent(一致),则能在最优情况下达到线性时间复杂度,但实际复杂度取决于启发式函数的好坏,一般会优于无信息搜索。

B.空间复杂度分析:

  • 存储队列(或堆栈,对于DFS)中的中间状态会导致的空间复杂度为 O(bd),因为在最坏情况下,搜索树的每一层都有 b 个状态,共搜索 d 层。

  • 如果使用哈希表来记录已经访问过的状态以避免重复,则空间复杂度取决于状态总数,对于八数码问题,共有 9! 种不同的排列(不考虑空白格子的位置),因此理论上哈希表空间复杂度可能高达 O(9!),但在实际应用中,很多状态是重复的,所以实际占用的空间会小得多。

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JJJ69

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值