20道面试题001

news2024/12/23 2:30:40
常考语法就是指针,指针与数组、指针与字符串、指针与结构体、指针与函数之间的关系与使用, 以上课为准,辅助《深度理解C指针》这本书。
1. 指针与数组
定义: 数组名在表达式中通常被视为指向数组首元素的指针。
访问元素: 可以通过指针访问数组元素。例如,arr[i]等效于*(arr + i)。
传递数组: 在函数中可以通过指针参数传递数组,以避免复制整个数组
    
2. 指针与字符串
定义: 字符串在C中实际上是一个字符数组,字符串常量(如 "hello")被视为字符指针。
操作: 可以使用指针来遍历字符串
传递字符串: 字符串可以作为指向字符的指针传递给函数

3. 指针与结构体
定义: 结构体可以通过指针进行操作,指针可以指向结构体类型。
访问成员: 使用箭头操作符(->)来访问结构体的成员
动态分配: 可以使用指针动态分配结构体的内存
    
4. 指针与函数
函数指针: 可以定义指向函数的指针,允许将函数作为参数传递或返回。
回调函数: 使用函数指针实现回调机制
传递指针: 可以通过指针传递参数,实现对函数外变量的修改    
———————————————————————
2、数据结构与算法:
常见的八大排序,特别的:快排、堆排,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


3. 快速排序
快速排序是基于分治法的排序算法。它通过选择一个"基准"元素,将数组分为两个部分,小于基准的放左边,大于基准的放右边,然后递归地对这两部分进行排序。平均时间复杂度为 (O(n \log n)),最坏情况为 (O(n^2))。

4. 归并排序
归并排序也是一种基于分治法的算法。它将数组分成两半,分别对两部分进行递归排序,然后将两部分合并成一个有序的数组。归并排序的时间复杂度为 (O(n \log n)),并且是稳定的排序算法。

5. 希尔排序
希尔排序是对插入排序的一种改进。它通过将数组分成若干个子序列,对每个子序列进行插入排序,然后逐步减少子序列的数量,最终完成整个数组的排序。希尔排序的时间复杂度因增量选择的不同而有所不同,平均一般为 (O(n^{1.3})) 到 (O(n^{2}))。

6. 堆排序
堆排序利用堆这种数据结构来进行排序。首先将待排序的数组构建成一个最大堆,然后将堆顶元素(最大值)与数组的最后一个元素交换,并重新调整堆。这个过程重复进行,直到堆为空。时间复杂度为 (O(n \log n))。

7. 插入排序
插入排序通过构建一个有序序列,将未排序的元素逐个插入到已排序序列中,直到所有元素都有序。它适合于数据量较小的场景,时间复杂度为 (O(n^2)),但在数据基本有序时效率较高。  
    
1. 选择排序
基本思想是:首先找到数组中的最小元素,将其与数组的第一个元素交换;然后在剩余的元素中继续寻找最小元素,并将其与第二个元素交换,依此类推,直到整个数组排序完成。时间复杂度为 (O(n^2))。

2. 冒泡排序
冒泡排序通过重复比较相邻的元素并交换它们来实现排序。如果在一趟比较中没有发生任何交换,则说明已经排序完成。这个过程重复进行,直到整个数组有序。时间复杂度同样为 (O(n^2))。    
    
数据结构中:链表的操作(创建、插入、删除、逆置、判断环等)、
#include <stdio.h>
#include <stdlib.h>

// 链表节点结构
typedef struct ListNode {
    int value;
    struct ListNode* next;
} ListNode;


// 创建链表
ListNode* create_linked_list(int* values, int size) {
    if (size == 0) return NULL;

    ListNode* head = (ListNode*)malloc(sizeof(ListNode));
    head->value = values[0];
    head->next = NULL;

    ListNode* current = head;
    for (int i = 1; i < size; i++) {
        ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));
        new_node->value = values[i];
        new_node->next = NULL;
        current->next = new_node;
        current = new_node;
    }
    return head;
}
// 在头部插入
ListNode* insert_at_head(ListNode* head, int value) {
    ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));
    new_node->value = value;
    new_node->next = head;
    return new_node;
}

// 在尾部插入
ListNode* insert_at_tail(ListNode* head, int value) {
    ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));
    new_node->value = value;
    new_node->next = NULL;

    if (!head) return new_node;

    ListNode* current = head;
    while (current->next) {
        current = current->next;
    }
    current->next = new_node;
    return head;
}

// 在指定位置插入
ListNode* insert_at_position(ListNode* head, int value, int position) {
    if (position == 0) {
        return insert_at_head(head, value);
    }

    ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));
    new_node->value = value;

    ListNode* current = head;
    for (int i = 0; current != NULL && i < position - 1; i++) {
        current = current->next;
    }

    if (current == NULL) {
        free(new_node); // 释放内存
        return head; // 超出范围
    }

    new_node->next = current->next;
    current->next = new_node;
    return head;
}
// 删除指定值的节点
ListNode* delete_node(ListNode* head, int value) {
    if (!head) return head;

    if (head->value == value) {
        ListNode* temp = head->next;
        free(head);
        return temp; // 删除头节点
    }

    ListNode* current = head;
    while (current->next) {
        if (current->next->value == value) {
            ListNode* temp = current->next;
            current->next = temp->next;
            free(temp);
            return head;
        }
        current = current->next;
    }
    return head;
}
// 逆置链表
ListNode* reverse_linked_list(ListNode* head) {
    ListNode* prev = NULL;
    ListNode* current = head;
    ListNode* next_node = NULL;

    while (current) {
        next_node = current->next; // 保存下一个节点
        current->next = prev;      // 逆转指针
        prev = current;            // 移动 prev
        current = next_node;       // 移动 current
    }
    return prev; // 新的头节点
}
// 判断链表是否存在环        快慢指针
int has_cycle(ListNode* head) {
    ListNode* slow = head;
    ListNode* fast = head;

    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;

        if (slow == fast) {
            return 1; // 存在环
        }
    }
    return 0; // 不存在环
}

int main() {
    int values[] = {1, 2, 3, 4, 5};
    ListNode* head = create_linked_list(values, 5);

    // 测试插入
    head = insert_at_head(head, 0);
    head = insert_at_tail(head, 6);
    head = insert_at_position(head, 7, 3);

    // 测试删除
    head = delete_node(head, 3);

    // 逆置链表
    head = reverse_linked_list(head);

    // 检查环
    printf("Has cycle: %d\n", has_cycle(head));

    // 释放链表内存
    while (head) {
        ListNode* temp = head;
        head = head->next;
        free(temp);
    }

    return 0;
}
树(平衡二叉树、B+树(数据库索引底层数据结构)、
//1. 树的基本概念
树是一种非线性数据结构,由节点组成,节点之间通过边连接。树有以下基本属性:

根节点:树的顶层节点,没有父节点。
叶子节点:没有子节点的节点。
高度:树中节点到叶子的最长路径长度。
深度:节点到根节点的路径长度。
子树:从某个节点开始的子结构。
    
    
//2. 平衡二叉树
	//2.1 定义
平衡二叉树(AVL树)是一种特殊的二叉搜索树(BST),它保证了任何节点的两个子树的==高度差==不超过 1,从而保持树的平衡。这种平衡性确保了查找、插入和删除操作的时间复杂度为 O(log n)。

	//2.2 特点
自平衡:在每次插入或删除之后,AVL树会自动进行==旋转==以保持平衡。
旋转操作:主要有四种旋转操作:
    
右旋 (Right Rotation):当树的左子树比右子树高两个层级,并且插入操作发生在左子树的左侧时(左左情况),需要进行右旋。用于解决左子树过高的问题。
左旋(Left Rotation):当树的右子树比左子树高两个层级,并且插入操作发生在右子树的右侧时(右右情况),需要进行左旋用于解决右子树过高的问题。
左-右旋 (Left-Right Rotation):当树的左子树比右子树高两个层级,并且插入操作发生在左子树的右侧时(左右情况),首先进行左旋,然后进行右旋。用于处理左子树右侧过高的情况。
右-左旋 (Right-Left Rotation):当树的右子树比左子树高两个层级,并且插入操作发生在右子树的左侧时(右左情况),首先进行右旋,然后进行左旋。用于处理右子树左侧过高的情况。
    
	//2.3 时间复杂度
查找:O(log n)
插入:O(log n)
删除:O(log n)
	//2.4 应用
数据库索引
内存中的动态数据结构
实现优先队列
    
    
//3. B+ 树
	//3.1 定义
B+ 树是一种自平衡的树数据结构,广泛用于数据库和文件系统中,特别适合于大规模数据的存储与检索。B+ 树是 B 树的一种变体,它具有所有叶子节点在同一层,并且所有数据都存储在叶子节点中。

	//3.2 特点
多路平衡树:每个节点可以有多个子节点,而不仅仅是两个。B+ 树的每个节点通常包含多个键值对。
叶子节点链接:所有叶子节点通过指针相互链接,便于范围查询。
顺序访问:由于叶子节点的顺序链接,B+ 树能够高效地进行区间查询。
	//3.3 节点结构
内部节点:存储指向子节点的指针和键的值,用于导航。
叶子节点:存储实际的数据记录,并且与其他叶子节点通过指针相连。
	//3.4 操作
查找:与 B 树类似,使用关键字进行查找,复杂度为 O(log n)。
插入:插入操作可能需要分裂节点,复杂度为 O(log n)。
删除:也会涉及节点的合并和调整,复杂度为 O(log n)。
	//3.5 应用
数据库索引(如 MySQL 的 InnoDB 存储引擎)
文件系统(如 NTFS 和 ext4)
大数据存储与检索
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int key;
    struct Node* left;
    struct Node* right;
    int height;
} Node;

// 函数声明
Node* createNode(int key);            // 创建新节点
int getHeight(Node* N);               // 获取节点高度
int getBalance(Node* N);              // 获取平衡因子
Node* rightRotate(Node* y);           // 右旋
Node* leftRotate(Node* x);            // 左旋
Node* insert(Node* node, int key);    // 插入节点(内部包括四种旋转)
Node* minValueNode(Node* node);       // 获取最小值节点
Node* deleteNode(Node* root, int key);// 删除节点
void inorder(Node* root);             // 中序遍历
void preorder(Node* root);            // 前序遍历
void postorder(Node* root);           // 后序遍历
Node* search(Node* root, int key);
// 创建新节点
Node* createNode(int key) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->key = key;
    newNode->left = NULL;
    newNode->right = NULL;
    newNode->height = 1; // 新节点初始高度为 1
    return newNode;
}

// 获取节点高度
int getHeight(Node* N) {
    if (N == NULL)
        return 0;
    return N->height;
}

// 获取平衡因子
int getBalance(Node* N) {
    if (N == NULL)
        return 0;
    return getHeight(N->left) - getHeight(N->right);
}
// 右旋
Node* rightRotate(Node* y) {
    Node* x = y->left;
    Node* T2 = x->right;

    // 旋转
    x->right = y;
    y->left = T2;

    // 更新高度
    y->height = 1 + max(getHeight(y->left), getHeight(y->right));
    x->height = 1 + max(getHeight(x->left), getHeight(x->right));

    return x;
}

// 左旋
Node* leftRotate(Node* x) {
    Node* y = x->right;
    Node* T2 = y->left;

    // 旋转
    y->left = x;
    x->right = T2;

    // 更新高度
    x->height = 1 + max(getHeight(x->left), getHeight(x->right));
    y->height = 1 + max(getHeight(y->left), getHeight(y->right));

    return y;
}
// 插入节点
Node* insert(Node* node, int key) {
    // 1. 执行常规的 BST 插入
    if (node == NULL)
        return createNode(key);
    if (key < node->key)
        node->left = insert(node->left, key);
    else if (key > node->key)
        node->right = insert(node->right, key);
    else // 不允许重复值
        return node;

    // 2. 更新节点的高度
    node->height = 1 + max(getHeight(node->left), getHeight(node->right));

    // 3. 获取平衡因子并检查是否失衡
    int balance = getBalance(node);

    // 如果失衡,则进行旋转

    // 左左情况
    if (balance > 1 && key < node->left->key)
        return rightRotate(node);

    // 右右情况
    if (balance < -1 && key > node->right->key)
        return leftRotate(node);

    // 左右情况
    if (balance > 1 && key > node->left->key) {
        node->left = leftRotate(node->left);
        return rightRotate(node);
    }

    // 右左情况
    if (balance < -1 && key < node->right->key) {
        node->right = rightRotate(node->right);
        return leftRotate(node);
    }

    return node;
}
// 获取最小值节点
Node* minValueNode(Node* node) {
    Node* current = node;
    while (current->left != NULL)
        current = current->left;
    return current;
}

// 删除节点
Node* deleteNode(Node* root, int key) {
    // 1. 执行常规的 BST 删除
    if (root == NULL)
        return root;

    if (key < root->key)
        root->left = deleteNode(root->left, key);
    else if (key > root->key)
        root->right = deleteNode(root->right, key);
    else {
        // 找到该节点
        if ((root->left == NULL) || (root->right == NULL)) {
            Node* temp = root->left ? root->left : root->right;

            // 没有子节点
            if (temp == NULL) {
                temp = root;
                root = NULL;
            } else // 一个子节点
                *root = *temp; // 复制非空子节点的内容

            free(temp);
        } else {
            // 有两个子节点
            Node* temp = minValueNode(root->right);
            root->key = temp->key; // 复制后继节点的值
            root->right = deleteNode(root->right, temp->key); // 删除后继节点
        }
    }

    // 如果树只有一个节点,返回
    if (root == NULL)
        return root;

    // 更新节点高度
    root->height = 1 + max(getHeight(root->left), getHeight(root->right));

    // 获取平衡因子并检查是否失衡
    int balance = getBalance(root);

    // 如果失衡,则进行旋转

    // 左左情况
    if (balance > 1 && getBalance(root->left) >= 0)
        return rightRotate(root);

    // 左右情况
    if (balance > 1 && getBalance(root->left) < 0) {
        root->left = leftRotate(root->left);
        return rightRotate(root);
    }

    // 右右情况
    if (balance < -1 && getBalance(root->right) <= 0)
        return leftRotate(root);

    // 右左情况
    if (balance < -1 && getBalance(root->right) > 0) {
        root->right = rightRotate(root->right);
        return leftRotate(root);
    }

    return root;
}
// 中序遍历
void inorder(Node* root) {
    if (root != NULL) {
        inorder(root->left);
        printf("%d ", root->key);
        inorder(root->right);
    }
}

// 前序遍历
void preorder(Node* root) {
    if (root != NULL) {
        printf("%d ", root->key);
        preorder(root->left);
        preorder(root->right);
    }
}

// 后序遍历
void postorder(Node* root) {
    if (root != NULL) {
        postorder(root->left);
        postorder(root->right);
        printf("%d ", root->key);
    }
}

// 查找节点
Node* search(Node* root, int key) {
    if (root == NULL || root->key == key)
        return root;

    if (key < root->key)
        return search(root->left, key);
    return search(root->right, key);
}
// 主程序
int main() {
    Node* root = NULL;

    // 插入节点
    int keys[] = {10, 20, 30, 40, 50, 25};
    for (int i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) {
        root = insert(root, keys[i]);
    }

    printf("中序遍历结果:");
    inorder(root);
    printf("\n");

    printf("前序遍历结果:");
    preorder(root);
    printf("\n");

    printf("后序遍历结果:");
    postorder(root);
    printf("\n");

    // 查找节点
    int searchKey = 25;
    Node* foundNode = search(root, searchKey);
    if (foundNode) {
        printf("找到节点: %d\n", foundNode->key);
    } else {
        printf("未找到节点: %d\n", searchKey);
    }

    // 删除节点
    root = deleteNode(root, 30);
    printf("删除节点 30 后的中序遍历结果:");
    inorder(root);
    printf("\n");

    return 0;
}

红黑树(STL中关联式容器的底层数据结构,这个比较繁琐一些))、
红黑树是一种自平衡的二叉搜索树,其主要特性是节点被涂成红色或黑色,以确保树的平衡性。红黑树的“红黑”指的是节点的颜色属性。具体来说,红黑树遵循以下几个性质:

节点颜色:每个节点要么是红色,要么是黑色。
根节点:树的根节点是黑色。
叶子节点:每个叶子节点(空节点)是黑色。
红色节点的限制:如果一个节点是红色,则它的两个子节点必须是黑色(即不能有两个连续的红色节点)。
黑色高度:从任意节点到其每个叶子节点的路径都包含相同数量的黑色节点。

这些性质保证了树的高度在一定范围内,从而在最坏情况下也能保持对数时间复杂度的查找、插入和删除操作。通过使用红黑树,能够有效地保持二叉搜索树的平衡性,避免退化为链表的情况。
    
这些特性确保了树的高度是对数级别,从而保证了插入、删除和查找操作的时间复杂度为 (O(\log n))。
节点结构:RBNode 包含键、颜色、左右子节点和父节点指针。
红黑树结构:RBTree 包含根节点和哨兵节点。
创建节点和树:createNode 和 createRBTree 用于初始化节点和树。
左旋和右旋:leftRotate 和 rightRotate 用于维护树的平衡。
插入操作:insert 方法插入新节点并调用 fixInsert 来修复树的性质。
中序遍历:inOrder 和 inOrderHelper 用于打印树的节点。
    
#include <stdio.h>
#include <stdlib.h>

// 节点颜色定义
typedef enum { RED, BLACK } NodeColor;

// 红黑树节点定义
typedef struct RBNode {
    int key;
    NodeColor color;
    struct RBNode *left, *right, *parent;
} RBNode;

// 红黑树定义
typedef struct RBTree {
    RBNode *root;
    RBNode *TNULL; //TNULL: 一个特殊的哨兵节点,用于简化树的操作(如插入和删除)
} RBTree;






// 创建新节点
RBNode* createNode(int key) {
    RBNode* node = (RBNode*)malloc(sizeof(RBNode));
    node->key = key;
    node->left = NULL;
    node->right = NULL;
    node->parent = NULL;
    node->color = RED; // 新节点默认为红色
    return node;
}

// 创建红黑树及哨兵节点
RBTree* createRBTree() {
    RBTree* tree = (RBTree*)malloc(sizeof(RBTree));
    tree->TNULL = createNode(0); // 哨兵节点
    tree->TNULL->color = BLACK; // 哨兵节点为黑色
    tree->root = tree->TNULL;
    return tree;
}

// 左旋转
void leftRotate(RBTree *tree, RBNode *x) {
    RBNode *y = x->right;
    x->right = y->left;

    if (y->left != tree->TNULL) {
        y->left->parent = x;
    }

    y->parent = x->parent;

    if (x->parent == tree->TNULL) {
        tree->root = y;
    } else if (x == x->parent->left) {
        x->parent->left = y;
    } else {
        x->parent->right = y;
    }

    y->left = x;
    x->parent = y;
}

// 右旋转
void rightRotate(RBTree *tree, RBNode *x) {
    RBNode *y = x->left;
    x->left = y->right;

    if (y->right != tree->TNULL) {
        y->right->parent = x;
    }

    y->parent = x->parent;

    if (x->parent == tree->TNULL) {
        tree->root = y;
    } else if (x == x->parent->right) {
        x->parent->right = y;
    } else {
        x->parent->left = y;
    }

    y->right = x;
    x->parent = y;
}

// 插入修正函数
void fixInsert(RBTree *tree, RBNode *k) {
    RBNode *u; // 叔叔节点

    while (k->parent->color == RED) {
        if (k->parent == k->parent->parent->left) {
            u = k->parent->parent->right;
            if (u->color == RED) {
                // 情况 1: 叔叔是红色
                k->parent->color = BLACK;
                u->color = BLACK;
                k->parent->parent->color = RED;
                k = k->parent->parent;
            } else {
                if (k == k->parent->right) {
                    // 情况 2: 当前节点是右孩子
                    k = k->parent;
                    leftRotate(tree, k);
                }
                // 情况 3: 当前节点是左孩子
                k->parent->color = BLACK;
                k->parent->parent->color = RED;
                rightRotate(tree, k->parent->parent);
            }
        } else {
            u = k->parent->parent->left;
            if (u->color == RED) {
                // 情况 1: 叔叔是红色
                k->parent->color = BLACK;
                u->color = BLACK;
                k->parent->parent->color = RED;
                k = k->parent->parent;
            } else {
                if (k == k->parent->left) {
                    // 情况 2: 当前节点是左孩子
                    k = k->parent;
                    rightRotate(tree, k);
                }
                // 情况 3: 当前节点是右孩子
                k->parent->color = BLACK;
                k->parent->parent->color = RED;
                leftRotate(tree, k->parent->parent);
            }
        }
        if (k == tree->root) {
            break;
        }
    }
    tree->root->color = BLACK;
}

// 插入节点
void insert(RBTree *tree, int key) {
    RBNode *node = createNode(key);
    node->parent = tree->TNULL;

    RBNode *y = tree->TNULL;
    RBNode *x = tree->root;

    while (x != tree->TNULL) {
        y = x;
        if (node->key < x->key) {
            x = x->left;
        } else {
            x = x->right;
        }
    }

    node->parent = y;
    if (y == tree->TNULL) {
        tree->root = node;
    } else if (node->key < y->key) {
        y->left = node;
    } else {
        y->right = node;
    }

    node->left = tree->TNULL;
    node->right = tree->TNULL;

    // 修复红黑树
    fixInsert(tree, node);
}

// 中序遍历
void inOrderHelper(RBNode *node, RBTree *tree) {
    if (node != tree->TNULL) {
        inOrderHelper(node->left, tree);
        printf("%d ", node->key);
        inOrderHelper(node->right, tree);
    }
}

// 打印中序遍历
void inOrder(RBTree *tree) {
    inOrderHelper(tree->root, tree);
}

// 主函数测试红黑树
int main() {
    RBTree *tree = createRBTree();

    // 插入节点
    insert(tree, 55);
    insert(tree, 40);
    insert(tree, 65);
    insert(tree, 30);
    insert(tree, 50);
    insert(tree, 60);
    insert(tree, 70);

    // 打印中序遍历
    printf("中序遍历结果: ");
    inOrder(tree);
    printf("\n");

    // 释放内存(在实际使用中需要实现完整的释放逻辑)
    free(tree->TNULL);
    free(tree);
    return 0;
}

红黑树的哨兵节点
哨兵节点(不实际存储数据)在红黑树等数据结构中能够简化操作,主要是因为它提供了一个统一的处理方式,避免了对空指针的特殊情况处理。具体来说,哨兵节点的优势包括:

消除空指针检查:使用哨兵节点允许我们在树的每个节点都可以访问左右子节点,即使是叶子节点。这样一来,很多操作(如插入、删除和遍历)就无需特别判断当前节点是否为空,从而简化代码逻辑。

统一结构:哨兵节点充当了一个虚拟的叶节点(通常为黑色),使得所有非叶节点都遵循相同的结构。这种一致性使得实现各种操作时的逻辑更加直观。

减少边界条件:在执行旋转或调整颜色等操作时,哨兵节点可以作为一个稳定的参考点,减少了边界条件的复杂性。例如,在插入新节点时,无需单独处理根节点的情况,因为根节点的父指针可以指向哨兵节点,而不是 NULL。

简化算法实现:许多维护红黑树性质的算法(如修复插入后的性质)可以通过统一的逻辑实现,避免了多次重复代码。因为哨兵节点的存在,算法的实现可以更加简洁且易于理解
          [10B]
         /      \
     [5R]        [15R]
     /   \       /    \
  [2B]  [7B] [12B]   [20B]
   |      |     |      |
 [NIL]  [NIL] [NIL] [NIL]
哈希表(这个在STL中无序关联式容器也会被问到)。
//哈希表的定义
哈希表(Hash Table)是一种数据结构,通过将键(Key)映射到值(Value)的方式来实现高效的数据存储和查找。它利用哈希函数将键转换为数组的索引,从而支持快速的数据访问。

//哈希表的基本组成
哈希函数:将输入的键转换为固定大小的整数索引。
数组:存储值的容器,通常被称为“桶”或“槽”。
冲突处理机制:当多个键映射到同一索引时,采用的方法以解决此问题(如链地址法、开放寻址法等)。
    
//哈希表的问题
1.hash冲突:多个键可能被哈希到同一个索引。解决方法包括:

链地址法:在每个索引处使用链表存储冲突的键值对。
开放寻址法:在表中寻找下一个空槽来存储冲突的键值对。
负载因子:负载因子是哈希表中元素的数量与数组大小的比率。负载因子过高会影响性能,因此通常会在达到一定负载因子时进行扩容。

2.hash函数的选择:一个好的哈希函数能均匀地分配键值,避免冲突。差的哈希函数会导致大量冲突,从而降低性能。
3.动态扩展:当哈希表装填达到一定阈值时,需要动态扩展,这涉及重新计算每个元素的哈希值并插入新的数组。

//哈希表的优点
快速查找:平均时间复杂度为 O(1),非常高效。
灵活性:可以存储各种类型的数据,不仅限于数字。
哈希表的缺点
内存消耗:为了减少冲突,可能会预留较多的空间。
不支持有序操作:哈希表内部没有顺序,无法按键的顺序遍历。    
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TABLE_SIZE 10  // 哈希表大小

// 键值对结构
typedef struct KeyValue {
    char *key;
    int value;
    struct KeyValue *next; // 指向下一个节点
} KeyValue;

// 哈希表结构
typedef struct HashTable {
    KeyValue **table; // 哈希桶数组
} HashTable;

// 哈希函数
unsigned int hash(const char *key) {
    unsigned long int hashval = 0;
    while (*key != '\0') {
        hashval = (hashval << 5) + *key++; // 计算哈希值
    }
    return hashval % TABLE_SIZE;
}

// 创建哈希表
HashTable *create_table() {
    HashTable *ht = malloc(sizeof(HashTable));
    ht->table = malloc(sizeof(KeyValue *) * TABLE_SIZE);
    for (int i = 0; i < TABLE_SIZE; i++) {
        ht->table[i] = NULL; // 初始化为NULL
    }
    return ht;
}

// 插入键值对
void insert(HashTable *ht, const char *key, int value) {
    unsigned int index = hash(key);
    KeyValue *new_pair = malloc(sizeof(KeyValue));
    new_pair->key = strdup(key); // 复制键
    new_pair->value = value;
    new_pair->next = ht->table[index]; // 将新节点插入链表头
    ht->table[index] = new_pair;
}

// 查找值
int search(HashTable *ht, const char *key) {
    unsigned int index = hash(key);
    KeyValue *pair = ht->table[index];
    while (pair != NULL) {
        if (strcmp(pair->key, key) == 0) {
            return pair->value; // 找到返回值
        }
        pair = pair->next; // 移动到下一个节点
    }
    return -1; // 未找到返回-1
}

// 删除键值对
void delete(HashTable *ht, const char *key) {
    unsigned int index = hash(key);
    KeyValue *pair = ht->table[index];
    KeyValue *prev = NULL;

    while (pair != NULL) {
        if (strcmp(pair->key, key) == 0) {
            if (prev == NULL) {
                ht->table[index] = pair->next; // 删除头节点
            } else {
                prev->next = pair->next; // 删除中间或尾节点
            }
            free(pair->key);
            free(pair);
            return;
        }
        prev = pair;
        pair = pair->next;
    }
}

// 释放哈希表
void free_table(HashTable *ht) {
    for (int i = 0; i < TABLE_SIZE; i++) {
        KeyValue *pair = ht->table[i];
        while (pair != NULL) {
            KeyValue *temp = pair;
            pair = pair->next;
            free(temp->key);
            free(temp);
        }
    }
    free(ht->table);
    free(ht);
}

int main() {
    HashTable *ht = create_table();
    
    insert(ht, "apple", 1);
    insert(ht, "banana", 2);
    insert(ht, "orange", 3);

    printf("Value for 'apple': %d\n", search(ht, "apple"));
    printf("Value for 'banana': %d\n", search(ht, "banana"));
    
    delete(ht, "banana");
    printf("Value for 'banana' after deletion: %d\n", search(ht, "banana"));

    free_table(ht);
    return 0;
}

———————————————————————
3、Linux:
进程与线程的基本概念、
进程和线程是操作系统中两个重要的概念,它们用于管理程序的执行。

//进程(Process)
定义:进程是一个正在运行的程序实例,是操作系统资源分配的基本单位。每个进程都有自己的地址空间、数据栈和其他辅助数据。

特性:
独立性:进程之间相互独立,一个进程的崩溃不会直接影响到其他进程。
资源拥有:每个进程有自己的资源,如内存、文件句柄等。
状态:进程可以处于创建、就绪、运行、阻塞和终止等状态。
调度:操作系统通过==调度算法==来管理多个进程的运行,确保系统资源的合理利用。

//线程(Thread)
定义:线程是进程中的一个执行单元,是程序执行的最小单位。一个进程可以包含多个线程,这些线程共享进程的资源。

特性:
轻量级:线程比进程更轻量,创建和销毁的开销较小。
共享资源:同一进程内的线程可以共享该进程的内存和资源,从而能够更高效地进行通信。
并发执行:多个线程可以并发执行,提高程序的运行效率。
调度:线程调度通常由操作系统或运行时环境负责,支持多线程并发执行。

//进程与线程的比较
特性	      进程	      			线程
资源拥有	拥有独立的资源			 共享进程的资源
创建开销	较大	        		 较小
通信方式	通过进程间通信(IPC)	通过共享内存、信号量等
切换开销	较大					较小
独立性		 进程独立	 			线程依赖于所在的进程
//总结
进程是资源分配的基本单位,而线程是程序执行的基本单位。
理解进程和线程的区别有助于更好地进行程序设计和优化,以提高系统性能和响应速度。
进程间通信的几种方式以及区别;
进程间通信(Inter-Process Communication, IPC)是指在不同进程之间交换数据和信息的机制。以下是几种常见的进程间通信方式及其区别:

1. 管道(Pipe)
定义:管道是一种半双工的通信方式,允许一个进程将数据写入管道,而另一个进程则从管道中读取数据。
特点:
只能在具有亲缘关系的进程间使用(如父子进程)。
数据传输是顺序的,先进先出(FIFO)。
适合小量数据的传输。
    
2. 命名管道(Named Pipe)
定义:命名管道也是一种管道,但它通过一个名字在文件系统中创建,可以在不相关的进程间通信。
特点:
支持双向通信。
允许不相关的进程进行数据传输。
仍然保持先进先出的特性。
    
3. 消息队列(Message Queue)
定义:消息队列允许进程以消息的形式异步地发送和接收数据。
特点:
支持多个发送者和接收者。
消息可以按照优先级处理。
适合需要异步处理的大量数据传输。
    
4. 共享内存(Shared Memory)
定义:共享内存允许多个进程直接访问同一块内存区域,以进行数据交换。
特点:
速度快,因为数据直接在内存中共享。
需要同步机制(如信号量)来防止竞争条件。
适合大规模数据传输。
    
5. 信号(Signal)
定义:信号是一种用于通知进程某个事件发生的机制,例如外部中断或进程状态变化。
特点:
主要用于进程之间的事件通知。
信号本身不传递数据,只传递控制信息。
信号处理较为复杂,易出错。
    
6. 套接字(Socket)
定义:套接字是一种用于在网络上或本地计算机上进行进程间通信的机制。
特点:
支持跨网络的进程通信。
可以是面向连接(TCP)或无连接(UDP)的。
适合需要网络通信的应用场景。
    
7. 文件映射(Memory-Mapped Files)
定义:文件映射允许多个进程访问同一文件的内容,就像访问内存一样。
特点:
适合大数据量的共享。
通过操作系统的文件系统进行管理。
需要注意同步问题。
OSI七层模型,每一层常规协议;
//从下到上,物链网传会表应

1. 物理层 (Physical Layer)
功能: 负责数据的物理传输,包括电气信号、光信号或无线信号等。
常规协议/标准:Ethernet (IEEE 802.3),USB,DSL,RS-232
    
2. 数据链路层 (Data Link Layer)
功能: 提供节点间的数据帧传输,处理物理地址和错误检测。
常规协议/标准:Ethernet (IEEE 802.3),Wi-Fi (IEEE 802.11),PPP (Point-to-Point Protocol)
HDLC (High-Level Data Link Control)
    
3. 网络层 (Network Layer)
功能: 负责数据包的路由选择和转发,处理逻辑地址(如IP地址)。
常规协议/标准:IP (Internet Protocol),ICMP (Internet Control Message Protocol)
IGMP (Internet Group Management Protocol),RIP (Routing Information Protocol)
    
4. 传输层 (Transport Layer)
功能: 提供端到端的通信,确保数据完整性和顺序。
常规协议/标准:TCP (Transmission Control Protocol),UDP (User Datagram Protocol)
SCTP (Stream Control Transmission Protocol)
    
5. 会话层 (Session Layer)
功能: 管理会话和连接,控制对话的建立、维护和终止。
常规协议/标准:NetBIOS,RPC (Remote Procedure Call),PPTP (Point-to-Point Tunneling Protocol)
    
6. 表示层 (Presentation Layer)
功能: 数据格式化和转换,处理加密和解密。
常规协议/标准:SSL/TLS (Secure Sockets Layer / Transport Layer Security)
JPEG, GIF, PNG (图像格式),ASCII, EBCDIC (字符编码)
    
7. 应用层 (Application Layer)
功能: 提供用户与应用程序之间的接口,处理高层协议。
常规协议/标准:HTTP/HTTPS (Hypertext Transfer Protocol),FTP (File Transfer Protocol)
SMTP (Simple Mail Transfer Protocol),DNS (Domain Name System)
TCP三次握手与四次挥手;
//TCP三次握手(建立连接)
第一次握手:客户端发送一个SYN(同步)报文段,带有初始序列号,表示请求建立连接。
第二次握手:服务器收到SYN报文段后,回复一个SYN-ACK(同步-确认)报文段,表示同意建立连接,同时也发送			 自己的初始序列号。
第三次握手:客户端收到SYN-ACK报文段后,发送一个ACK(确认)报文段,确认收到服务器的SYN,此时连接建			  立完成。

//TCP四次挥手(终止连接)
第一次挥手:主动关闭连接的一方(例如客户端)发送一个FIN(结束)报文段,表示希望关闭连接。
第二次挥手:另一方(例如服务器)收到FIN报文段后,发送一个ACK报文段,确认收到关闭请求,这一阶段连接		  的一方进入FIN_WAIT_1状态。
第三次挥手:服务器在处理完所有数据后,发送一个FIN报文段,表示也希望关闭连接。
第四次挥手:客户端收到FIN报文段后,发送一个ACK报文段,确认关闭请求,此时客户端进入TIME_WAIT状态,		  等待可能的重发报文段,之后最终关闭连接。

//总结
三次握手用于建立连接,确保双方都准备好进行数据传输。
四次挥手用于安全地终止连接,确保所有数据都已成功传输并接收。
socket网络编程的流程;
//server
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);

// 创建 socket
server_socket = socket(AF_INET, SOCK_STREAM, 0);

// 设置地址结构 sockaddr_in
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有 IP
server_addr.sin_port = htons(PORT);

// 绑定 bind
bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

// 监听 listen
listen(server_socket, BACKLOG);

// 接受连接 accept
client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &addr_len);

// 数据传输 send recv
recv(client_socket, buffer, sizeof(buffer), 0);
send(client_socket, response, sizeof(response), 0);

// 关闭连接 close
close(client_socket);
close(server_socket);

//client
int client_socket;
struct sockaddr_in server_addr;

// 创建 socket
client_socket = socket(AF_INET, SOCK_STREAM, 0);

// 设置地址结构 address_in
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "SERVER_IP", &server_addr.sin_addr); // 服务器 IP

// 连接到服务器 connect
connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

// 数据传输 send recv
send(client_socket, request, sizeof(request), 0);
recv(client_socket, buffer, sizeof(buffer), 0);

// 关闭连接 close
close(client_socket);

三种IO多路复用的原理、底层数据结构、使用方法,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

IO多路复用是指在单个线程中同时监控多个文件描述符的状态,以便在某个文件描述符准备好进行读写操作时,能够及时响应。常见的三种IO多路复用机制是:select、poll和epoll。以下是它们的原理和底层数据结构的简要介绍。

1. select
//原理
select函数允许程序监视多个文件描述符,以查看它们是否可以进行读、写或异常处理。
它使用一个集合(fd_set)来表示要监视的文件描述符。
//底层数据结构
fd_set:一个位域数组,每一位对应一个文件描述符。最大文件描述符的数量受限于系统常量FD_SETSIZE(通常为1024)。
使用宏FD_SET、FD_CLR、FD_ISSET来操作这个集合。
//缺点
文件描述符的数量受到FD_SETSIZE的限制,且每次调用select都需要重新设置监视的集合。
效率较低,在高并发场景下性能下降明显。
    
2. poll
//原理
poll函数与select类似,但它不使用固定大小的集合,而是使用一个可扩展的数组来存储待监视的文件描述符及其事件。
它能监视更多的文件描述符,没有FD_SETSIZE的限制。
//底层数据结构
struct pollfd:包含文件描述符和事件类型的结构体。
poll函数接受一个pollfd数组和其大小作为参数。
//缺点
每次调用poll都需传递整个数组,仍然存在线性扫描的问题,性能在大量文件描述符时可能会下降。

3. epoll
//原理
epoll是Linux特有的高效IO多路复用机制,设计用于处理大量并发连接。
它通过内核空间与用户空间的分离来提高效率,只在感兴趣的文件描述符上进行操作。
//底层数据结构
epoll_event:包含文件描述符及其关注的事件。
epoll接口提供了epoll_create、epoll_ctl和epoll_wait等函数来创建、控制和等待事件。
//优点
可以处理大规模的文件描述符,性能优于select和poll,尤其是在大量连接的情况下。
支持边缘触发(edge-triggered)和水平触发(level-triggered)两种模式。
struct pollfd {
    int fd;         // 文件描述符
    short events;   // 关注的事件(如可读、可写等)
    short revents;  // 返回的事件(实际发生的事件)
};

struct epoll_event {
    uint32_t events;    // 关注的事件(如EPOLLIN、EPOLLOUT等)
    epoll_data_t data;  // 用户自定义的数据
};
特别是epoll的用法、边沿触发与水平触发的区别与联系;
//边沿触发与水平触发的区别与联系
水平触发(Level Triggered)
定义:在水平触发模式下,只要文件描述符的状态满足条件(例如可读或可写),epoll_wait就会返回该事件。
特点:
只要数据尚未被读取,事件就会持续触发。
程序需要确保在处理完事件后尽快读取数据,以避免反复触发同一个事件。
使用场景:适合大多数应用,因为简单易理解。
    
边缘触发(Edge Triggered)
定义:在边缘触发模式下,只有当文件描述符的状态从未准备好变为准备好时,epoll_wait才会返回该事件。
特点:
一旦事件被触发,如果没有新数据到来,之后不会再次触发。
程序必须在事件触发时尽可能多地读取数据,否则可能会遗漏后续的可读事件。
使用场景:适合高性能、低延迟的应用,能够减少不必要的系统调用,但需要更复杂的逻辑处理。
    
联系
性能:边缘触发通常在高负载情况下提供更好的性能,因为它减少了重复唤醒的次数。
实现复杂性:边缘触发的实现需要开发者更加小心,以确保数据完全被读取,避免漏掉后续事件。
选择:开发者需要根据应用场景和性能需求选择使用水平触发或边缘触发。
// 添加文件描述符时设置边缘触发
ev.events = EPOLLIN | EPOLLET; // 边缘触发
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

// 处理事件时确保尽可能多地读取数据
while (1) {
    int n = read(fd, buffer, sizeof(buffer));
    if (n <= 0) {
        // 处理错误或EOF
        break;
    }
    // 处理读取的数据
}
同步、异步、阻塞、非阻塞;
阻塞/非阻塞关注的是/*==用户态进程/线程==*/的状态
其要访问的数据是否就绪,进程/线程是否需要等待。当前接口数据还未准备就绪时,线程是否被阻塞挂起。
    //阻塞挂起
    就是当前线程还处于CPU时间片当中,调用了阻塞的方法,由于数据未准备就绪,则时间片还未到就让出CPU。
    //非阻塞
    就是当前接口数据还未准备就绪时,线程不会被阻塞挂起,可以不断轮询请求接口,看看数据是否已经准备就绪。


同步/异步关注的是/*消息通信机制*/
    同步,就是在发出一个调用时,自己需要参与等待结果的过程,则为同步。同步需要主动读写数据,在读写数据的过程中还是会阻塞。
    异步IO,则指出发出调用以后到数据准备完成,自己都未参与,则为异步。异步只需要关注IO操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。
五种网络IO模型;
1. 阻塞I/O (Blocking I/O)
定义:在阻塞I/O模型中,调用I/O操作时,程序会被挂起,直到I/O操作完成。
特点:
程序在进行I/O操作时无法执行其他任务。
简单易用,但在高并发情况下可能导致性能瓶颈。
    
2. 非阻塞I/O (Non-blocking I/O)
定义:在非阻塞I/O模型中,I/O操作不会使程序挂起。如果数据不可用,操作会立即返回。
特点:
程序可以执行其他任务,不会因I/O操作而阻塞。
通常需要轮询或使用回调机制来检查数据是否可用。
    
3. I/O复用 (I/O Multiplexing)
定义:通过使用select、poll或epoll等系统调用,程序可以监视多个I/O流,并处理其中有数据的流。
特点:
适合于高并发场景,可以同时处理多个连接。
程序在等待I/O时不会被阻塞,而是等待多个文件描述符的状态变化。
    
4. 信号驱动I/O (Signal-driven I/O)
定义:在信号驱动I/O模型中,程序注册一个信号处理程序,以便在I/O操作就绪时接收信号通知。
特点:
可以避免轮询,提高效率。
复杂性较高,需要处理信号安全和信号处理的细节。
    
5. 异步I/O (Asynchronous I/O)
定义:在异步I/O模型中,程序发起I/O请求后立即返回,并在I/O操作完成时通过回调函数或其他机制获得结果。
特点:
无需轮询或等待,能够有效利用CPU资源。
编程模型相对复杂,需要管理回调和状态。
常见的并发服务器模型。
1. 单线程模型 (Single-threaded Model)
定义:服务器在一个线程中处理所有请求,通常使用非阻塞I/O或I/O复用来管理多个连接。
特点:
简单实现,无需多线程管理。
适合小型应用和低并发场景。
可能在高负载下成为瓶颈。
    
2. 多线程模型 (Multi-threaded Model)
定义:每个客户端请求由一个独立的线程处理。
特点:
充分利用多核CPU,能处理多个请求。
线程切换开销较高,可能导致性能下降。
需要管理线程的创建、销毁和同步。
    
3. 进程模型 (Multi-process Model)
定义:每个请求由一个独立的进程处理,通常使用系统的多进程支持。
特点:
提供更强的隔离性和安全性,避免了线程间共享内存的问题。
进程间通信开销较大。
启动和管理进程的开销高于线程。
    
4. 线程池模型 (Thread Pool Model)
定义:预先创建一组线程,客户端请求到达时从线程池中获取线程处理,处理完成后返回线程池。
特点:
降低了线程创建和销毁的开销。
可以控制并发量,提高资源利用率。
适合高并发场景。
    
5. 事件驱动模型 (Event-driven Model)
定义:使用事件循环和回调机制响应客户端的请求,通常结合非阻塞I/O。
特点:
高效处理大量连接,适合I/O密集型应用。
复杂性较高,需管理事件和回调。
Node.js 和 Nginx 是基于此模型的典型例子。
    
6. 混合模型 (Hybrid Model)
定义:结合多线程和事件驱动模型,例如在每个线程中使用事件循环。
特点:
灵活性高,可以根据需求进行调整。
适合复杂的应用场景,能够同时处理计算密集型和I/O密集型任务。
———————————————————————
4、C++的基础:
常见的基础语法,比如:const可以修饰哪些(指针常量与常量指针的区别、
const int *ptr:指向的值是常量,不能通过指针修改它的值,但指针本身可以指向其他变量。
int *const ptr:指针是常量,不能改变指向,但可以修改指向的值。
    
const 修饰   变量,指针,成员函数,参数    
数组指针与指针数组、函数指针与指针数组的区别,这个上课怎么讲的);
int (*ptr)[5] = &arr; // ptr是指向包含5个int类型元素的数组的指针
int *arr[5]; // arr是一个包含5个指针的数组
void (*funcPtr)(int) = myFunction; // funcPtr是指向myFunction的指针
void (*funcArr[2])(int) = {func1, func2}; // funcArr是一个包含2个函数指针的数组

1. 数组指针与指针数组
数组指针(Pointer to Array)
定义:指向一个数组的指针。        声明:通常使用type (*ptr)[size]的形式。
指针数组(Array of Pointers)
定义:数组中的每个元素都是指针。  声明:通常使用type *arr[size]的形式。 
    
2. 函数指针与指针数组
函数指针(Pointer to Function)
定义:指向函数的指针,可以用来调用指向的函数。  return_type (*ptr)(parameter_types)的形式    
指针数组(Array of Function Pointers)
定义:数组中的每个元素都是指向函数的指针。  return_type (*arr[size])(parameter_types)的形式    
四种强制转换叫什么,有什么区别;
float b = static_cast<float>(a); // int 转 float
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 安全转换,基类转派生类
int* b = const_cast<int*>(&a); // 去掉const修饰
void* ptr = reinterpret_cast<void*>(&a); // int* 转 void*

static_cast
用途:用于在相关类型之间进行安全的转换,如基本数据类型(int、float等)之间的转换,或者类层次结构中的基类和派生类之间的转换。
特点:编译时检查,确保类型转换的安全性。
    
dynamic_cast
用途:主要用于类的多态性,安全地将基类指针或引用转换为派生类指针或引用。
特点:运行时检查,如果转换不安全,将返回nullptr(对于指针)或抛出std::bad_cast(对于引用)
    
const_cast
用途:用于去掉对象的常量性(const),允许修改原本被声明为const的对象。
特点:只能添加或去除常量性,不改变对象的类型
    
reinterpret_cast
用途:用于进行底层的类型转换,可以将指针类型转换为任何其他指针类型,或将整数类型转换为指针类型等。
特点:不进行任何检查,转换结果可能是不安全的,使用时需谨慎    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2183526.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络:计算机网络概述:网络、互联网与因特网的区别

文章目录 网络、互联网与因特网的区别网络分类 互联网因特网基于 ISP 的多层次结构的互连网络因特网的标准化工作因特网管理机构因特网的组成 网络、互联网与因特网的区别 若干节点和链路互连形成网络&#xff0c;若干网络通过路由器互连形成互联网 互联网是全球范围内的网络…

PWM驱动LED呼吸灯

背景知识&#xff1a;TIM输出比较-CSDN博客 stm32f10x_tim.h函数 // *** OC是Output Compare输出比较函数 void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TI…

给Windows系统设置代理的操作方法

一、什么是代理 网络代理是一种特殊的网络服务&#xff0c;允许一个网络终端通过这个服务与另一个网络终端进行非直接的连接&#xff0c;而提供代理服务的电脑系统或其它类型的网络终端被称为代理服务器。 代理服务器是网络信息的中转站&#xff0c;代理服务器就像是一个很大的…

56. QTreeWidget的基本使用

1. 说明 在软件开发中会遇到将数据信息制作成一种树目录的形式进行展示,那么此时就可以借助QT提供的QTreeWidget控件来实现这种需求,本篇博客会做一个案例简要说明这个控件的基本使用方法,博客中代码能够实现的功能是将此项目代码所在文件夹中的内容展示出来,如下图所示:…

模式识别编程实践1:身高和/或体重数据进行性别分类

&#x1f31e;欢迎莅临我的个人主页&#x1f448;&#x1f3fb;这里是我专注于深度学习领域、用心分享知识精粹与智慧火花的独特角落&#xff01;&#x1f349; &#x1f308;如果大家喜欢文章&#xff0c;欢迎&#xff1a;关注&#x1f377;点赞&#x1f44d;&#x1f3fb;评论…

回溯大总结

目录 0、基础什么是回溯&#xff1f;回溯法解决的问题回溯模板 1、组合问题77. 组合216.组合总和III17. 电话号码的字母组合39. 组合总和&#xff1a;40.组合总和II 0、基础 什么是回溯&#xff1f; 回溯是一种穷举的搜索算法&#xff0c;并不是一个高效的算法&#xff0c;当…

高并发内存池(五):ThreadCache、CentralCache和PageCache的内存回收机制、阶段性代码展示和释放内存过程的调试

目录 ThreadCache的内存回收机制 补充内容1 补充内容2 补充内容3 补充内容4 ListTooLong函数的实现 CentralCache的内存回收机制 MapObjectToSpan函数的实现 ReleaseListToSpans函数的实现 PageCache的内存回收机制 补充内容1 补充内容2 ReleaseSpanToPageCache函…

【Spine】引入PhotoshopToSpine脚本

引入 右键Photoshop图标&#xff0c;选择属性 打开文件所在位置 找到目录下的\Presets\Scripts文件夹。 找到Spine目录下的\scripts\photoshop文件夹下的PhotoshopToSpine.jsx 复制它&#xff0c;丢到Photoshop刚才找的那个目录下。 使用 打开.psd文件&#xff0c;检查不要…

二叉树:总结篇!【需要掌握的二叉树技能都在这里啦】

文章目录 前言二叉树理论基础二叉树理论基础二叉树的遍历方式深度优先遍历广度优先遍历 N叉树的遍历方式求二叉树的属性二叉树&#xff1a;是否对称二叉树&#xff1a;求最大深度二叉树&#xff1a;求最小深度二叉树&#xff1a;求有多少个节点二叉树&#xff1a;是否平衡二叉树…

外贸财务软件精选,提升管理效率与精准度

ZohoBooks、QuickBooks等六款会计软件各具特色&#xff0c;支持多币种、国际化等功能&#xff0c;适合不同规模外贸企业。其中&#xff0c;ZohoBooks功能全面&#xff0c;QuickBooks操作简便&#xff0c;SageIntacct适合复杂业务&#xff0c;用友U8和金蝶K/3面向中大型企业&…

CommandLineRunner 和 ApplicationRunner

CommandLineRunner 和 ApplicationRunner 背景&#xff1a; 项目启动之前&#xff0c;预先加载数据。比如&#xff0c;权限容器、特殊用户数据等。通常我们可以使用监听器、事件来操作。但是&#xff0c;springboot提供了一个简单的方式来实现此类需求&#xff0c;即&#xf…

《Linux从小白到高手》理论篇(九):Linux的资源监控管理

本篇介绍Linux的资源监控管理。 1、CPU 资源管理 进程调度&#xff1a; Linux 采用公平的进程调度算法&#xff0c;确保每个进程都能获得合理的 CPU 时间。调度算法会根据进程的优先级、等待时间等因素来决定哪个进程获得 CPU 使用权。 可以通过调整进程的优先级来影响其获得…

C++继承实例讲解

C类继承的基本概念 base class&#xff0c;基类、父类 derived class&#xff0c;派生类、子类 C中的类可以扩展&#xff0c;创建保留基类特征的新类&#xff0c;这个过程称之为继承。类继承也可以描述为&#xff1a;派生类继承基类的成员&#xff0c;并在其上添加自己的成员…

【hot100-java】【单词搜索】

回溯 回溯可以使用DFS剪枝解决 class Solution {public boolean exist(char[][] board, String word) {char[] wordsword.toCharArray();for(int i0;i<board.length;i){for(int j0;j<board[0].length;j){if(dfs(board,words,i,j,0)) return true;}}return false;}boolean…

关于Elastic Search与MySQL之间的数据同步

目录 前言 思路分析 同步调用 异步通知 监听binlog 选择 实现数据同步 思路 运行项目 声明交换机、队列 1&#xff09;引入依赖 2&#xff09;声明队列交换机名称 3&#xff09;声明队列交换机 发送MQ消息 接收MQ消息 前言 Elastic Search中的酒店数据来自于MyS…

TypeScript 算法手册【插入排序】

文章目录 TypeScript 算法手册 - 插入排序1. 插入排序简介1.1 插入排序定义1.2 插入排序特点 2. 插入排序步骤过程拆解2.1 选择当前元素2.2 寻找插入位置2.3 插入元素 3. 插入排序的优化3.1 二分查找插入排序案例代码和动态图 4. 插入排序的优点5. 插入排序的缺点总结 【 已更新…

48.哀家要长脑子了!

1.376. 摆动序列 - 力扣&#xff08;LeetCode&#xff09; 看问题抓本质 本质&#xff01;&#xff01;识别和追踪数组中元素值的变化趋势。摆动序列是什么&#xff0c;什么是摆动序列&#xff0c;就是差值正负正负的来&#xff0c;最后要求摆动序列的子序列的长度的话&#x…

如何在KEIL的Debug模式下导出数据

我们知道&#xff0c;利用Keil编写程序时&#xff0c;可以实时显示数据的值&#xff0c;如上图所示&#xff0c;实时显示Voltage和fre的值&#xff0c;那如何导出该数据呢&#xff0c;下边进行详细说明。 首先&#xff0c;进入Debug模式&#xff0c;点击调试里边的函数编辑器。…

计算机毕业设计 基于Python的摄影平台交流系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

GPU、AI、CUDA

文章目录 1.千层面层多层 2. CPU与GPU架构差异3.大规模矩阵操作4.专为并行计算设计的库 1.千层面 神经网络的本质是千层面&#xff0c;由一层一层的线性代数方程组成&#xff0c;每个方程都表示一段数据与另一段数据相关的可能性 层 神经网络的每一次层可以看作是一次线性代…