DataStructureCode

排序

插入排序

直接插入排序

//直接插入排序
void InsertSort(int A[], int n) {
   int i, j, temp;
   for (i = 1; i < n; i++) {  //将各元素插入已经排好序的序列中
       if (A[i] < A[i - 1]) { //若A[i]关键字小于前驱
           temp = A[i];       //用temp暂存A[i]
           for (j = i - 1; j >= 0 && A[j] > temp;
                j--) {          //检查所有前面已拍好序的元素
               A[j + 1] = A[j]; //所有大于temp的元素都向后移动
           }
           A[j + 1] = temp; //复制到插入位置
       }
   }
}
//直接插入排序(带哨兵)
void InsertSort2(int A[], int n) {
   int i, j;
   for (i = 2; i <= n; i++) { //元素下标为1-n,n个元素长度为n+1,所有此处i<=n
       if (A[i] < A[i - 1]) {
           A[0] = A[i];
           for (j = i - 1; A[j] > A[0]; j--) {
               A[j + 1] = A[j];
           }
           A[j + 1] = A[0];
       }
   }
}

折半插入排序

//折半插入排序(带哨兵)
void InsertSort3(int A[], int n) {
    int i, j, low, high, mid;
    for (i = 2; i <= n; i++) {
        A[0] = A[i];
        low = 1, high = i - 1; //设置折半查找范围
        //查找要插入的位置
        while (low <= high) {
            mid = (low + high) / 2;
            if (A[mid] > A[0])
                high = mid - 1;
            else
                low = mid + 1;
        }
        //将low到i-1位置的元素依次往后移
        for (j = i - 1; j >= low; j--) {
            A[j + 1] = A[j];
        }
        A[low] = A[0];
    }
}

希尔排序

//希尔排序
void ShellSort(int A[], int n) {
    int i, j, d;
    //A[0]只是暂存单元,不是哨兵
    for (d = n / 2; d >= 1; d /= 2) { //增量变化
        for (i = d + 1; i <= n; i++) {
            if (A[i] < A[i - d]) {
                A[0] = A[i];
                for (j = i - d; j > 0 && A[0] < A[j]; j -= d) {
                    A[j + d] = A[j];
                }
                A[j + d] = A[0];
            }
        }
    }
}

交换排序

冒泡排序

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}
//从后往前:每趟处理都会将最小元素放在最终位置
void BubbleSort(int A[], int n) {
    for (int i = 0; i < n - 1; i++) { //共进行n-1趟排序
        bool flag = false;
        for (int j = n - 1; j > i; j--) { //一趟冒泡过程
            if (A[j - 1] > A[j]) {        //若为逆序
                swap(A[j - 1], A[j]);     //交换
                flag = true;
            }
        }
        if (flag == false) { //本趟遍历后没有发生变化,说明表已经有序
            return;
        }
    }
}
//从前往后:每趟处理都会将最大元素放在最终位置
void BubbleSort2(int A[], int n) {
    for (int i = 0; i < n - 1; i++) {
        bool flag = false;
        for (int j = 0; j < n - 1 - i; j++) {
            if (A[j] > A[j + 1]) {
                swap(A[j], A[j + 1]);
                flag = true;
            }
        }
        if (flag == false) { //本趟遍历后没有发生变化,说明表已经有序
            return;
        }
    }
}

快速排序

//使用第一个元素将排序序列划分成左右两个部分
int Partition(int A[], int low, int high) {
    int pivot = A[low]; //用第一个元素作为枢纽
    /*
     *确定枢纽元素位置的思路:
     *移动high指针,直到找到比枢纽元素小的元素,执行A[low] = A[high]
     *然后移动low指针,直到找到比枢纽元素大的元素,执行A[high] = A[low]
     *然后再移动high指针.......,循环以上操作,直到low=high为止,
     *low或high即为枢纽元素位置
     */
    while (low < high) { //找出枢纽元素的位置
        while (low < high &&
               A[high] >= pivot) //移动high指针,直到找到比枢纽元素小的元素
            --high;
        A[low] = A[high];
        while (low < high &&
               A[low] <= pivot) //移动low指针,直到找到比枢纽元素大的元素
            ++low;
        A[high] = A[low];
    }
    A[low] = pivot; //枢纽元素存放到最终位置
    return low;
}
void QuickSort(int A[], int low, int high) {
    if (low < high) {//递归跳出的条件
        int pivotpos =Partition(A, low, high); //划分,获取枢纽元素的位置
        QuickSort(A, low, pivotpos-1);//划分左子表
        QuickSort(A, pivotpos+1, high);//划分右子表
    }
}

选择排序

直接选择排序

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}
//选择排序
void SelectSort(int A[], int n) {
    for (int i = 0; i < n; i++) {
        int min = i; //最小元素下标
        for (int j = i + 1; j < n; j++) {
            if (A[j] < A[min]) {
                min = j; //更新最小元素下标
            }
        }
        if (min != i) {
            swap(A[i], A[min]);
        }
    }
}

堆排序

/**
*大根堆:递增序列
*小根堆:递减序列
*/

//将以k为根的子树调整为大根堆
void HeapAdjust(int A[], int k, int len) {
    A[0] = A[k]; // A[0]暂存子树的根节点
    for (int i = 2 * k; i <= len; i *= 2) {
        if (i < len && A[i] < A[i + 1]) //沿k较大的子节点向下筛选
            i++;
        if (i < len && A[0] >= A[i])
            break;
        else {
            A[k] = A[i]; //将A[i]调整到父节点上
            k = i;       //修改k值,继续向下筛选
        }
    }
    A[k] = A[0];//被筛选结点的值放入最终位置
}

//建立大根堆
void BuildMaxHeap(int A[], int len) {
    for (int i = (len / 2); i > 0; i--) {
        HeapAdjust(A, i, len); //从后往前调整所有非终端节点
    }
}

//堆排序的完整逻辑
void HeapSort(int A[], int len) {
    BuildMaxHeap(A, len); //初始建堆
    for (int i = len; i > 1; i--) {//n-1趟的交换和建堆过程
        swap(A[i], A[1]); //堆顶元素和堆底元素进行交换
        HeapAdjust(A, 1, len - 1);//调节剩余元素为堆
    }
}

归并排序

/**
 *算法执行思想:将两个有序序列合并为有序序列
 *      对一个序列来说,其单个的元素即分别为单独的有序序列
 *      所以第一趟处理是将整个序列分成n个单独的子序列
 *      然后再递归的执行合并相邻的有序序列
 */
int n;
int* B = (int*)malloc(n * sizeof(int)); //辅助数组B

// A[low..mid]和A[mid+1...high]各自有序.将两部分合并
void Merge(int A[], int low, int mid, int high) {
    int i, j, k;
    for (k = low; k <= high; k++) {
        B[k] = A[k]; //将A中所有元素复制到B中
    }
    /**
    *以mid为界,B序列中[low,mid]及[mid+1,high]为两个有序序列
     *i指向B中第一个序列的首元素
     *j指向B中第2个序列的首元素
     *k指向A序列中待存放元素的位置
     */
    for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++) {
        if (B[i] <= B[j]) {
            A[k] = B[i++]; //将较小值复制到A中
        } else {
            A[k] = B[j++];
        }
    }
    while (i <= mid) {
        A[k++] = B[i++];
    }
    while (j <= high) {
        A[k++] = B[j++];
    }
}
void MergeSort(int A[], int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2;  //从中间划分
        MergeSort(A, low, mid);      //对左半部分归并排序
        MergeSort(A, mid + 1, high); //对右半天归并排序
        Merge(A, low, mid, high);    //归并
    }
}

查找

顺序查找

#define ElemType int
#define MaxSize 10
typedef struct {
   ElemType* elem; //动态数组基地址
   int TableLen;   //表长
} SSTable;
int Search_Seq1(SSTable L, ElemType key) {
   L.elem[0] = key;
   int i;
   for (i = L.TableLen; L.elem[i] != key; --i)
       ;     //从后往前找
   return i; //若表中不存在关键字为key的元素,将查找到i为0时退出for循环
}

int Search_Seq2(SSTable L, ElemType key) {
   int i;
   for (int i = 0; i < L.TableLen && L.elem[i] != key; i++)
       ;
   return i == L.TableLen ? -1 : i;
}

折半查找

#define ElemType int
#define MaxSize 10
typedef struct {
    ElemType* elem; //动态数组基地址
    int TableLen;   //表长
} SSTable;
//折半查找
int Binary_Search(SSTable L, ElemType key) {
    int low = 0, high = L.TableLen - 1, mid;
    while (low <= high) {
        mid = (high + low) / 2;
        if (L.elem[mid] == key) {
            return mid;
        } else if (L.elem[mid] > key) {
            high = mid - 1;

        } else {
            low = mid + 1;
        }
    }
    return -1;
}

二叉排序树

typedef struct BSTNode {
    int key;
    int length;
    struct BSTNode *lchild, *rchild;
} BSTNode, *BSTree;

//在二叉排序树中查找-非递归实现
BSTNode* BST_Search(BSTree T, int key) {
    while (T != NULL && key != T->key) {
        if (key < T->key)
            T = T->lchild;
        else
            T = T->rchild;
    }
    return T;
}
//在二叉排序树中查找-递归实现
BSTNode* BST_Search2(BSTree T, int key) {
    if (T == NULL) {
        return NULL;
    }
    if (key == T->key) {
        return T;
    } else if (key > T->key) {
        return BST_Search(T->rchild, key);
    } else {
        return BST_Search(T->lchild, key);
    }
}

//二叉排序树的插入
bool Bst_Insert(BSTree& T, int key) {

    if (T == NULL) {
        T = (BSTNode*)malloc(sizeof(BSTNode));
        T->key = key;
        T->lchild = NULL;
        T->rchild = NULL;

        return true;
    } else if (key == T->key) {
        return false;
    } else if (key > T->key) {
        return Bst_Insert(T->rchild, key);
    } else {
        return Bst_Insert(T->lchild, key);
    }
    return true;
}
//二叉排序树的构造
//按照str[]中的关键字序列建立二叉排序树
void Create_BST(BSTree& T, int str[], int n) {
    T = NULL; //初始时T为空树
    int i = 0;
    while (i < n) {
        Bst_Insert(T, str[i]);
        i++;
    }
}
int main() {
    BSTree T;
    int str[4];
    Create_BST(T, str, 4);
}

递归遍历

#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
struct ElemType {
    int data;
};
typedef struct BiTNode {
    ElemType data;
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

void visit(BiTree& T) { cout << "当前根节点" << T->data.data << endl; }
//先序遍历
void PreOrder(BiTree T) {

    if (T != NULL) {
        visit(T);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

//中序遍历
void InOrder(BiTree T) {
    if (T != NULL) {
        PreOrder(T->lchild);
        visit(T);
        PreOrder(T->rchild);
    }
}
//后续遍历
void PostOrder(BiTree T) {
    if (T != NULL) {
        PreOrder(T->lchild);
        PreOrder(T->rchild);
        visit(T);
    }
}
//求树的深度
int treeDepth(BiTree T) {

    if (T == NULL) {
        return 0;
    } else {
        int l = treeDepth(T->lchild);
        int r = treeDepth(T->rchild);
        return l > r ? l + 1 : r + 1;
    }
}

非递归遍历

//中序遍历
void Inoreder2(BiTree T) {
    InitStack(S);              //初始化栈S
    BiTree p = T;              // P是遍历指针
    while (p || !IsEmpty(S)) { //栈不空或p不空时循环
        if (p) {               //一路向左
            push(S, p);        //当前结点入栈
            p = p->lchild;     //左孩子不空,一直向左走
        } else {               //出战,并转向出战结点的右子树
            Pop(S, p);         //栈顶元素出战
            visit(p);          //访问出栈结点
            p = p->rchild; //向右子树走,p赋值为当前结点的右孩子
        }                  //返回while循环继续进入 if-else语句
    }
}

//先序遍历
void PreOrder2(BiTree T) {
    InitStack(S);              //初始化栈S
    BiTree p = T;              // P是遍历指针
    while (p || !IsEmpty(S)) { //栈不空或p不空时循环
        if (p) {               //一路向左
            visit(p);          //访问出栈结点
            push(S, p);        //当前结点入栈
            p = p->lchild;     //左孩子不空,一直向左走
        } else {               //出战,并转向出战结点的右子树
            Pop(S, p);         //栈顶元素出战
            p = p->rchild; //向右子树走,p赋值为当前结点的右孩子
        }                  //返回while循环继续进入 if-else语句
    }
}
/**
 * 后序遍历:
 *    思想:1.沿着根的左孩子,依次入栈,直到左孩子为空
 *         2.读栈顶元素:若其右孩子不空且未被访问过,将右子树转执行,否则,栈顶元素出栈并访问
*/
void PostOrder2(BiTree T) {
    InitStack(S);
    BiTNode* P = T;
    BiTNode* r = NULL;
    while (P || !IsEmpty(S)) {
        if (p) //走到最左边
        {
            push(S, p);
            p = p->lchild;

        } else {                             //向右
            GetTop(S, p);                    //读栈顶顶点(非出栈)
            if (p->rchild && p->rchild != r) //若右子树存在,且未被访问过
            {
                p = p->rchild;  //转向右
            } else {            //否则,弹出结点并访问
                pop(S, p);      //将结点弹出
                visit(p->data); //访问该结点
                r = p;          //记录最近访问过的结点
                p = NULL;       //结点访问完后,重置p指针
            }
        }
    }
}

层序遍历

#include <cstddef>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <ostream>
#include <stdio.h>
#include <stdlib.h>
/**
 *算法思想:
 *1.初始化一个辅助队列
 *2.根节点入队
 *3.若队列非空,则队头结点出队,访问该结点,并将其左右结点插入队尾
 *4.重复3直至队列为空
 **/
typedef struct BiTNode {
    int data;
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

typedef struct LinkNode {
    BiTNode* data; //存储的是结点
    struct LinkNode* next;
} LinkNode;
typedef struct {
    LinkNode *front, *rear;
} LinkQueue;

void EnQueue(LinkQueue& Q, BiTree& T) {
    LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
    s->data = T;
    s->next = NULL;
    if (Q.front == NULL) {
        Q.front = s;
        Q.rear = s;
    } else {
        Q.rear->next = s;
        Q.rear = s;
    }
}
bool DeQueue(LinkQueue& Q, BiTree& T) {
    if (Q.front == NULL) { //空队
        return false;
    }
    LinkNode* q = Q.front;
    T = q->data;
    Q.front = q->next;
    if (Q.rear == q) {
        Q.rear = NULL;
        Q.front = NULL;
    }
    free(q);
    return true;
}
void InitQueue(LinkQueue& Q) {
    Q.front = NULL;
    Q.rear = NULL;
}
bool IsEmpty(LinkQueue& Q) {
    if (Q.front == NULL) {
        return true;
    } else {
        return false;
    }
}
void visit(BiTree& T) { printf("%d\n", T->data); }
//层序遍历
void LevelOrder(BiTree T) {
    LinkQueue Q;
    InitQueue(Q);//初始化辅助队列
    BiTree p;
    EnQueue(Q, T);//将根节点入队
    while (!IsEmpty(Q)) {//队列不空则循环
        DeQueue(Q, p);//队头结点出队
        visit(p);//访问出队结点
        if (p->lchild != NULL) {
            EnQueue(Q, p->lchild);//左结点入队
        }
        if (p->rchild != NULL) {
            EnQueue(Q, p->rchild);//右结点入队
        }
    }
}

中序线索二叉树

#include <cstddef>
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
struct ElemType {
    int data;
};
typedef struct ThreadNode {
    ElemType data;
    struct ThreadNode *lchild, *rchild;
    int ltag, rtag; //左右线索标志
} ThreadNode, *ThreadTree;
//全局变量pre,指向当前访问节点的前驱
ThreadNode* pre = NULL;
void visit(ThreadTree& p) {
    if (p->lchild == NULL) { //左子树为空,建立前驱线索
        p->lchild = pre;
        p->ltag = 1;
    }
    if (pre != NULL && pre->rchild == NULL) {
        pre->rchild = p; //建立前驱节点的后继线索
        pre->rtag = 1;
    }
    pre = p;
}
void InThread(ThreadTree T) {
    if (T != NULL) {
        InThread(T->lchild);
        visit(T);
        InThread(T->rchild);
    }
}

void CreateInThread(ThreadTree& T) {
    pre = NULL;

    if (T != NULL) {
        InThread(T); //中序线索化二叉树
        if (pre->rchild == NULL) {
            pre->rtag = 1; //处理遍历的最后一个结点
        }
    }
}

//寻找中序后继
//找到以p为根的子树中,第一个被中序遍历的结点
ThreadNode* FirstNode(ThreadNode* p) {
    //循环找到最左下结点(不一定是叶结点)
    while (p->ltag == 0) {
        p = p->lchild;
    }
    return p;
}
//寻找指定结点的后继结点
ThreadNode* Nextnode(ThreadNode* p) {
    if (p->rtag == 0) {
        return FirstNode(p->rchild);
    } else {
        return p->rchild; // rtag==1直接返回后继线索
    }
}
//对中序线索二叉树进行中序遍历(非递归算法)
void Inorder(ThreadNode* T) {
    /**
     *FirstNode(T):找到以p为根的子树中,第一个被中序遍历的结点
     *Nextnode(p):找当前结点的中序后继
     *空间复杂度:O(1)
     */
    for (ThreadNode* p = FirstNode(T); p != NULL; p = Nextnode(p)) {
        visit(p);
    }
}

//寻找中序前驱
//找到以p为根的子树中,最后一个被中序遍历的结点
ThreadNode* LastNode(ThreadNode* p) {
    //循环找到最右下结点(不一定是叶结点)
    while (p->rtag == 0) {
        p = p->rchild;
    }
    return p;
}
//寻找指定结点的前驱节点
ThreadNode* PreNode(ThreadNode* p) {
    if (p->ltag == 0) {
        return LastNode(p->lchild);
    } else {
        return p->lchild; // ltag==1直接返回前继线索
    }
}
//对中序线索二叉树进行逆向中序遍历
void RevInorder(ThreadNode* T) {
    /**
     *LastNode(T):找到以p为根的子树中,最后一个被中序遍历的结点
     *PreNode(p):找当前结点的中序前驱
     *空间复杂度:O(1)
     */
    for (ThreadNode* p = LastNode(T); p != NULL; p = PreNode(p)) {
        visit(p);
    }
}

先序线索二叉树

#include <cstddef>
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
/**
 *只有先序遍历才会出现循环指向问题
 */
struct ElemType {
    int data;
};
typedef struct ThreadNode {
    ElemType data;
    struct ThreadNode *lchild, *rchild;
    int ltag, rtag; //左右线索标志
} ThreadNode, *ThreadTree;
//全局变量pre,指向当前访问节点的前驱
ThreadNode* pre = NULL;
void visit(ThreadTree& p) {
    if (p->lchild == NULL) { //左子树为空,建立前驱线索
        p->lchild = pre;
        p->ltag = 1;
    }
    if (pre != NULL && pre->rchild == NULL) {
        pre->rchild = p; //建立前驱节点的后继线索
        pre->rtag = 1;
    }
    pre = p;
}
void PreThread(ThreadTree T) {
    if (T != NULL) {
        visit(T);
        /*解决先序遍历中的循环指向问题
         *左孩子指针一旦线索化,它所指向的节点就是当前节点的前驱节点,
         *然后再遍历该节点的左孩子将会陷入循环
         *
         **/
        if (T->ltag == 0) { // lchild不是前驱线索
            PreThread(T->lchild);
        }
        PreThread(T->rchild);
    }
}

void CreatePreThread(ThreadTree& T) {
    pre = NULL;

    if (T != NULL) {
        PreThread(T); //中序线索化二叉树
        if (pre->rchild == NULL) {
            pre->rtag = 1; //处理遍历的最后一个结点
        }
    }
}

//寻找先序后继
//找到以p为根的子树中,第一个被中序遍历的结点
ThreadNode* FirstNode(ThreadNode* p) {
    //循环找到最左下结点(不一定是叶结点)
    while (p->ltag == 0) {
        p = p->lchild;
    }
    return p;
}
//寻找指定结点的后继结点
ThreadNode* Nextnode(ThreadNode* p) {
    /**
     * p->rtag == 0:说明一定有右节点,但有没有左节点不确定
     *        根据先序遍历规则,根左右,当左结点存在时,即为当前节点后继
     *                                当只有右结点时,后继节点即为右节点
     */
    if (p->rtag == 0) {
        if (p->ltag == 0) {
            return p->lchild;
        } else {
            return p->rchild;
        }

    } else {
        return p->rchild; // rtag==1直接返回后继线索
    }
}

/**寻找指定结点的前驱结点
 *1.若p->ltag==1,则pre=p->lchild
 *2.若p->ltag==0,说明p必有左节点,但按照先序遍历的规则
 *      不能往回找
 * 解决方式:
 *     1.采用传统的先序遍历方式,即重新进行一遍先序遍历
 *     2.创建带有父指针的三叉链表
 *           可能出现的情况:
                1.如果能找到p的父节点,且p是左孩子
                    p的父节点即为其前驱
                2.如果能找到p的父节点,且p是右孩子,其左兄弟为空
                    p的父节点即为其前驱
                3.如果能找到p的父节点,且p是右孩子,其做兄弟非空
                    p的前驱为左兄弟子树中最后一个被先序遍历的节点
                4.如果p是根节点,则p没有先序前驱    
 */

后序线索二叉树

#include <cstddef>
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
struct ElemType {
    int data;
};
typedef struct ThreadNode {
    ElemType data;
    struct ThreadNode *lchild, *rchild;
    int ltag, rtag; //左右线索标志
} ThreadNode, *ThreadTree;
//全局变量pre,指向当前访问节点的前驱
ThreadNode* pre = NULL;
void visit(ThreadTree& p) {
    if (p->lchild == NULL) { //左子树为空,建立前驱线索
        p->lchild = pre;
        p->ltag = 1;
    }
    if (pre != NULL && pre->rchild == NULL) {
        pre->rchild = p; //建立前驱节点的后继线索
        pre->rtag = 1;
    }
    pre = p;
}
void PostThread(ThreadTree T) {
    if (T != NULL) {
        PostThread(T->lchild);
        PostThread(T->rchild);
        visit(T);
    }
}

void CreatePosThread(ThreadTree& T) {
    pre = NULL;

    if (T != NULL) {
        PostThread(T); //中序线索化二叉树
        if (pre->rchild == NULL) {
            pre->rtag = 1; //处理遍历的最后一个结点
        }
    }
}

/**寻找指定结点的前驱结点
 *1.若p->ltag==1,则pre=p->lchild
 *2.若p->ltag==0,说明p必有左节点,但有没有右节点不一定
 *        根据后序遍历规则,左右根,当右结点存在时,即为当前节点前驱
 *                                当只有左结点时,后继节点即为左节点
 */
 //寻找指定结点的前驱结点
ThreadNode* PreNode(ThreadNode* p) {
    if (p->ltag == 0) {
        if (p->rtag == 0) {
            return p->rchild;
        } else {
            return p->lchild;
        }

    } else {
        return p->lchild; // rtag==1直接返回后继线索
    }
}

/**寻找指定结点的后继结点
 *1.若p->rtag==1,则next=p->rchild
 *2.若p->rtag==0,说明p必有右节点,但按照后序遍历的规则
 *      左右节点只能是它的前驱,不能是它的后继
 * 解决方式:
 *     1.采用传统的后续序遍历方式,即重新进行一遍先序遍历
 *     2.创建带有父指针的三叉链表
 *           可能出现的情况:
                1.如果能找到p的父节点,且p是右孩子
                    p的父节点即为其后继
                2.如果能找到p的父节点,且p是左孩子,其右兄弟为空
                    p的父节点即为其后继
                3.如果能找到p的父节点,且p是左孩子,其右兄弟非空
                    p的后继为右兄弟子树中第一个被后序遍历的节点
                4.如果p是根节点,则p没有后序后继   
 */
posted @ 2023-10-16 22:58  Zh'Blog  阅读(131)  评论(0编辑  收藏  举报