凸包(Convex Hull)问题算法详解

参考:https://blog.csdn.net/Zhang_Chen_/article/details/102417129

前言:

  • 首先,什么是凸包?
    假设平面上有p0~p12共13个点,过某些点作一个多边形,使这个多边形能把所有点都“包”起来。当这个多边形是凸多边形的时候,我们就叫它“凸包”。如下图:
    在这里插入图片描述
  • 然后,什么是凸包问题?
    我们把这些点放在二维坐标系里面,那么每个点都能用 (x,y) 来表示。
    现给出点的数目13,和各个点的坐标。求构成凸包的点?

概述:

  • 凸包(Convex Hull)是一个计算几何(图形学)中的概念。
    在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点( X1 ,. . . ,Xn )的凸组合来构造。
  • 在二维欧几里得空间中,凸包可想象为一条刚好包着所有点的橡皮圈。用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点
  • 凸包问题:给定点集,求构成凸包的点

1. 穷举法(暴力法、蛮力法)

  • 时间复杂度:O(n³)。
  • 思路:两点确定一条直线,如果剩余的其它点都在这条直线的同一侧,则这两个点是凸包上的点,否则就不是。
  • 步骤:
  1. 将点集里面的所有点两两配对,组成在这里插入图片描述
    条直线。对于每条直线,再检查剩余的( n − 2 ) 个点是否在直线的同一侧。

  2. 如何判断一个点 p 3 p3 p3是在点 p 1 、 p 2 p1、p2 p1p2连成的直线的左边还是右边呢?坐标: p 1 ( x 1 , y 1 ) , p 2 ( x 2 , y 2 ) , p 3 ( x 3 , y 3 ) p 1 ( x 1 , y 1 ) , p 2 ( x 2 , y 2 ) , p 3 ( x 3 , y 3 ) p1(x1,y1)p2(x2,y2)p3(x3,y3)
    在这里插入图片描述

2. 分治法

  • 时间复杂度:O(n㏒n)。
  • 思路:应用分治法思想,把一个大问题分成几个结构相同的子问题,把子问题再分成几个更小的子问题……。然后我们就能用递归的方法,分别求这些子问题的解。最后把每个子问题的解“组装”成原来大问题的解。
  • 步骤:
  1. 把所有的点都放在二维坐标系里面。那么横坐标最小和最大的两个点 P 1 、 P n P1、Pn P1Pn一定是凸包上的点。直线 P 1 P n P1Pn P1Pn把点集分成了两部分,即X轴上面和下面两部分,分别叫做上包和下包。
  2. 对上包:求距离直线 P 1 P n P1Pn P1Pn最远的点,假设为点 P m a x Pmax Pmax
  3. 作直线 P 1 P m a x 、 P n P m a x P1Pmax、PnPmax P1PmaxPnPmax,把直线 P 1 P m a x P1Pmax P1Pmax左侧的点当成是上包,把直线 P n P m a x PnPmax PnPmax右侧的点也当成是上包。
  4. 重复步骤 2、3。
  5. 对下包也作类似操作。
    在这里插入图片描述
  6. 然而怎么求距离某直线最远的点呢?设有一个点 p 3 p3 p3和直线 p 1 p 2 p1p2 p1p2。坐标: p 1 ( x 1 , y 1 ) , p 2 ( x 2 , y 2 ) , p 3 ( x 3 , y 3 ) p1(x 1 , y 1 ) , p 2 ( x 2 , y 2 ) , p 3 ( x 3 , y 3 ) p1(x1,y1)p2(x2,y2)p3(x3,y3)
    在这里插入图片描述
    注意:在步骤1中,如果横坐标最小的点不止一个,那么这几个点都是凸包上的点,此时上包和下包的划分就有点不同了,需要注意。

代码实现:

#include <iostream>
#include <vector>

using namespace std;

vector<vector<int>> convex_hull; /*convex_hull储存所有凸包点*/

/*GetResult()实现功能:以坐标P0(x1,y1)和Pn(x2,y2)为直线,找出pack里面里这条直线最远的点Pmax
并找出直线P0Pmax和PmaxPn的上包,进行递归
*/

void GetResult(vector<vector<int>> point, int x1, int y1, int x2, int y2)
{
    /*tmax:最远点在point中的索引
    Rmax:最远距离的值*/
    int i, x3, y3, R, Rmax, tmax; 
    vector<vector<int>> result_pack; /*存放上包点或者下包点*/
    
    /*上包点或者下包点计数,初始化为零*/
    result_pack.push_back({0});
    
	x3 = point[1][0];
    y3 = point[1][1];
    R = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3;
    Rmax = R;
    tmax = 1;

    if (R >= 0)
    {
        result_pack.push_back({x3, y3});
        result_pack[0][0] = result_pack[0][0] + 1;
    }
    
    for(int i=2;i<=point[0][0];i++) /*从点集的第二个点开始循环*/
    {
        x3 = point[i][0];
        y3 = point[i][1];
        R = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3;
        if(R >= 0) /*如果R>=0,则是同一测包(上包或下包)的点*/
        {
            result_pack.push_back({x3, y3});
            result_pack[0][0] = result_pack[0][0] + 1;
        }
        if(R > Rmax)
        {
            Rmax = R;
            tmax = i;
        }
    } /*找到一测距离直线最远的点的距离和索引*/
    
    if(Rmax <= 0) /*如果已经是边界点了*/
    {
        for(int i=1;i<=result_pack[0][0];i++)
        {
            x3 = result_pack[i][0];
            y3 = result_pack[i][1];
            R = x1*y2 + x3*y1 + x2*y3 - x3*y2 - x2*y1 - x1*y3;
            if(R == 0 && !((x3==x2&&y3==y2)||(x3==x1&&y3==y1))) /*如果R是零并且这个新点不是决定直线的两个点,则加入凸包点集合*/
            {
                convex_hull.push_back({result_pack[i][0], result_pack[i][1]});
                convex_hull[0][0] = convex_hull[0][0] + 1;
            }
        }
        return;
    }
    else
    {
        convex_hull.push_back({point[tmax][0], point[tmax][1]});
        convex_hull[0][0] = convex_hull[0][0] + 1;
        if(result_pack[0][0] == 0)
            return;
    }
    GetResult(result_pack, x1, y1, point[tmax][0], point[tmax][1]);
    GetResult(result_pack, point[tmax][0], point[tmax][1], x2, y2);
}

int main(int argc, char** argv)
{
    vector<vector<int>> pointset; /*pointset储存所有点*/
    int count=1; /*整型变量conut用于计数*/
    int x1, y1, x2, y2, x3, y3; /*三个点的坐标*/
    convex_hull.push_back({0}); /*convex_hull的第一行第一列元素存放凸包点的个数,初始化为0*/
	pointset.push_back({0}); /*pointset的第一行第一列元素存放点集里面有几个点,初始化为0*/
    
	cout<<"===请输入所有点的坐标==="<<endl;
    
    /*初始化点集*/
    int x, y;
    while(count<20) /*设置输入20个点*/
	{
		cout<<"请输入点"<<count<<"的x轴坐标:"<<endl;
		cin>>x;
		cout<<"请输入点"<<count<<"的y轴坐标:"<<endl;
		cin>>y;
		pointset.push_back({x, y});
        count++;
	}
    /*点集里一共有多少个点*/
	pointset[0][0] = count-1; 

	x1 = pointset[1][0];
    y1 = pointset[1][1];
    
	x2 = x1;
    y2 = y1;
    
	for(int i=2;i<=pointset[0][0];i++)
    {
        x3 = pointset[i][0];
        y3 = pointset[i][1];
        if(x3 < x1)
        {
            x1 = x3;
            y1 = y3;
        } /*找到x最小的点赋给(x1, y1)*/
        else if(x3 > x2)
        {
            x2 = x3;
            y2 = y3;
        } /*找到x最大的点赋给(x2, y2)*/
    }

	/*两点是凸包点*/
    convex_hull.push_back({x1, y1});
    convex_hull.push_back({x2, y2});
	
	/*凸包点个数加二*/
    convex_hull[0][0] += 2;
    
    /*因为新x1-x2和x2-x1符号相反,所以上包点和下包点对应的“计算距离公式分子绝对值内的数学表达式”的一正一负
    所以下面调换x1和x2顺序作为输入保证两者计算的“计算距离公式分子绝对值内的数学表达式”为正的情况各是上包点和下包点中的一种*/
	GetResult(pointset, x1, y1, x2, y2);
    GetResult(pointset, x2, y2, x1, y1);

    /*打印凸包点*/
    cout<<"\n\n构成凸包的点有:"<<endl;
    for(int i=1;i<=convex_hull[0][0];i++)
    {
        cout<<"("<<convex_hull[i][0]<<", "<<convex_hull[i][1]<<")"<<endl;
    }
}

3. Jarvis步进法

  • 时间复杂度:O(nH)。(其中 n 是点的总个数,H 是凸包上的点的个数)
  • 思路:纵坐标最小的那个点一定是凸包上的点,例如下图中的 P 0 P 0 P0。从 P 0 P 0 P0开始,按逆时针的方向,逐个找凸包上的点,每前进一步找到一个点,所以叫作步进法。
  • 怎么找下一个点呢?利用夹角。假设现在已经找到 P 0 , P 1 , P 2 P 0 , P 1 , P 2 P0P1P2了,要找下一个点:剩下的点分别和 P 2 P 2 P2组成向量,设这个向量与向量 P 1 P 2 P 1 P 2 P1P2的夹角为 β 。当 β 最小时就是所要求的下一个点了,此处为 P 3 P 3 P3
    在这里插入图片描述
  • 注意
  1. 找第二个点 P 1 P 1 P1时,因为已经找到的只有 P 0 P 0 P0一个点,所以向量只能和水平线作夹角 α,当 α 最小时求得第二个点。
  2. 共线情况:如果直线 P 2 P 3 P 2 P 3 P2P3上还有一个点 P 4 P 4 P4,即三个点共线,此时由向量 P 2 P 3 P 2 P 3 P2P3和向量 P 2 P 4 P 2 P 4 P2P4产生的两个 β 是相同的。我们应该把 P 3 、 P 4 P 3、 P 4 P3P4都当做凸包上的点,并且把距离 P 2 P 2 P2最远的那个点(即上图中的 P 4 P 4 P4)作为最后搜索到的点,继续找它的下一个连接点。

4. Graham(格拉翰)扫描法

  • 时间复杂度:O(n㏒n) 。
  • 思路:Graham扫描的思想和Jarvis步进法类似,也是先找到凸包上的一个点,然后从那个点开始按逆时针方向逐个找凸包上的点,但它不是利用夹角。
    在这里插入图片描述
  • 步骤:
  1. 把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,记为 P 0 P 0 P0
  2. 把所有点的坐标平移一下,使 P 0 P 0 P0作为原点。
  3. 计算各个点相对于 P 0 P 0 P0的幅角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 P 0 P 0 P0比较近的排在前面。例如上图得到的结果为 P 1 , P 2 , P 3 , P 4 , P 5 , P 6 , P 7 , P 8 P 1 , P 2 , P 3 , P 4 , P 5 , P 6 , P 7 , P 8 P1P2P3P4P5P6P7P8。我们由几何知识可以知道,结果中第一个点 P 1 P 1 P1和最后一个点 P 8 P 8 P8一定是凸包上的点。
  4. 以上,我们已经知道了凸包上的第一个点 P 0 P 0 P0和第二个点 P 1 P 1 P1,我们把它们放在栈里面。现在从步骤3求得的那个结果里,把 P 1 P 1 P1后面的那个点拿出来做当前点,即 P 2 P 2 P2。接下来开始找第三个点。
  5. 连接栈最上面的两个元素,得到直线L。看当前点是在直线L的右边还是左边。如果在直线的右边就执行步骤6;如果在直线上,或者在直线的左边就执行步骤7。
  6. 如果在右边,则栈顶的那个元素不是凸包上的点,把栈顶元素出栈。执行步骤5。
  7. 当前点是凸包上的点,把它压入栈,执行步骤8。
  8. 检查当前的点是不是步骤3那个结果的最后一个元素。是最后一个元素的话就结束。如果不是的话就把当前点后面那个点做当前点,返回步骤5。
  9. 最后,栈中的元素就是凸包上的点了。
    以下为用Graham扫描法动态求解的过程:
    在这里插入图片描述
  • Graham(格拉翰)扫描法要求开始时必须至少知道一个必然在凸包上的点作为起始点(还好这比较简单)。它有个缺点就是直接用它去求一个给定多边形的凸包可能会导致错误,因此算法开始前必须将点有序化。

5. Melkman算法

讲述了c++的各种使用的数据结构和算法,对于初学者和已经入门的都很有帮助,里面的内容有 目 录 译者序 前言 第一部分 预备知识 第1章 C++程序设计 1 1.1 引言 1 1.2 函数与参数 2 1.2.1 传值参数 2 1.2.2 模板函数 3 1.2.3 引用参数 3 1.2.4 常量引用参数 4 1.2.5 返回值 4 1.2.6 递归函数 5 1.3 动态存储分配 9 1.3.1 操作符new 9 1.3.2 一维数组 9 1.3.3 异常处理 10 1.3.4 操作符delete 10 1.3.5 二维数组 10 1.4 类 13 1.4.1 类Currency 13 1.4.2 使用不同的描述方法 18 1.4.3 操作符重载 20 1.4.4 引发异常 22 1.4.5 友元和保护类成员 23 1.4.6 增加#ifndef, #define和#endif语句 24 1.5 测试与调试 24 1.5.1 什么是测试 24 1.5.2 设计测试数据 26 1.5.3 调试 28 1.6 参考及推荐读物 29 第2章 程序性能 30 2.1 引言 30 2.2 空间复杂性 31 2.2.1 空间复杂性的组成 31 2.2.2 举例 35 2.3 时间复杂性 37 2.3.1 时间复杂性的组成 37 2.3.2 操作计数 37 2.3.3 执行步数 44 2.4 渐进符号(O、 健?、 o) 55 2.4.1 大写O符号 56 2.4.2 椒?58 2.4.3 符号 59 2.4.4 小写o符号 60 2.4.5 特性 60 2.4.6 复杂性分析举例 61 2.5 实际复杂性 66 2.6 性能测量 68 2.6.1 选择实例的大小 69 2.6.2 设计测试数据 69 2.6.3 进行实验 69 2.7 参考及推荐读物 74 第二部分 数据结构 第3章 数据描述 75 3.1 引言 75 3.2 线性表 76 3.3 公式化描述 77 3.3.1 基本概念 77 3.3.2 异常类NoMem 79 3.3.3 操作 79 3.3.4 评价 83 3.4 链表描述 86 3.4.1 类ChainNode 和Chain 86 3.4.2 操作 88 3.4.3 扩充类Chain 91 3.4.4 链表遍历器类 92 3.4.5 循环链表 93 3.4.6 与公式化描述方法的比较 94 3.4.7 双向链表 95 3.4.8 小结 96 3.5 间接寻址 99 3.5.1 基本概念 99 3.5.2 操作 100 3.6 模拟指针 102 3.6.1 SimSpace的操作 103 3.6.2 采用模拟指针的链表 106 3.7 描述方法的比较 110 3.8 应用 111 3.8.1 箱子排序 111 3.8.2 基数排序 116 3.8.3 等价类 117 3.8.4 凸包 122 3.9 参考及推荐读物 127 第4章 数组和矩阵 128 4.1 数组 128 4.1.1 抽象数据类型 128 4.1.2 C++数组 129 4.1.3 行主映射和列主映射 129 4.1.4 类Array1D 131 4.1.5 类Array2D 133 4.2 矩阵 137 4.2.1 定义和操作 137 4.2.2 类Matrix 138 4.3 特殊矩阵 141 4.3.1 定义和应用 141 4.3.2 对角矩阵 143 4.3.3 三对角矩阵 144 4.3.4 三角矩阵 145 4.3.5 对称矩阵 146 4.4 稀疏矩阵 149 4.4.1 基本概念 149 4.4.2 数组描述 149 4.4.3 链表描述 154 第5章 堆栈 161 5.1 抽象数据类型 161 5.2 派生类和继承 162 5.3 公式化描述 163 5.3.1 Stack的效率 164 5.3.2 自定义Stack 164 5.4 链表描述 166 5.5 应用 169 5.5.1 括号匹配 169 5.5.2 汉诺塔 170 5.5.3 火车车厢重排 172 5.5.4 开关盒布线 176 5.5.5 离线等价类问题 178 5.5.6 迷宫老鼠 180 5.6 参考及推荐读物 188 第6章 队列 189 6.1 抽象数据类型 189 6.2 公式化描述 190 6.3 链表描述 194 6.4 应用 197 6.4.1 火车车厢重排 197 6.4.2 电路布线 201 6.4.3 识别图元 204 6.4.4 工厂仿真 206 6.5 参考及推荐读物 217 第7章 跳表和散列 218 7.1 字典 218 7.2 线性表描述 219 7.3 跳表描述 222 7.3.1 理想情况 222 7.3.2 插入和删除 223 7.3.3 级的分配 224 7.3.4 类SkipNode 224 7.3.5 类SkipList 225 7.3.6 复杂性 229 7.4 散列表描述 229 7.4.1 理想散列 229 7.4.2 线性开型寻址散列 230 7.4.3 链表散列 234 7.5 应用——文本压缩 238 7.5.1 LZW压缩 239 7.5.2 LZW压缩的实现 239 7.5.3 LZW解压缩 243 7.5.4 LZW解压缩的实现 243 7.6 参考及推荐读物 247 第8章 二叉树和其他树 248 8.1 树 248 8.2 二叉树 251 8.3 二叉树的特性 252 8.4 二叉树描述 253 8.4.1 公式化描述 253 8.4.2 链表描述 254 8.5 二叉树常用操作 256 8.6 二叉树遍历 256 8.7 抽象数据类型BinaryTree 259 8.8 类BinaryTree 260 8.9 抽象数据类型及类的扩充 263 8.9.1 输出 263 8.9.2 删除 264 8.9.3 计算高度 264 8.9.4 统计节点数 265 8.10 应用 265 8.10.1 设置信号放大器 265 8.10.2 在线等价类 268 8.11 参考及推荐读物 275 第9章 优先队列 276 9.1 引言 276 9.2 线性表 277 9.3 堆 278 9.3.1 定义 278 9.3.2 最大堆的插入 279 9.3.3 最大堆的删除 279 9.3.4 最大堆的初始化 280 9.3.5 类MaxHeap 281 9.4 左高树 285 9.4.1 高度与宽度优先的最大及最小 左高树 285 9.4.2 最大HBLT的插入 287 9.4.3 最大HBLT的删除 287 9.4.4 合并两棵最大HBLT 287 9.4.5 初始化最大HBLT 289 9.4.6 类MaxHBLT 289 9.5 应用 293 9.5.1 堆排序 293 9.5.2 机器调度 294 9.5.3 霍夫曼编码 297 9.6 参考及推荐读物 302 第10章 竞?303 10.1 引言 303 10.2 抽象数据类型WinnerTree 306 10.3 类WinnerTree 307 10.3.1 定义 307 10.3.2 类定义 307 10.3.3 构造函数、析构函数及Winner 函数 308 10.3.4 初始化赢者树 308 10.3.5 重新组织比赛 310 10.4 输者树 311 10.5 应用 312 10.5.1 用最先匹配法求解箱子装载 问题 312 10.5.2 用相邻匹配法求解箱子装载 问题 316 第11章 搜索树 319 11.1 二叉搜索树 320 11.1.1 基本概念 320 11.1.2 抽象数据类型BSTree和 IndexedBSTree 321 11.1.3 类BSTree 322 11.1.4 搜索 322 11.1.5 插入 323 11.1.6 删除 324 11.1.7 类DBSTree 326 11.1.8 二叉搜索树的高度 327 11.2 AVL树 328 11.2.1 基本概念 328 11.2.2 AVL树的高度 328 11.2.3 AVL树的描述 329 11.2.4 AVL搜索树的搜索 329 11.2.5 AVL搜索树的插入 329 11.2.6 AVL搜索树的删除 332 11.3 红-黑树 334 11.3.1 基本概念 334 11.3.2 红-黑树的描述 336 11.3.3 红-黑树的搜索 336 11.3.4 红-黑树的插入 336 11.3.5 红-黑树的删除 339 11.3.6 实现细节的考虑及复杂性分析 343 11.4 B-树 344 11.4.1 索引顺序访问方法 344 11.4.2 m 叉搜索树 345 11.4.3 m 序B-树 346 11.4.4 B-树的高度 347 11.4.5 B-树的搜索 348 11.4.6 B-树的插入 348 11.4.7 B-树的删除 350 11.4.8 节点结构 353 11.5 应用 354 11.5.1 直方图 354 11.5.2 用最优匹配法求解箱子装载 问题 357 11.5.3 交叉分布 359 11.6 参考及推荐读物 363 第12章 图 365 12.1 基本概念 365 12.2 应用 366 12.3 特性 368 12.4 抽象数据类型Graph和Digraph 370 12.5 无向图和有向图的描述 371 12.5.1 邻接矩阵 371 12.5.2 邻接压缩表 373 12.5.3 邻接链表 374 12.6 网络描述 375 12.7 类定义 376 12.7.1 不同的类 376 12.7.2 邻接矩阵类 377 12.7.3 扩充Chain类 380 12.7.4 类LinkedBase 381 12.7.5 链接类 382 12.8 图的遍历 386 12.8.1 基本概念 386 12.8.2 邻接矩阵的遍历函数 387 12.8.3 邻接链表的遍历函数 388 12.9 语言特性 389 12.9.1 虚函数和多态性 389 12.9.2 纯虚函数和抽象类 391 12.9.3 虚基类 391 12.9.4 抽象类和抽象数据类型 393 12.10 图的搜索算法 394 12.10.1 宽度优先搜索 394 12.10.2 类Network 395 12.10.3 BFS的实现 395 12.10.4 BFS的复杂性分析 396 12.10.5 深度优先搜索 397 12.11 应用 399 12.11.1 寻找路径 399 12.11.2 连通图及其构件 400 12.11.3 生成树 402 第三部分 算法设计方法 第13章 贪婪算法 405 13.1 最优化问题 405 13.2 算法思想 406 13.3 应用 409 13.3.1 货箱装船 409 13.3.2 0/1背包问题 410 13.3.3 拓扑排序 412 13.3.4 二分覆盖 415 13.3.5 单源最短路径 421 13.3.6 最小耗费生成树 424 13.4 参考及推荐读物 433 第14章 分而治之算法 434 14.1 算法思想 434 14.2 应用 440 14.2.1 残缺棋盘 440 14.2.2 归并排序 443 14.2.3 快速排序 447 14.2.4 选择 452 14.2.5 距离最近的点对 454 14.3 解递归方程 462 14.4 复杂性的下限 463 14.4.1 最小最大问题的下限 464 14.4.2 排序算法的下限 465 第15章 动态规划 467 15.1 算法思想 467 15.2 应用 469 15.2.1 0/1背包问题 469 15.2.2 图像压缩 471 15.2.3 矩阵乘法链 476 15.2.4 最短路径 480 15.2.5 网络的无交叉子集 483 15.2.6 元件折叠 486 15.3 参考及推荐读物 491 第16章 回溯 492 16.1 算法思想 492 16.2 应用 496 16.2.1 货箱装船 496 16.2.2 0/1背包问题 503 16.2.3 最大完备子图 506 16.2.4 旅行商问题 508 16.2.5 电路板排列 510 第17章 分枝定界 516 17.1 算法思想 516 17.2 应用 519 17.2.1 货箱装船 519 17.2.2 0/1背包问题 526 17.2.3 最大完备子图 528 17.2.4 旅行商问题 529 17.2.5 电路板排列 532
### DeepSeek 本地部署完成后使用方法 #### 浏览器入口 对于希望快速上手并测试模型功能的用户而言,最便捷的方式是通过网页版进行操作。只需打开浏览器,在地址栏中输入指定网址(例如官方提供的 www.deepseek.com 或者本地服务器对应的 IP 地址及端口号),即可进入应用界面[^1]。 #### Python 脚本调用 针对开发者或有编程基础的技术人员来说,则可以利用 Python 编写脚本来实现更复杂的功能需求。下面给出一段简单的代码片段用于展示如何连接到本地运行的服务: ```python import openai openai.api_key = "your_api_key" openai.base_url = "http://localhost:8000/v1" response = openai.ChatCompletion.create( model="deepseek-chat", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Tell me about the weather today"}, {"role": "assistant", "content": ""} ] ) print(response.choices[0].message.content.strip()) ``` 这段程序首先设置了 API 的密钥以及请求的基础 URL;接着定义了一个对话列表作为消息参数传递给 `ChatCompletion` 方法来获取回复内容;最后打印出了由模型生成的回答文字[^4]。 #### GUI 工具交互 除了上述两种方式外,《小白也能看懂的DeepSeek-R1本地部署指南》还提到可以通过特定的应用程序如 Cherry Studio 和 Chatbox 实现图形化界面下的交流体验。这类工具通常提供了更加直观易用的操作环境,适合那些不熟悉命令行指令或是偏好可视化流程管理的人群[^3]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值