数据结构:线性结构之顺序表、链表篇

news2024/11/15 2:02:57

数据结构:顺序表、链表篇

  • 线性表
  • 一、顺序表
    • (一)顺序表的结构定义
    • (二)顺序表的功能实现
      • 1、初始化
      • 2、销毁
      • 3、扩容
      • 4、插入
      • 5、删除
    • (三)顺序表例题分析
        • 1、删除有序数组中的重复项
        • 2、合并两个有序数组
      • (四)顺序表的弊端
  • 二、链表
    • (一)链表的结构定义
    • (二)链表的功能实现
      • 1、链表的初始化
      • 2、链表的插入
      • 3、链表的删除
      • 4、链表的销毁
    • (三)链表的例题分析
      • 1、移除链表元素
      • 2、反转链表
        • 题目分析
      • 3、链表的中心结点
        • 题目分析
      • 4、合并两个有序链表
        • 题目分析
      • 5、链表的回文结构
        • 题目分析
        • 方法一
        • 方法二
      • 6、相交链表
        • 题目分析
      • 7、环形链表
        • 题目分析
      • 8、环形链表||
        • 题目分析
      • 9、随机链表的复制
        • 题目分析
      • 结束语

线性表

线性表:线性表是具有逻辑结构是连续,物理结构不一定是连续的一类数据结构的集合。链表和顺序表都是线性表

顺序表 : 物理结构连续,逻辑结构连续

链表 : 物理结构不一定连续(动态内存申请的空间可能是连续的,但是一般不会), 逻辑结构连续

一、顺序表

物理地址连续的存储单元依次储存数据结构的线性结构,一般采用数组实现。顺序表分为动态顺序表和静态顺序表,为了防止空间过度浪费,空间不足,我们一般采用动态顺序表。

(一)顺序表的结构定义

typedef int SLdataType;

typedef struct SeqList {
	SLdataType* data;
	int count, size;
}SeqList;

用到typedef, 可以使得我们的顺序表存放数据的类型更加的灵活。

(二)顺序表的功能实现

1、初始化

void initSeqList(SeqList* SL) {
	SL->data = NULL;
	SL->count = SL->size = 0;
	return;
}

2、销毁

void clearSeqList(SeqList* SL) {
	if (SL == NULL) return;
	if (SL->data) free(SL->data);
	SL->data = NULL;
	SL->count = SL->size = 0;
	return;
}

3、扩容

采用 realloc 进行扩容,考虑到原来的容量为 0, 不可单纯的进行乘二

void SLCheckCapacity(SeqList* SL) {
	if (SL->count == SL->size) {
		int n = SL->count == 0 ? 4 : 2 * SL->size;
		SLdataType* temp = (SLdataType*)realloc(SL->data, sizeof(SLdataType) * n);
		if (temp == NULL) {
			perror("realloc fail\n");
			exit(1);
		}
		SL->data = temp;
		SL->size *= n;
	}
	return;
}

4、插入

插入操作分为头插、尾插,和任意位置插入。
插入操作需要整体后移 : 从后面像前面遍历,反之会产生数据的覆盖。

//头插
void insertPushFront(SeqList* SL, SLdataType x) {
	SLCheckCapacity(SL);
	for (int i = SL->count - 1; i >= 0 ; i--) {
		SL->data[i + 1] = SL->data[i];
	}
	SL->data[0] = x;
	SL->count += 1;
	return;
}

//尾插
void insertPushBack(SeqList* SL, SLdataType x) {
	SLCheckCapacity(SL);
	SL->data[SL->count++] = x;
	return; 
}

//任意位置插入
void insert(SeqList* SL, SLdataType x, int pos) {
	if (pos < 0 && pos > SL->count) return;
	SLCheckCapacity(SL);
	for (int i = SL->count - 1; i >= pos; i--) {
		SL->data[i + 1] = SL->data[i];
	}
	SL->data[pos] = x;
	SL->count += 1;
	return;
}

5、删除

删除操作分为头删、尾删,和任意位置删除。
删除操作需要整体前移 : 从前面向后面遍历,反之会产生数据的覆盖。

//头删
void erasePopFront(SeqList* SL) {
	for (int i = 1; i < SL->count; i++) {
		SL->data[i - 1] = SL->data[i];
	}
	SL->count -= 1;
	return;
}

//尾删
void erasePopBack(SeqList* SL) {
	assert(SL);
	assert(SL->count);
	SL->count -= 1;
	return;
}

//任意位置删除
void erase(SeqList* SL, int pos) {
	if (pos < 0 && pos >= SL->count) return;
	for (int i = pos; i < SL->count - 1; i++) {
		SL->data[i] = SL->data[i + 1];
	}
	SL->count -= 1;
	return;
}

(三)顺序表例题分析

1、删除有序数组中的重复项

https://leetcode.cn/problems/remove-duplicates-from-sorted-array/
在这里插入图片描述

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int src = 1, dst = 0;
        while(src < nums.size()){
               if(nums[src] == nums[dst]){
                src += 1;
            }
            else{
                nums[++dst] = nums[src++];
            }
        }
        return dst + 1;
    }
};

题目中我们用到双指针指针删除重复项,其中while 循环的条件设计十分巧妙

2、合并两个有序数组

https://leetcode.cn/problems/merge-sorted-array/

在这里插入图片描述

小结 : 采用两个指针分别指向两个数组的末尾,依次将数据放在数组一。
while 循环可以用 && 也可以用 || 采用两种代码实现

采用 || 的方式实现


class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int l1 = m - 1, l2 = n - 1, l3 = m + n - 1;
        while (l1 >= 0 || l2 >= 0) {
            if (l2 < 0 || (l1 >= 0 && nums1[l1] > nums2[l2]))
                nums1[l3--] = nums1[l1--];
            else
                nums1[l3--] = nums2[l2--];
        }
    }
};

或者采用 && 的方式实现

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int l1 = m - 1, l2 = n - 1, l3 = m + n - 1;
        while (l1 >= 0 && l2 >= 0) {
            if (nums1[l1] > nums2[l2])
                nums1[l3--] = nums1[l1--];
            else
                nums1[l3--] = nums2[l2--];
        }
        while (l2 >= 0) {
            nums1[l3--] = nums2[l2--];
        }
    }
};

(四)顺序表的弊端

1、顺序表的插入删除操作的时间复杂度为O(n)
2、顺序表扩容后任然可能造成空间的浪费,并且顺序表扩容带来性能消耗

二、链表

(一)链表的结构定义

这里也用的typedef 可以使得我们的数据类型更加灵活。

typedef int SLTDataType;

typedef struct SListNode {
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

(二)链表的功能实现

1、链表的初始化

同顺序表相同,链表在初始化时也采取泛型的方式,适应多种数据类型。

SLTNode* BuyNode(SLTDataType x) {
	SLTNode* p = (SLTNode*)malloc(sizeof(SLTNode));
	p->data = x;
	p->next = NULL;
	return p;
}

2、链表的插入

链表的插入分为头插、尾插和任意位置插入。任意位置插入时采用双指针定向移动。

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) {
	SLTNode* node = BuyNode(x);
	if (*pphead == NULL) {
		*pphead = node;
		return;
	}
	SLTNode* p = *pphead;
	while (p->next) p = p->next;
	p->next = node;
	return;
}

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x) {
	SLTNode* node = BuyNode(x);
	if (*pphead == NULL) {
		*pphead = node;
		return;
	}
	node->next = *pphead;
	*pphead = node;
	return;
}

//任意位置之前插入,采用双指针的方式
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
	assert(pos);
	assert(*pphead);
	SLTNode* node = BuyNode(x);
	if (*pphead == pos) {
		node->next = pos;
		*pphead = node;
		return;
	}
	SLTNode* fast = (*pphead)->next, * slow = *pphead;
	while (fast != pos) {
		fast = fast->next;
		slow = slow->next;
	}
	slow->next = node;
	node->next = fast;
	return;
}

//任意位置之后插入方式会大大简便
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
	assert(pos);
	SLTNode* node = BuyNode(x);
	node->next = pos->next;
	pos->next = node;
	return;
}

3、链表的删除

链表的插入分为头删、尾删和任意位置删除。任意位置删除时采用双指针定向移动。

//尾删
void SLTPopBack(SLTNode** pphead) {
	assert(*pphead);
	if (!(*pphead)->next) {
		free(*pphead);
		*pphead = NULL;
		return;
	}
	SLTNode* ptail = *pphead;
	SLTNode* prev = NULL;
	while (ptail->next)
	{
		prev = ptail;
		ptail = ptail->next;
	}
	prev->next = NULL;
	free(ptail);
	ptail = NULL;
	return;
}

//头删
void SLTPopFront(SLTNode** pphead) {
	assert(*pphead);
	SLTNode* node = (*pphead)->next;
	free(*pphead);
	*pphead = node;
	return;
}

//任意位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos) {
	assert(*pphead);
	assert(pos);
	if (pos == *pphead) {
		SLTPopFront(pphead);
	}
	else {
		SLTNode* p = *pphead;
		while (p->next != pos) {
			p = p->next;
		}
		p->next = pos->next;
		free(pos);
		pos = NULL;
	}
	return;
}

4、链表的销毁

存储链表的下一个结点,然后进行 free

void SListDestroy(SLTNode** pphead)
{
	assert(pphead && *pphead);

	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

(三)链表的例题分析

1、移除链表元素

https://leetcode.cn/problems/remove-linked-list-elements/
在这里插入图片描述

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* newhead = NULL, *tail = NULL, *p = head;
        while(p){
            if(p->val != val){
                if(newhead == NULL) {
                    newhead = tail = p;
                }else{
                    tail->next = p;
                    tail = tail->next;
                }
            }
            p = p->next;
        }
        if(tail) tail->next = NULL;
        return newhead;
    }
};

小结: 如果 tail 不是NULL, 要将 tail 置空。

2、反转链表

https://leetcode.cn/problems/reverse-linked-list/
在这里插入图片描述

题目分析

在这里插入图片描述

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL) return NULL;
        ListNode* pre = NULL, * cur = head, * Next = head->next;
        while(cur){
            cur->next = pre;
            pre = cur;
            cur = Next;
            if(!cur) break;
            Next = Next->next;
        }
        return pre;
    }
};

小结:与另外新建一个链表的方式不同,这种算法可以在原来的链表上进行处理就能达到反转链表的效果。

3、链表的中心结点

https://leetcode.cn/problems/middle-of-the-linked-list/description/
在这里插入图片描述

题目分析

采用快慢指针进行分析,快指针每次走两步,慢指针每次走一步,当
fast = NULL 或者 fast -> next = NULL 时,slow指针指向的就是中间位置的指针。

在这里插入图片描述

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* fast, *slow;
        fast = slow = head;
        while(fast && fast->next){
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};

小结: 用快慢指针有很多好处,这道题的中间值就是一个

4、合并两个有序链表

https://leetcode.cn/problems/merge-two-sorted-lists/description/
在这里插入图片描述

题目分析

这道题的思路并不困难,主要是学一种新的头节点创建方式,通过 malloc 申请内存来获得头节点。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* newhead, * tail;
        newhead = tail = (ListNode*)malloc(sizeof(ListNode));
        newhead->next = NULL;
        while(list1 && list2){
            if(list1->val < list2->val){
                tail->next = list1;
                tail = tail ->next;
                list1 = list1->next;
            }else{
                tail->next = list2;
                tail = tail->next;
                list2 = list2->next;
            }
        }
        if(list1) tail->next = list1;
        if(list2) tail->next = list2;
        ListNode* ret = newhead->next;
        free(newhead);
        newhead = NULL;
        return ret;
    }
};

小结:这道题可以有三种方式创建头结点
1、直接开辟变量 Node head, 返回head->next;
2、创建两个指针Node* head, * tail;
3、用 malloc 开辟空间,返回malloc 的下一个结点, 记得要free;

5、链表的回文结构

https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&qru=/ta/2016test/question-ranking
在这里插入图片描述

题目分析

这道题有两个思路:
方法一 : 采用数组,将链表的结点数据依次放入数组之中,然后创建两个指针向中间移动,依次比较。但是创建数组的时间复杂度为O(n),不可以通过。
方法二 : 中间结点后面的结点进行反转,切记反转链表反转的是指针的方向,数据位置没有变。然后同一。

方法一
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        int arr[1000], i = 0;
        ListNode* p = A;
        while (p) {
            arr[i++] = p->val;
            p = p->next;
        }
        int left = 0, right = i - 1;
        while(left <= right) {
            if(arr[left++] != arr[right--]) return false;
        }
        return true;
    }
};
方法二

在这里插入图片描述

这种在原链表上进行修改的反转操作有妙用

6、相交链表

https://leetcode.cn/problems/intersection-of-two-linked-lists/description/
在这里插入图片描述

题目分析

将长的链表先截成和短的链表的长度,因为是后面部分相交,挨个比较直到两个指针的地址相等为相交结点

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* p = headA;
        int lenA = 0, lenB = 0;
        while(p){
            p = p->next;
            lenA += 1;
        }
        p = headB;
        while(p){
            p = p->next;
            lenB += 1;
        }
        int length = abs(lenA - lenB);
        ListNode* longlist = headA;
        ListNode* shortlist = headB;
        if(lenA < lenB){
            longlist = headB;
            shortlist = headA;
        }
        while(length--){
            longlist = longlist->next;
        }
        while(longlist != shortlist){
            longlist = longlist->next;
            shortlist = shortlist->next;
        }
        return shortlist;
    }
};

7、环形链表

https://leetcode.cn/problems/linked-list-cycle/
在这里插入图片描述

题目分析

用快慢指针,如果有环,那么快指针就会追上慢指针。

问题一 : 快指针为什么一定会追上慢指针
因为每次追逐两个指针的距离都会减一

问题二:快指针每次可以走2, 3, 4 ~步吗
下面我们以快指针每次走三步为例,结果是一定相遇,其他推理结论相同
在这里插入图片描述

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* fast ,* slow;
        fast = slow = head;
        while(fast && fast->next){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow) return true;
        }
        return false;
    }
};

小结:上面的文章里面,我们用快慢指针找中间结点,现在又多了一种新的用法,用来判断是否有环。

8、环形链表||

https://leetcode.cn/problems/linked-list-cycle-ii/description/
在这里插入图片描述

题目分析

在相遇之后,相遇点和头结点到环的起始点的距离相等,用两个指针从这两个位置同时出发,直到相遇。
在这里插入图片描述

9、随机链表的复制

https://leetcode.cn/problems/copy-list-with-random-pointer/description/在这里插入图片描述

题目分析

如下图
在这里插入图片描述
步骤一 : 添加复制的结点
在这里插入图片描述
步骤二: 给random 赋值
步骤三: 断开原来的链表和拷贝链表
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    Node* BuyNode(int val) {
        Node* p = (Node*)malloc(sizeof(Node));
        p->val = val;
        p->next = p->random = NULL;
        return p;
    }

    void AddNode(Node* head) {
        Node *pcur = head, *next;
        while (pcur) {
            next = pcur->next;
            Node* node = BuyNode(pcur->val);
            pcur->next = node;
            node->next = next;
            pcur = next;
        }
        return;
    }

    Node* SetRandom(Node* head) {
        Node* pcur = head;
        while (pcur) {
            Node* temp = pcur->next;
            if (pcur->random) {
                temp->random = pcur->random->next;
            }
            pcur = pcur->next->next;
        }
        return head;
    }

    Node* getNewLinkList(Node* head) {
        Node *newHead, *tail;
        Node* pcur = head;
        newHead = tail = head->next;
        while (pcur) {
            pcur->next = tail->next;
            if (tail->next) {
                tail->next = pcur->next->next;
                tail = tail->next;
            }
            pcur = pcur->next;
        }
        // tail->next = NULL; // 确保新链表的尾部正确
        return newHead;
    }

    Node* copyRandomList(Node* head) {
        if (head == NULL)
            return NULL;
        AddNode(head);
        head = SetRandom(head);
        head = getNewLinkList(head);
        return head;
    }
};

小结:无需多言,值得反复学习

结束语

好了,小编也要睡觉了,下一篇小编会带来双向链表的博文。如果感兴趣的话记得要给博主一个关注哦,不然就再也找不到啦,小伙伴们周末快乐!
在这里插入图片描述

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

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

相关文章

【Hot100】LeetCode—73. 矩阵置零

目录 1- 思路开辟额外两个一维数组 2- 实现⭐53. 最大子数组和——题解思路 3- ACM 实现 原题连接&#xff1a;73. 矩阵置零 1- 思路 开辟额外两个一维数组 1- 利用额外的两个一维数组 boolean 数组空间&#xff0c;遇到 0 则将当前位置的元素设置为 true 一维 row 数组&…

如何判断一个dll/exe是32位还是64位

通过记事本判断&#xff08;可判断C或者C#&#xff09; 64位、将dll用记事本打开&#xff0c;可以看到一堆乱码&#xff0c;但是找到乱码行的第一个PE&#xff0c;如果后面是d?则为64位 32位、将dll用记事本打开&#xff0c;可以看到一堆乱码&#xff0c;但是找到乱码行的第…

最好用的Linux发行版---WSL

使用debian开发半年&#xff0c;那个号称稳定的操作系统&#xff0c;ubuntu也是基于它的testing版本开发的&#xff0c;在一次设置testing更新后英伟达驱动掉了、引导区无法启动、bios损坏&#xff0c;现在老实了&#xff0c;换回了Window&#xff0c;并且激进的选择了win11&am…

c语言中的宏函数及c++的内联函数及auto及NULL

c的内联函数 使用内联函数可以减少函数栈帧的开销。 Swap(a, b); 00A516C8 mov eax,dword ptr [a] 00A516CB mov dword ptr [ebp-20h],eax 00A516CE mov ecx,dword ptr [b] 00A516D1 mov dword ptr [a],ecx 00A516D4 mov …

Linux登录后自动健康检查:一键掌握系统状态

Linux登录后自动健康检查&#xff1a;一键掌握系统状态 最近开始公众号文章也开始同步更新了&#xff0c;对Java、大数据、人工智能、开发运维相关技术分享&#xff0c;文章对您有用的话&#xff0c;辛苦您也关注下公众号&#xff0c;感谢&#xff01; 引言 当我们登录到某些服…

rt-thread 打开flashdb若干问题

1、打开FAL和SFUD功能 2、打开FLASHDB时&#xff0c;想用utest测试工程&#xff0c;结果报错&#xff0c;缺少mkdir函数&#xff1a; 解决办法&#xff1a;打开DFS RT-Thread Components → DFS: device virtual file system 重新编译

大数据-87 Spark 集群 案例学习 Spark Scala 案例 手写计算圆周率、计算共同好友

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

windows重装系统后无法安装软件,Windows软件无法打开,缺少dll,缺少c++运行库

如果你使用Directx&#xff0c;要么识别不出来&#xff0c;要么装上之后更新会发现重复安装了很多运行库 装一个visual stiudio&#xff1a;<Visual Studio 2022 IDE - 适用于软件开发人员的编程工具> 选择使用C游戏开发、.Net桌面开发 然后安装即可

学习Java的日子 Day71 手写一个SpringMVC的框架(一)

手写一个SpringMVC的框架 1.理解为什么要写这样一个框架 SpringMVC 实际上跟Servlet是一样&#xff0c;都是 Controller的一个解决方案&#xff0c;也就是说我们手写这个框架的目的就是为了替换原来的 Servlet 注意&#xff1a; spring不是框架&#xff0c;springMVC才是框架&…

一文入门mysql 数据库

一、数据库概述 什么是数据库 数据库是一个用于存储和管理数据的仓库。数据按照特定的格式存储&#xff0c;可以对数据库中的数据进行增加、修改、删除和查询操作。数据库的本质是一个文件系统&#xff0c;按照一定的逻辑结构组织数据&#xff0c;以方便高效地访问和维护。 什…

使用Python创建LNK文件选择器并导出配置文件

在这篇博客中&#xff0c;我将介绍如何使用Python的wxPython库开发一个GUI应用程序&#xff0c;该应用程序可以选择文件夹中的.lnk&#xff08;快捷方式&#xff09;文件&#xff0c;并将选中的文件导出为特定格式的buttons.ini配置文件。这个工具非常适合用来快速生成配置文件…

使用Java调用Apache commons-text求解字符串相似性实战

目录 前言 一、字符串距离的几种计算方法 1、Levenshtein 距离 2、Overlap Coefficient计算 3、Q-gram Matching 4、余弦相似性计算 二、基于余弦相似性的基地名称对比 1、加载百科中的基地信息列表 2、设置忽略词列表 3、将数据库地名和Excel进行对比 三、总结 前言…

c语言音频.wav读写示例

1 .wav格式说明 一. RIFF 概念 在 Windows 环境下&#xff0c;大部分的多媒体文件都依循着一种结构来存放信息&#xff0c;这种结构称为"资源互换文件格式"(Resources lnterchange File Format)&#xff0c;简称 RIFF。例如声音的 WAV 文件、视频的 AV1 文件等等均…

EmguCV学习笔记 VB.Net 2.4 Image类

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV学习笔记目录 Vb.net EmguCV学习笔记目录 C# 笔者的博客网址&#xff1a;VB.Net-CSDN博客 教程相关说明以及如何获得pdf教…

【图解秒杀系列】秒杀技术点——秒杀按钮点亮、削峰

【图解秒杀系列】秒杀技术点——秒杀按钮点亮、削峰 秒杀按钮点亮涉及的问题以及解决办法处理流程 削峰答题 & 验证码具体流程 排队 秒杀按钮点亮 在秒杀场景中&#xff0c;秒杀商品页面是需要处理按钮点亮的逻辑的。在秒杀未开始前&#xff0c;按钮置灰&#xff0c;不可点…

POSIX信号量semaphore实现线程同步

POSIX标准定义了信号量接口如下&#xff0c;常常用于线程间同步。 #include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value); int sem_destroy(sem_t *sem); int sem_post(sem_t *sem); int sem_wait(sem_t *sem); sem_init()在sem指向的地址…

微信小程序反编译工具

目录 介绍 工程结构还原 微信开发者工具运行 如何查看当前运行版本? 开启小程序F12 重新打包运行 效果示例 安装 用法 参数说明 获取微信小程序AppID 文件夹名即为AppID 下载地址 介绍 纯Golang实现,一个用于自动化反编译微信小程序的工具,小程序安全利器, 自…

【杂谈】-8个常用的Python图像操作库

8个常用的Python图像操作库 文章目录 8个常用的Python图像操作库1、OpenCV2、Pillow&#xff08;PIL&#xff09;3、Scikit Image4、Numpy5、SciPy6、Mahotas7、SimpleITK8、Matplotlib 在当今世界&#xff0c;数据在每个行业垂直领域中都发挥着至关重要的作用。图像可以是提取…

Redis 操作的原子性及其保证机制

Redis 操作的原子性及其保证机制 1、单命令的原子性2、事务的原子性3、并发操作的考虑4、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Redis 的操作之所以是原子性的&#xff0c;主要得益于其单线程的执行模型。这种模型确保了每个命…

5.9.8 最优化控制初探——PID参数优化

总目录&#xff1a;http://t.csdnimg.cn/YDe8m 5.9.8 最优化控制初探——PID参数优化 之前在“A_2_PID控制转速例程”例程中&#xff0c;PID参数是手动调节的。然而在已经获得系统完整数学模型的情况下&#xff0c;我们可以使用效率更高的方法&#xff0c;即最优化控制。先来看…