首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >数据结构与算法(十一)——线索化二叉树&哈夫曼树

数据结构与算法(十一)——线索化二叉树&哈夫曼树

作者头像
拉维
发布于 2022-04-19 02:16:57
发布于 2022-04-19 02:16:57
1.5K00
代码可运行
举报
文章被收录于专栏:iOS小生活iOS小生活
运行总次数:0
代码可运行

一、线索化二叉树

1,产生的背景

如上图所示,是一个二叉树。可以看到,每一个节点都有三个元素:左子指针域、右子指针域、值域。对于存在左右子树的节点,其左右指针域指向的分别是各自的左右子节点;而对于未存在左子树,或者未存在右子树,或者左右子树均未存在的节点,该节点的左子指针域、右子指针域、左右指针域就会指向为空,此时就会存在指针域空间浪费的情况。而线索化二叉树就可以将这些浪费的指针域空间给利用起来,这是第一个背景。

第二个背景就是,可以更加快速和便捷地查找到某个节点的前驱和后继节点

2,对二叉树进行线索化的逻辑

二叉树的线索化,实际上就是将某个节点的未利用到的指针域空间指向某种遍历次序(前序、中序、后序)下的前驱或者后继节点。如下图所示,实线指向的就是子节点,虚线指向的就是前驱结点或者后序节点:

需要注意的是,前驱或者后继的指向是跟遍历次序有关的,某一个节点的前驱和后继在不同的遍历次序下是不一样的。比如,在前序、中序以及后序遍历的次序下,某个节点的前驱或者后继的指向都是不一样的。

与一般的二叉树相比,线索化的二叉树无非就是将空余的指针域给利用了起来,然后将这些空余的指针域指向某种遍历次序下的前驱或者后继。线索化的目的其实就是为了能够快速找到某一个节点的前驱和后继节点

并不是所有的二叉树节点都可以线索化的,只有当前节点的左子节点或者右子节点为空的时候,当前节点才可以被线索化。若某节点的左子树为空,则该节点的左子指针域指向前驱节点;若某节点的右子树为空,则该节点的右子指针域指向后继节点。

如上图所示,是中序遍历次序下的二叉树中各个前驱后继指针的指向。实际上,我们在对二叉树进行线索化的时候,肯定是需要进行一次遍历的,分析遍历到的每一个节点,如果有无用的指针域,那么就为其设置前驱或者后继。

3,如何判断某节点的左子节点指针指向的是左子节点还是前驱结点

线索化之后,二叉树的某一个节点的左子指针域和右子指针域就都指向了某一个节点,那么我们该如何区分左子指针域指向的是该节点的左子节点还是前驱结点呢?

可以通过在节点结构中加一个左标识(右标识)来判断左子指针域(右子指针域)指向的是左子节点(右子节点)还是前驱节点(后继节点)

如下图所示,就是加了标识位的线索化了的二叉树:

可以看到,此时的节点结构中包含五个元素:值域、左右子节点指针域、左右子节点指针类型

4,代码

(1)二叉树的结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include "string.h"
#include "stdio.h"
#include "stdlib.h"


#include "math.h"
#include "time.h"
#include <stdbool.h>


// 操作的状态
typedef enum : int {
  Success,
  Error,
} Status;


// 指针域指向的类型
typedef enum : int {
  link, // 左子节点、右子节点
  thread, // 线索化,前驱节点、后继节点
} PointerType;


// 节点值的类型
typedef char ElementValueType;
#define Nil '#' // 定义元素的空值


// 节点构造
typedef struct BinaryTreeNode {
  // 值域
  ElementValueType data;

  // 指针域
  struct BinaryTreeNode *leftChild;
  struct BinaryTreeNode *rightChild;

  // 指针域类型
  PointerType leftChildType;
  PointerType rightChildType;
} BinaryTreeNode;


// 二叉树类型
typedef struct BinaryTreeNode * BinaryTree;

(2)二叉树的构造

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 1,二叉树的构造
char *initialString = "ABDH##I##EJ###CF##G##";
int initialIndex = 0;
void createBinaryTree(BinaryTree *tree) {
  // 递归结束条件
  ElementValueType currentValue = initialString[initialIndex];
  if (currentValue == Nil) { // 当前节点值为空值
    return;
  }

  // 为当前节点开辟空间
  *tree = malloc(sizeof(BinaryTreeNode));

  // 给当前节点值域赋值
  (*tree)->data = initialString[initialIndex++];

  // 递归构造左子节点
  createBinaryTree(&((*tree)->leftChild));
  (*tree)->leftChildType = (*tree)->leftChild ? link : thread;

  // 递归构造右子节点
  createBinaryTree(&((*tree)->rightChild));
  (*tree)->rightChildType = (*tree)->rightChild ? link : thread;
}

(3)二叉树的线索化

我们这里采用中序遍历的次序进行线索化。

使用preNode来记录当前遍历到的节点的上一个节点。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 2,中序遍历二叉树,将其线索化
struct BinaryTreeNode *preNode;
void threadBinaryTree(BinaryTree *tree) {
  // 递归结束的条件
  if (!(*tree)) {
    return;
  }

  // 处理左子树
  threadBinaryTree(&((*tree)->leftChild));

  // 左子节点指针域
  if (!(*tree)->leftChild) {
    // 前驱结点
    (*tree)->leftChild = preNode;
    (*tree)->leftChildType = thread;
  } else {
    (*tree)->leftChildType = link;
  }

  // 前驱节点的右子节点指针域
  if (preNode && !preNode->rightChild) {
    preNode->rightChild = *tree;
    preNode->rightChildType = thread;
  } else if (preNode) {
    preNode->rightChildType = link;
  }

  // 更新上一个节点
  preNode = *tree;

  // 处理右子树
  threadBinaryTree(&((*tree)->rightChild));
}

(4)中序遍历二叉树,将其循环线索化

上面👆第(3)步中线索化好了的二叉树,在遍历的时候,实际上就类似于去操作一个双向链表结构。此时我们可以考虑一下,将这个“双向链表”优化成“双向循环链表”,也就是说,我们可以在二叉树根节点的头上再增加一个头结点。关于这个头结点相关的各个指针指向,说明如下:

  • ①头结点的左子节点设置为二叉树的根节点
  • ②头结点的右子节点指针线索化指向为二叉树的最后一个叶子节点
  • ③二叉树的第一个遍历到的节点的左子节点指针线索化为头结点
  • ④二叉树的最后一个叶子节点指针线索化为头结点

图示如下:

这样做的一个好处就是,我可以从第一个节点开始沿着后继节点进行遍历,也可以从最后一个节点开始沿着前驱节点进行遍历

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 3,中序遍历二叉树,将其循环线索化
void circleThreadBinaryTree(BinaryTree *circleThreadedTree, BinaryTree *tree) {
  // 创建头结点
  *circleThreadedTree = malloc(sizeof(BinaryTreeNode));

  // 头结点的左子节点设置为根节点
  (*circleThreadedTree)->leftChild = *tree;
  (*circleThreadedTree)->leftChildType = link;

  // 在线索化二叉树之前设置preNode的初始值,以将二叉树的第一个遍历到的节点的前驱设置为叶子节点
  preNode = *circleThreadedTree;

  // 对二叉树进行线索化
  threadBinaryTree(tree);

  // 头结点的右子节点线索化为二叉树的最后一个叶子节点
  (*circleThreadedTree)->rightChild = preNode;
  (*circleThreadedTree)->rightChildType = thread;

  // 二叉树的最后一个叶子节点的后继设置为头结点
  preNode->rightChild = *circleThreadedTree;
  preNode->rightChildType = thread;
}

(5)遍历循环线索化之后的二叉树

之前我们在遍历未经线索化的二叉树的时候,使用的是递归操作;二叉树经过线索化之后,再对其进行遍历就没必要使用递归了,而是直接通过后继节点和前驱节点进行迭代就可以了。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 4,遍历线索化后的二叉树
void printBinaryNodeValue(ElementValueType element) {
  printf("%d", element);
}


void traverseCircleThreadedBinaryTree(BinaryTree tree) {
  struct BinaryTreeNode *tempNode = tree->leftChild; // 一开始指向根节点,自根节点开始遍历

  while (tempNode != tree) { // 经过一轮遍历之后最终会重新回到头结点,此时结束遍历
    // 找到当前未遍历到的最前面的节点
    while (tempNode->leftChildType == link) {
      tempNode = tempNode->leftChild;
    }

    // 打印当前节点信息
    printBinaryNodeValue(tempNode->data);

    // 通过后继指针依次找到接下来的节点
    while (tempNode->rightChildType == thread && tempNode->rightChild != tree) { // 有后继节点的时候则打印后继节点
      tempNode = tempNode->rightChild;
      printBinaryNodeValue(tempNode->data);
    }

    // 有右子节点的时候,则进行新一轮遍历
    tempNode = tempNode->rightChild;
  }
}

逻辑思路图示如下:

二、哈夫曼树 & 哈夫曼编码

哈夫曼树,又称为最优二叉树,是一种带权路径长度最短的二叉树

哈夫曼编码是一种用于无损数据压缩的权编码算法。

1,哈夫曼树的必要性

如上图所示,学生分五类:优秀(90~100)、良好(80~90)、中等(70~80)、及格(60~70)、不及格(60分以下),这里我是按照成绩依次递增的顺序进行条件判断的。各个成绩区间的学生比例分布如下:

可以看到,有70%的学生是位于70~90的分数区间之内,但这些学生需要经历3~4次判断才可以确定其最终成绩,这样就会多出来很多初期的判断。如果数据量很大,那么就会造成效率问题。接下来我们就将树的路径长度作为指标来分析一下该效率问题。

节点的路径长度指的是,从根节点到该节点的路径上所包括的边的数目。

树的路径长度指的是,该树中所有节点的路径长度之和。

如上图所示,节点D的路径长度是4。

上面二叉树的路径长度是1+1+2+2+3+3+4+4=20。

接下来我按照权重占比,来调整一下条件判断的次序,如下图所示:

此时,占比较高的良好(D)和中等(C)的路径长度都是2,而不是之前的4和3了。

再计算一下二叉树的路径长度,是1+1+2+2+2+2+3+3=16。

可以看到,根据权重占比对二叉树进行改造之后,占比较高的节点的路径长度就变小了。

上面👆我只是计算了二叉树改造前后的路径长度,该路径长度并没有将权重计算进去,此时我们是没有办法据此来判断一个树的效率高低的。接下来我们就来计算一下WPL(Weighted Path Length of Tree),即树的带权重的路径长度,它是树的所有叶子节点的带权路径长度之和,它是可以判断树的效率高低的

首先来看一下改造之前的树的WPL:

一定要注意哦,一棵树的WPL指的是该树所有的叶子节点的加权路径长度之和。因此这里的WPL=315。

接下来再看一下根据权重改造之后的二叉树的WPL:

可以看到,按权重改造优化之后,树的WPL变为了220,对比之前的350,说明效率大大提高了。

2,哈夫曼树的构建思路

假设A、B、C、D、E五个节点的权重值分别是5、15、40、30、10,那么如何来构建一个哈夫曼树呢?

首先,取出两个最小权重节点A和E,小的放左边,大的放右边,并将这俩节点的值相加构建一个新的节点N1:

此时N1的权重值是15。

然后找到剩余节点中权重最小的节点B,此时发现N1和B节点的权重值都是15,所以将B节点放到N1节点的右侧,并生成一个新的节点N2:

此时,N2的权重值是30。

然后找到剩余节点中权重最小的节点D,此时发现N2和D节点的权重值都是30,所以将D节点放到N2节点的右侧,并生成一个新的节点N3:

此时,N3节点的权重值是60。

然后找到剩余节点中权重最小的节点C,其权重值是40,比N3的权重值60要小,所以将C节点放到N3节点的左侧,并生成一个新的节点T:

这里的T节点就是该哈夫曼树的根节点。

此时该树的加权路径长度WPL为40*1+30*2+15*3+5*4+10*4=205。

3,哈夫曼编码思考

现在需要将26个英文字母转换成二进制,假设A是000,后面依次累加,则ABCDEFG…转换成二进制如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
000 001 010 011 100 101 110……

按照这样的规则,字符串BADCADFEED转换成二进制为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
001 000 011 010 000 011 101 100 100 011

接下来我们看一下如何对这些英文字母进行哈夫曼编码

我们知道,在英文语境中,肯定有一些字母是出现频率比较高的,有些是出现频率比较低的。这里我们假设ABCDEF的权重分别为27、8、15、15、30、5,如下:

接下来我按照权重对这些字母进行排序,如下:

然后我们就可以按照权重来构建一个哈夫曼树

首先找到权重最小的两个节点F和B,权重较小的F放在左边,二者生成一个父节点N1,如下:

此时N1的权重值是13。

接下来找到剩余节点中权重最小的那一个C节点,其权重是15,比N1的权重13要大,所以放在N1的右边,如下图所示:

N2是N1和C的双亲节点,其权重值是28。

接着找到余下来的权重最小的节点D,其权重值是15。这里的这个D放在哪里呢,是放在节点N2的右侧吗?不要这样想当然哦~此时需要重新再构建一个子树,与当前权重倒数第二小的节点A(27)分别作为左右子节点,然后生成一个双亲结点N3,如下图所示:

此时,N3的权重值是42,N2的权重值是28。

现在还剩最后一个节点E,其权重值是30,30比28大比42小,所以将节点E放在N2的右侧,并且生成双亲结点N4:

此时,N3的权重值是42,N4的权重值是58。

最后,以N3、N4分别作为左、右子节点,生成一个双亲结点T:

这里的T就是哈夫曼二叉树的根节点,至此,一个哈夫曼二叉树就构建出来了。

可以看到,在哈夫曼二叉树中,很重要的一个原则就是保证左右子树的权重平衡

好,现在已经生成了一个哈夫曼二叉树,接下来我将二叉树中所有的左子树路径全部标记为0,所有的右子树路径全部标记为1,如下图所示:

这样的话,ABCDEF的二进制表示如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
A——01
B——1001
C——101
D——00
E——11
F——1000

然后我们在比较一下BADCADFEED这个字符串的纯二进制表示和哈弗曼编码表示:

可以看到,使用哈夫曼编码表示比使用纯二进制表示要节省了5个字符空间

4,哈夫曼树的构建代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include "string.h"
#include "stdlib.h"
#include <stdbool.h>

// 权重最大值
#define MaxWeight 10000

// 哈夫曼节点结构
typedef struct HaffTreeNode {
  int weight; // 权重值
  int parentIndex; // 父节点坐标
  int leftChildIndex; // 左子节点坐标
  int rightChildIndex; // 右子节点坐标
  bool isAddedIntoTree; // 是否已经加入进了二叉树中
} HaffTreeNode;

// 哈夫曼树结构类型
typedef struct HaffTreeNode * HaffTree;

// 构建哈夫曼树
/*
 ①采用顺序存储的方式
 ②需要从外界传入权重数组相关信息
 */
void createHaffTree(int *weights, int weighttCount, HaffTree tree) {
  // 1,首先初始化所有的节点
  /*
   ①如果叶子节点的个数是n,则该哈夫曼树中就会有2n-1个节点
   ②将n个叶子节点都存放在数组的前n个位置
   ③将所有的叶子节点都赋予指定的权重值,所有的非叶子节点的权重值都赋值为0
   ④将所有节点的父节点坐标都初始化为0,子节点坐标都初始化为-1(代表没有子节点),isAddedIntoTree均设置为false代表未加入到二叉树中
   */
  for (int i = 0; i < 2*weighttCount-1; i++) {
    if (i < weighttCount) { // 当是叶子节点的时候
      tree[i].weight = weights[i];
    } else { // 当是非叶子节点的时候
      tree[i].weight = 0;
    }
    tree[i].parentIndex = 0;
    tree[i].leftChildIndex = -1; // 默认左右子节点都不存在
    tree[i].rightChildIndex = -1;
    tree[i].isAddedIntoTree = false; // 默认未加入到二叉树中
  }

  // 2,循环遍历所有节点,处理其相互关系
  int theMinWeight = MaxWeight; // 最小权重值
  int theSecondToTheMinWeight = MaxWeight; // 倒数第二小权重值
  int indexOfMin = 0; // 最小权重的节点坐标
  int indexOfSecondToMin = 0; // 倒数第二小权重的节点坐标
  /*
   如叶子节点有n个,则总共会有2n-1个节点,所以非叶子节点的个数为n-1
   */
  for (int i = 0; i < weighttCount - 1; i++) {
    // (1)寻找权重最小的俩节点
    /*
     寻找的区间是0~weighttCount+i之间。
     一定要记住,每一次都是找到俩权重最小的节点,然后构建一个新的双亲结点并加入到节点数组中。
     */
    for (int j = 0; j < weighttCount + i; j++) {
      if (tree[j].weight < theMinWeight && !tree[j].isAddedIntoTree) {
        theSecondToTheMinWeight = theMinWeight;
        indexOfSecondToMin = indexOfMin;

        theMinWeight = tree[j].weight;
        indexOfMin = j;
      } else if (tree[j].weight < theSecondToTheMinWeight && !tree[j].isAddedIntoTree) {
        theSecondToTheMinWeight = tree[j].weight;
        indexOfSecondToMin = j;
      }
    }

    // (2)将权重最小的俩节点加入到二叉树中
    /*
     找到权重最小的俩节点之后,将这俩节点组合成一个小树,所以需要生成一个双亲结点,这个双亲结点就存储在节点数组的接下来的位置上。
     一定要注意哦,这里的双亲结点的存储位置是我自己定义的哦,这跟我们之前说的按照层序遍历的次序进行存储是不一样的哦~
     */
    // 处理俩子节点的双亲结点指针指向
    tree[indexOfMin].parentIndex = weighttCount + i;
    tree[indexOfSecondToMin].parentIndex = weighttCount + i;

    // 将俩子节点标记为已经加入到二叉树中
    tree[indexOfMin].isAddedIntoTree = true;
    tree[indexOfSecondToMin].isAddedIntoTree = true;

    // 给父节点权重赋值,并且设置父节点的俩子节点指针
    tree[weighttCount + i].weight = tree[indexOfMin].weight + tree[indexOfSecondToMin].weight;
    tree[weighttCount + i].leftChildIndex = indexOfMin;
    tree[weighttCount + i].rightChildIndex = indexOfSecondToMin;
  }
}

5,哈夫曼编码

哈夫曼编码,指的就是通过哈夫曼树来获取编码。其代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 哈弗曼编码的最大位数
#define MaxHaffCodeSize 100

// 哈夫曼编码结构
typedef struct HaffCode {
  int bits[MaxHaffCodeSize]; // 承载哈弗曼编码的数组
  int finalBitIndex; // 哈弗曼编码在bits数组中占用的结束位置
  int weight; // 当前字母的权重
} HaffCode;

// 根据哈夫曼树来求出哈夫曼编码
void haffCode(HaffTree tree, int count, HaffCode *haffCodes) {
  struct HaffCode *tempHaffCode = malloc(sizeof(HaffCode));
  int childIndex;
  int parentsIndex;

  // 依次遍历各个叶子节点,以获取到每一个叶子节点的哈弗曼编码,并保存在haffCodes中
  for (int i = 0; i < count; i++) {
    // 1,获取当前遍历到的叶子节点的哈弗曼编码,并通过临时变量tempHaffCode来记录
    tempHaffCode->finalBitIndex = 0;
    tempHaffCode->weight = tree[i].weight;

    childIndex = i;
    parentsIndex = tree[childIndex].parentIndex;
    // 自叶子节点一直遍历到根节点
    while (parentsIndex != 0) {
      // 左孩子节点编码为0,右孩子节点编码为1
      tempHaffCode->bits[tempHaffCode->finalBitIndex] = tree[parentsIndex].leftChildIndex == childIndex ? 0 : 1;

      // 将节点在二叉树中位置上移一层
      tempHaffCode->finalBitIndex++;
      childIndex = parentsIndex;
      parentsIndex = tree[childIndex].parentIndex;
    }

    // 2,将临时编码变量tempHaffCode的数据赋值到haffCodes[i]中
    // (1)tempHaffCode中记录的编码是逆序的,因此需要给正序过来
    for (int j = 0; j < tempHaffCode->finalBitIndex - 1; j++) {
      haffCodes[i].bits[j] = tempHaffCode->bits[tempHaffCode->finalBitIndex-j-1];
    }
    // (2)将tempHaffCode中的编码结束位置和权重赋值给haffCodes[i]
    haffCodes[i].finalBitIndex = tempHaffCode->finalBitIndex;
    haffCodes[i].weight = tempHaffCode->weight;
  }
}

以上。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
JavaWeb项目部署到Linux服务器
注意:如下命令必须进入到Tomcat的bin目录才能执行。如果你配置好了环境变量就可以在任何路径下执行了。
兮动人
2021/06/11
3.6K0
Nginx 反向代理 通过域名访问Linux云服务器上
通过域名访问Linux云服务器上 server{ #监听的端口号 listen 80; #域名或二级域名 server_name 二级域名; // api 域名服务商配置 #默认文档 index index.jsp index.html index.htm; #反向代理的主域名+tomcat默认端口,也可以把域名写成IP+端口 location / {
程序员不务正业
2021/12/16
6.2K0
Nginx 反向代理 通过域名访问Linux云服务器上
Linux云服务器部署项目
Linux上使用yum命令后,会将OpenJDK安装到/usr/lib/jvm/目录下
JokerDJ
2023/11/27
8840
Linux云服务器部署项目
高并发解决方案-代理服务器Nginx[通俗易懂]
Nginx 是⼀款高性能的 http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。由俄罗斯的程序设计师伊戈尔·西索夫(Igor Sysoev)所开发,官方测试 Nginx 能够支撑 5 万并发链接,并且cpu、内存等资源消耗却非常低,运行非常稳定。
全栈程序员站长
2022/11/11
1.5K0
高并发解决方案-代理服务器Nginx[通俗易懂]
服务器部署项目
若项目是springboot项目的话,内置了Tomcat,所以只需要java环境即可。
栖西
2023/10/17
3980
使用nginx配置二级域名
打算使用 www.codeliu.com , test1.codeliu.com, test2.codeliu.com这三个域名,其中 test1.codeliu.com, test2.codeliu.com作为二级域名。
秃头哥编程
2019/06/05
8.5K0
使用nginx配置二级域名
Nginx(三)------nginx 反向代理
  Nginx 服务器的反向代理服务是其最常用的重要功能,由反向代理服务也可以衍生出很多与此相关的 Nginx 服务器重要功能,比如后面会介绍的负载均衡。本篇博客我们会先介绍 Nginx 的反向代理,当然在了解反向代理之前,我们需要先知道什么是代理以及什么是正向代理。
IT可乐
2018/09/28
3.5K0
Nginx(三)------nginx 反向代理
单服务器部署多个域名下的多端口服务,使用docker+nginx的方案
历经一个月,我的域名fizz.ink终于备案成功了。于是开始了新一轮的网站折腾。 折腾的目的是想要学习网站部署的细节以及相关知识,域名解析,一台服务器部署多个二级域名的项目。以及Docker的配置。
拿我格子衫来
2022/01/24
2.1K0
Nginx(四)------nginx 负载均衡
  在上一篇博客我们介绍了 Nginx 一个很重要的功能——代理,包括正向代理和反向代理。这两个代理的核心区别是:正向代理代理的是客户端,而反向代理代理的是服务器。其中我们又重点介绍了反向代理,以及如何通过 Nginx 来实现反向代理。那么了解了Nginx的反向代理之后,我们要通过Nginx的反向代理实现另一个重要功能——负载均衡。
IT可乐
2018/09/28
9590
Nginx(四)------nginx 负载均衡
Nginx80端口转发+域名—实现IP+端口隐藏
1.打开conf下nginx.conf 在最后一个 } 前插入 include proxy.conf; 2.新建文件 proxy.conf ,进行如下配置
小诸葛
2020/04/14
7.5K0
Nginx服务器
Nginx 是一款高性能的 http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。由俄罗斯的程序设计师伊戈尔·西索夫(Igor Sysoev)所开发,官方测试 nginx 能够支支撑 5 万并发链接,并且 cpu、内存等资源消耗却非常低,运行非常稳定。
乐心湖
2021/01/18
1.2K0
Nginx服务器
前端人需要掌握的Nginx
作为一个前端开发,我们可能平时或多或少的都接触过Nginx,但是可能也因为平时我们并不需要自己去对他进行一些必要的配置导致我们需要对其进行深入了解,但是实际如果你在工作的业余时间也会开发自己的项目,你会发现,在很多场景下,nginx对于我们有非常多的遍历,同时其性能比较高效,在国内的很多网站中实际都有使用,在了解他之前,我们需要对其有一些基本的认知,首先,我们需要知道
Snine
2022/06/13
1.2K0
Nginx的相关问题
  答:虚拟主机是一种特殊的软硬件技术,它可以将网络上的每一台计算机分成多个虚拟主机,每个虚拟主机可以独立对外提供www服务,这样就可以实现一台主机对外提供多个web服务,每个虚拟主机之间是独立的,互不影响的。通过nginx可以实现虚拟主机的配置,nginx支持三种类型的虚拟主机配置,a、基于ip的虚拟主机, b、基于域名的虚拟主机 c、基于端口的虚拟主机。
别先生
2019/10/08
9590
Nginx的相关问题
云服务上搭建halo博客
为了在使用过程中不出现意外的事故,给出下列推荐的配置,我的是centos7.3版本
兮动人
2021/06/11
1.3K0
ginx反向代理多个域名指向同一个ip的不同网站的方法
  1.当前市面上看到的一些服务器,开放的端口一般都要求为 '80' 端口 所以80端口成了商用端口
习惯说一说
2019/07/04
6.5K0
Docker安装solo博客部署到云服务器教程
文章目录 1. Docker 部署 2. 日志配置 3. Nginx 反向代理 Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动 总的来说,这款博客框架还不错,社区也在不断的完善。最重要的是使用Java写的。像流行的Hexo、Hugo等博客框架都是静态博客,没有后台啥的,主要发表的文章是Markdown转HTML格式的形式发表的,但solo是有后台管理的,与Jpress有些相似。 官网:https
兮动人
2021/06/11
1.8K0
Docker安装solo博客部署到云服务器教程
Nginx
刚开始做这个网站的时候,只有一个定时任务jar包,每天定时给指定用户发短信,后来觉得其他人可能也需要这种服务,在其基础上二次开发,可扩展多任务,多用户定时任务,赚了一点点,这时候已经是部署在tomcat上了,端口占用80; 后来想自己做博客,但是博客想要使用主域名,即原服务需要修改端口,但是这种形式容易忘记端口,还需要去防火墙重新放开一个端口,想的是一个二级域名可以直接访问,这时候想到Nginx!
不期而遇丨
2022/09/09
5280
高并发解决方案相关面试题
DNS域名解析就是讲域名转化为不需要显示端口(二级域名的端口一般为80)的IP地址,域名解析的一般先去本地环境的host文件读取配置,解析成对应的IP地址,根据IP地址访问对应的服务器。若host文件未配置,则会去网络运营商获取对应的IP地址和域名.
全栈程序员站长
2022/08/31
5810
高并发解决方案相关面试题
SpringBoot电商项目实战 — 前后端分离后的优雅部署
在如今的SpringBoot微服务项目中,前后端分离已成为业界标准使用方式,通过使用nginx等代理方式有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务(多个客户展现端,例如:web端,安卓app,IOSapp,微信小程序等)打下坚实的基础。这个步骤是系统架构从猿进化成人的必经之路。
攻城狮的那点事
2019/07/23
2.7K0
Docker部署Nginx代理多个服务:公网域名与内网IP场景全解
在内网环境部署Nginx和应用的话,除非是内网的生产应用会有内网的域名外,基本就是使用内网IP。在这种情况下,可以通过不同的端口号来区分应用,或是不同的路径名来区分应用。
潘晓可
2025/07/13
1150
Docker部署Nginx代理多个服务:公网域名与内网IP场景全解
相关推荐
JavaWeb项目部署到Linux服务器
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验