当前位置: 首页 > news >正文

(10)数据结构--排序

目录

一 基本概念

二 内部排序

2.1 插入排序

2.1.1 直接插入排序

2.1.2 优化——折半插⼊排序

2.1.3 希尔排序

2.2 交换排序

2.2.1 冒泡排序

2.2.2 快速排序

2.3 选择排序

2.3.1 简单选择排序

2.3.2 堆排序

2.3.3 堆的插入与删除

2.4 归并排序

2.5 基数排序

三 外部排序

3.1 外部排序及优化

3.2 败者树

3.3 置换-选择排序

3.4 最佳归并树


一 基本概念

排序(Sort),就是重新排列表中的元素,使表中的元素满足按关键字有序的过程。

需要关注指标:时间复杂度,空间复杂度,稳定性。

什么是稳定性?例如下图,表中有两个“3”,若经过排序算法,两个“3”的相对位置没有变,那么这个排序算法就是稳定的,否则就是不稳定的。

算法排序分类

二 内部排序

2.1 插入排序

2.1.1 直接插入排序

算法思想:每次将⼀个待排序的记录按其关键字⼤⼩插⼊到前⾯已排好序的⼦序列中,直到全部记录插⼊完成。

下图进行插入排序

1.从第二个元素入手,38比49小,49位置加1,38插入49位置前

2.第三个元素65,比前面已排好序都大,不用变动

3.第四个元素97,比前面已排好序都大,不用变动

4.第五个元素76,往前检查97大,97位置加1,再往前检查65,比65大插入65后边位置

5.剩余元素同理

带哨兵

//直接插入排序(带哨兵)
void InsertSort(int A[],int n){int i,j;for(i=2;i<=n;i++){    //依次将A[2]至A[n]插入到前面已排序序列if(A[i]<[i-1]){    //若A[i]关键码小于其前驱,将A[i]插入有序表A[0]=A[i];    //复制为哨兵,A[0]不存放元素for(j=i-1;A[0]<A[i];--j)  //从后往前查找待插入位置A[j+1]=A[j];    //向后挪位A[j+1]=A[0];    //复制到插入位置}        }
}

空间复杂度:O(1)
最好时间复杂度(全部有序):O(n)
最坏时间复杂度(全部逆序):
平均时间复杂度:
算法稳定性:稳定

2.1.2 优化——折半插⼊排序

思路:先⽤折半查找找到应该插⼊的位置,再移动元素

如下图排序当前位置

1.用已排序序列进行折半查找,mid为50,小于55,low=mid+1;mid=(low+high)/2

2.mid为70 大于55, high=mid-1;mid=(low+high)/2

2.mid为60 大于55, high=mid-1;当 low>high 时折半查找停⽌,应将 [low, i-1] 内的元素全部右移,并将 A[0] 复制到 low 所指位置

当 low>high 时折半查找停⽌,应将 [low, i-1] 内的元素全部右移,并将 A[0] 复制到 low 所指位置
当 A[mid]==A[0] 时,为了保证算法的“稳定性”,应继续在 mid 所指位置右边寻找插⼊位置

//折半插入排序
void InsertSort(int A[],int n)int i,j,low,high,mid;for(i=2;i<=n; i++){     //依次将A[2]~A[n]插入前面的已排序序列A[0]=A[il;    //将A[i]暂存到A[0]low=1;high=i-1;     //设置折半查找的范围while(low<=high){    //折半查找(默认递增有序)mid=(low+high)/2; //取中间点if(A[mid]>A[0])high=mid-1;//查找左半子表else low=mid+1;//查找右半子表}for(j=i-1;j>=high+l;--j)A[j+1]=A[j];    //统一后移元素,空出插入位置A[high+l]=A[0l;    //插入操作}
}

注:对链表进⾏插⼊排序,移动元素的次数变少了,但是关键字对⽐的次数依然是数量级,
整体来看时间复杂度依然是

2.1.3 希尔排序

希尔排序:先追求表中元素部分有序,再逐渐逼近全局有序       

希尔排序:先将待排序表分割成若⼲形如 L[i, i + d, i + 2d,…, i + kd] 的“特殊”⼦表,对各个⼦表分别进⾏直接插⼊排序。缩⼩增量d,重复上述过程,直到d=1为⽌。

第⼀趟:d1=n/2=4  相距为4的元素属于同一个子表,然后对子表直接进行插入排序

第⼆趟:d2=d1/2=2,相距为2的元素属于同一个子表,然后对子表直接进行插入排序

第三趟:d3=d2/2=1,也就是全表,然后对全表进行插入排序

全部趟数状态如下:

d1一般是n/2 ,也可以自定义取值

 代码可用上述示例走一遍,即可理解。

// 希尔排序
void ShellSort(int A[],int n){int d, i, j;//A[0]只是存储单元,不是哨兵,当i<=0是,插入位置已到for(d=n/2;d>=1;d=d/2)    //布长变化for(i=d+1;i<=n;++i)if(A[i]<A[i-d]){   //需将A[i]插入有序增量子表A[0]=A[i];    //暂存在A[0]for(j=i-d; j>0&&A[0]<A[j]; j-=d)    A[j+d]=A[j];    //记录后移,查找插入的位置A[j+d]=A[0];    // 插入}//if
}

空间复杂度:O(1)
时间复杂度:和增量序列 d1, d2, d3… 的选择有关,⽬前⽆法⽤数学⼿段证明确切的时间复杂度
最坏时间复杂度为,当n在某个范围内时,可达

稳定性:不稳定

例如下边49位置前后颠倒

适⽤性:仅适⽤于顺序表,不适⽤于链表

2.2 交换排序

2.2.1 冒泡排序

从后往前(或从前往后)两两⽐较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换它们,直到序列⽐较完。称这样过程为“⼀趟”冒泡排序

第1趟:从后依次两个比较,将最小的移动到最前(也可以从前往后将最大的移动到最后)

交换76与13

交换97与13

交换65与13

交换38与13

交换49与13

从后往前(或从前往后)两两⽐较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换它们,直到序列⽐较完。称这样过程为“⼀趟”冒泡排序。

第2趟处理

交换76与27

交换97与27

交换65与27

交换38与27

交换49与27

第一个元素已经确认,不用再比较

后边趟数同理

若某⼀趟排序没有发⽣“交换”,说明此时已经整体有序。

//交换
void swap(int &a, int &b){int temp = a;a = b;b = temp;
}// 冒泡排序
void VubbleSort(int A[],int n){for(int i=0;i<n-1;i++){}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;    //本趟遍历后没有发生交换,说明表已经有序}
}

空间复杂度:O(1)

时间复杂度:

        最好情况(有序)⽐较次数=n-1;交换次数=0,最好时间复杂度=O(n)

        最坏情况(逆序)⽐较次数==交换次数,最坏时间复杂度

         平均时间复杂度

稳定性:稳定  也适用链表。

2.2.2 快速排序

基于“交换”的排序:根据序列中两个元素关键字的⽐较结果来对换这两个记录在序列中的位置


算法思想:在待排序表L[1…n]中任取⼀个元素pivot作为枢轴(或基准,通常取⾸元素),通过⼀趟排序将待排序表划分为独⽴的两部分L[1…k-1]和L[k+1…n],使得L[1…k-1]中的所有元素⼩于pivot,L[k+1…n]中的所有元素⼤于等于pivot,则pivot放在了其最终位置L(k)上,这个过程称为⼀次“划分”。然后分别递归地对两个⼦表重复上述过程,直⾄每部分内只有⼀个元素或空为⽌,即所有元素放在了其最终位置上。

示例:以low作为基准(枢轴),low和high往中间扫描,更⼩的元素都交换到左边,更⼤的元素都交换到右边。

1.首先high的位置>=49的,high对应的元素不需要移动,high--;

2.此时high指向27,小于49,将high指向的元素27移动到low指向的位置。此时high指向的位置空出来了,low++;

3.low指向38 小于49,low++;

4.low指向65大于49,放到high指向的位置,此时low空了,high--;

5.high指向13小于49,放到low指向的位置,此时high空了,low--;

6.low指向79大于49,放到high指向的位置,此时low空了,high--;

7.high>=49, high--, 当low=high时,将49插入当前位置,此时49左边都比49小,右边是大于等于49

8. 基准元素49将表拆分两个子表,两个子表同理

     左子表27作为基准元素

        右子表76作为基准元素,做完后有划成两部分。同理

最终

// 用第一个元素将数组A[]划分为两个部分
int Partition(int A[], int low, int high){int pivot = A[low];    //第一个元素作为基准while(low<high){    //用low,high搜索基准的最终位置while(low<high && A[high]>=pivot) --high;A[low] = A[high];  //比基准小的元素移动到左端while(low<high && A[low]<=pivot) ++low;A[high] = A[low];    //比基准大的元素移动到右端}A[low] = pivot;    //基准元素存放到最终位置return low;    //返回存放基准的最终位置
} // 对A[]数组的low到high进行快速排序
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);    //划分右子表}
}

通过下图理解代码,每一层 QuickSort确定基准所在位置,每个基准都将表拆成左右两个子表,思考是否可用二叉树表示。

把n个元素组织成⼆叉树,⼆叉树的层数就是递归调⽤的层数,n个结点的⼆叉树
最⼩⾼度 =,最⼤⾼度 = n

空间复杂度=O(递归层数)

最好空间复杂度=
最坏空间复杂度=O(n)

时间复杂度=O(n*递归层数)

最好时间复杂度=
最坏时间复杂度=

稳定性:不稳定

2.3 选择排序

2.3.1 简单选择排序

选择排序:每⼀趟在待排序元素中选取关键字最⼩(或最⼤)的元素加⼊有序⼦序列

1.找到最小的是13,与第一个位置交换

2.找到第2小的,与第2进行交换

3.依次类推,最终排序

n个元素的简单选择排序需要 n-1 趟处理

// 交换a和b的值  
void swap(int &a, int &b){int temp = a;a = b;b = temp;
}// 对A[]数组共n个元素进行选择排序
void SelectSort(int A[], int n){for(int i=0; i<n-1; i++){          	//一共进行n-1趟,i指向待排序序列中第一个元素int min = i;        //记录最小元素位置for(int j=i+1; j<n; j++){		//在A[i...n-1]中选择最小的元素if(A[j]<A[min])    min = j;    //更新最小元素位置}if(min!=i)    //封装的swap()函数共移动元素3次                 swap(A[i], A[min]);}
}

空间复杂度:O(1)

时间复杂度=

稳定性:不稳定

适⽤性:既可以⽤于顺序表,也可⽤于链表

2.3.2 堆排序

若n个关键字序列L[1…n] 满⾜下⾯某⼀条性质,则称为堆(Heap):
① 若满⾜:L(i)≥L(2i)且L(i)≥L(2i+1) (1 ≤ i ≤n/2 )—— ⼤根堆(⼤顶堆)
② 若满⾜:L(i)≤L(2i)且L(i)≤L(2i+1) (1 ≤ i ≤n/2 )—— ⼩根堆(⼩顶堆)

为什么要用2i,2i+1,可用思考下,二叉树i的左孩子,有孩子等相关信息

⼤根堆:完全⼆叉树中,根≥左、右

⼩根堆:完全⼆叉树中,根≤左、右

 初始序列建立,大根堆。思路:把所有⾮终端结点都检查⼀遍,是否满⾜⼤根堆的要求,如果不满⾜,则进⾏调整。在顺序存储的完全⼆叉树中,⾮终端结点编号 i≤⌊n/2⌋。下面例子中i≤4

1.先处理i=4,不满足大根堆,找到左孩子2i,右孩子2i+1,找到最大的与当前结点互换。

2.处理i=3,不满足大根堆,找到左孩子2i,右孩子2i+1,找到最大的与当前结点互换。

3.处理i=2,不满足大根堆,找到左孩子2i,右孩子2i+1,找到最大的与当前结点互换。

4.处理i=1,不满足大根堆,找到左孩子2i,右孩子2i+1,找到最大的与当前结点互换。

5.更⼩的元素“下坠”,可能导致下⼀层的⼦树不符合⼤根堆的要求,例如53,若元素互换破坏了下⼀级的堆,则采⽤相同的⽅法继续往下调整(⼩元素不断“下坠”)

// 对初始序列建立大根堆
void BuildMaxHeap(int A[], int len){for(int i=len/2; i>0; i--) 		//从后往前调整所有非终端结点HeadAdjust(A, i, len);
}// 将以k为根的子树调整为大根堆
void HeadAdjust(int A[], int k, int len){A[0] = A[k];for(int i=2*k; i<=len; i*=2){	//沿k较大的子结点向下调整//这个if就是在选左右孩子里面较大的孩子,因为一会交换也是把大的孩子放上去而不是把小的孩子放上去,所以如果右孩子更大就选右孩子了if(i<len && A[i]<A[i+1])	i++;    //去key较大的子结点下标if(A[0] >= A[i])break;    //筛选结束else{A[k] = A[i];			//将A[i]调整至双亲结点上k=i;					//修改k值,以便继续向下筛选}}A[k] = A[0];    //被筛选结点的值放入最终位置
}

 基于⼤根堆进⾏排序,

堆排序:每⼀趟将堆顶元素加⼊有序⼦序列(与待排序序列中的最后⼀个元素交换)

1. 87为堆顶,与最后一个元素09交换位置

2. 此时09不符合大根堆,对09做下坠操作

3. 78为堆顶,与最后一个元素53交换位置

4. 此时53不符合大根堆,对53做下坠操作

5.依次类推,n-1趟后最终排序

// 交换a和b的值
void swap(int &a, int &b){int temp = a;a = b;b = temp;
}// 对长为len的数组A[]进行堆排序
void HeapSort(int A[], int len){BuildMaxHeap(A, len);         	//初始建立大根堆for(int i=len; i>1; i--){      	//n-1趟的交换和建堆过程swap(A[i], A[1]);//交换堆顶和待排序序列的最后一个元素交换HeadAdjust(A,1,i-1);//调整不包含已经排过序的数的大根堆}
}

 空间复杂度:O(1)

根节点最多“下坠” h-1 层,每下坠⼀层

⽽每“下坠”⼀层,最多只需对⽐关键字2次,因此每⼀趟排序复杂度不超过  O(h) =

共n-1 趟,总的时间复杂度 =  

稳定性:堆排序是不稳定的

2.3.3 堆的插入与删除

在小根堆插入13。

对于根堆,新元素放到表尾,与⽗节点对⽐,若新元素⽐⽗节点更⼩,则将⼆者互换。新元素就这样⼀路“上升”,直到⽆法继续上升为⽌

1,在堆中删除13

被删除的元素⽤堆底元素替代,然后让该元素不断“下坠”,直到⽆法下坠为⽌

2.4 归并排序

归并:把两个或多个已经有序的序列合并成⼀个

对⽐ i、j 所指元素,选择更⼩的⼀个放⼊ k 所指位置,

比较容易理解,直接给出最终结果。

上述为2路归并,下面为“4路”归并

对⽐ p1、p2、p3、p4所指元素,选择更⼩的⼀个放⼊ k 所指位置

结论:m路归并,每选出⼀个元素需要对⽐关键字 m-1 次

在内部排序中⼀般采⽤2路归并,核⼼操作:把数组内的两个有序序列归并为⼀个

代码实现,low指向第1个有序序列开头,mid指向第1个有序序列最后一个元素,high指向指向第2个有序序列最后一个元素。这样可用用low,mid,high区分两个序列的范围。

// 辅助数组B
int *B=(int *)malloc(n*sizeof(int));
// 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所有元素复制到Bfor(i=low, j=mid+1, k=i; i<=mid && j<= high; k++){if(B[i]<=B[j])A[k]=B[i++];    //较小值复制到A中elseA[k]=B[j++];}//for,此时已经合并完一个序列,下面两个while循环是将剩余元素合并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);     //归并}
}

 

 

2路归并的“归并树”——形态上就是⼀棵倒⽴的⼆叉树

空间复杂度= O(n),来⾃于辅助数组B

每趟归并时间复杂度为O(n) ,则算法时间复杂度为

稳定性:稳定的

2.5 基数排序

要求:得到按关键字“递减”的有序序列。

第⼀趟:以“个位”进⾏“分配”

第⼀趟“分配”结束

第⼀趟“收集”结束:得到按“个位”递减排序的序列

第⼀趟“收集”结束:得到按“个位”递减排序的序列

第⼆趟:以“⼗位”进⾏“分配”

第⼆趟“分配”结束,因为基于第一趟,“个位”越⼤的越先⼊队。。

第⼆趟“收集”:

第⼆趟“收集”结束:得到按“⼗位”递减排序的序列,“⼗位”相同的按“个位”递减排序

第三趟:以“百位”进⾏“分配”

因为基于第二趟,“⼗位”越⼤的越先⼊队

第三趟“收集”

第三趟按“百位”分配、收集:得到⼀个按“百位”递减排列的序列,若“百位”相同则按“⼗位”递减排列,若“⼗位”还相同则按“个位”递减排列

假设⻓度为n的线性表中每个结点aj的关键字由d元组 组成,其中,0≤ ≤ r - 1,r 称为“基数
最⾼位关键字(最主位关键字)例如438中的百位
最低位关键字(最次位关键字)例如438中的各位

基数排序得到递减序列的过程如下,
初始化: 设置 r 个空队列
按照各个 关键字位 权重递增的次序(个、⼗、百),对 d 个关键字位分别做“分配”和“收集”
分配:顺序扫描各个元素,若当前处理的关键字位=x,则将元素插⼊ Qx 队尾
收集:把 各个队列中的结点依次出队并链接

递增,是先收集值更小的,再收集值更大的,与上述收集相反。

需要 r 个辅助队列,空间复杂度 = O(r)

⼀趟分配O(n),⼀趟收集O(r),总共 d 趟分配、收集,总的时间复杂度=O(d(n+r))

稳定性:稳定的

基数排序的应用:某学校有 10000 学⽣,将学⽣信息按年龄递减排序

基数排序,时间复杂度 = O(d(n+r))≈ O(30000)

若采⽤的排序,≈
若采⽤的排序,≈O(140000)

三 外部排序

3.1 外部排序及优化

操作系统以“块”为单位对磁盘存储空间进⾏管理,如:每块⼤⼩ 1KB各个磁盘块内存放着各种各样的数据

磁盘的读/写以“块”为单位数据,读⼊内存后才能被修改,修改完了还要写回磁盘

外部排序:数据元素太多,⽆法⼀次全部读⼊内存进⾏排序

使⽤“归并排序”的⽅法,最少只需在内存中分配3块⼤⼩的缓冲区即可对任意⼀个⼤⽂件进⾏排序

“归并排序”要求各个⼦序列有序,每次读⼊两个块的内容,进⾏内部排序后写回磁盘

对输入缓冲区1,输入缓冲区2进行排序,再分别写入磁盘块中。

⼀个有序的“归并段”

构造初始归并段:需要16次“读”和16次“写”

初始归并段进行归并排序,将归并段1,归并段2最小的放入缓冲区,再进行归并操作。

有缓冲区空了,归并段1补下来一块。

归并为⼀个更⻓的有序序列

第二趟归并结果

经过3趟归并,整体有序

外部排序时间开销=读写外存的时间+内部排序所需时间+内部归并所需时间

 优化:多路归并,4路归并。

对 r 个初始归并段,做k路归并,则归并树可⽤ k 叉树表示,若树⾼为h,则归并趟数 = h-1 =      k越⼤,r越⼩,归并趟数越少,读写磁盘次数越少

结论:若能增加初始归并段的⻓度,则可减少初始归并段数量r

3.2 败者树

归并趟数S = ,归并路数k增加,归并趟数S减⼩,读写磁盘总次数减少

使⽤k路平衡归并策略,选出⼀个最⼩元素需要对⽐关键字 (k-1)次,导致内部归并所需时间增加

可⽤“败者树”进⾏优化 !

败者树——可视为⼀棵完全⼆叉树(多了⼀个头头)。k个叶结点分别是当前参加⽐较的元素,⾮叶⼦结点⽤来记忆左右⼦树中的“失败者”,⽽让胜者往上继续进⾏⽐较,⼀直到根结点

败者树在多路平衡归并中的应⽤

每个叶⼦结点对应⼀个归并段

根节点记录冠军🏆来⾃哪个归并段,分⽀结点记录失败败者来⾃哪个归并段。

最小的元素来自归并段3,也就是数字1,接下来用归并段下一个元素替代1的位置。

①对⽐3、4号归并段⾸元素,3号段胜出。②对⽐2、3号归并段⾸元素,3号段胜出③对⽐3、5号归并段⾸元素,5号段胜出

对于 k 路归并,第⼀次构造败者树需要对⽐关键字 k-1 次

有了败者树,选出最⼩元素,只需对⽐关键字

败者树解决的问题:使⽤多路平衡归并可减少归并趟数,但是⽤⽼⼟⽅法从 k 个归并段选出⼀个最
⼩/最⼤元素需要对⽐关键字 k-1 次,构造败者树可以使关键字对⽐次数减少到

3.3 置换-选择排序

1注:假设⽤于内部排序的内存⼯作区只能容纳3个记录

把最⼩的元素“置换”出去,MINIMAX= 4。 然后再填入新的元素

把最⼩的元素“置换”出去,MINIMAX= 6。 然后再填入新的元素

一直读到10,此时MINIMAX= 13,不可能放到归并段1的末尾,10先锁定

一直到内存⼯作区WA锁定完,若WA内的关键字都⽐ MINIMAX更⼩,则该归并段在此截⽌

然后继续归并段2

最终结果,得到3个初始归并段

使⽤置换-选择排序,可以让每个初始归并段的⻓度超越内存⼯作区⼤⼩的限制

3.4 最佳归并树

5个初始归并段,所占块数各不相同,进⾏⼆路归并

 这棵归并树的带权路径⻓度WPL = 2*1 + (5+1+6+2) * 3 = 44= 读磁盘的次数 = 写磁盘的次数

重要结论:归并过程中的磁盘I/O次数 = 归并树的WPL * 2

要让磁盘I/O次数最少,就要使归并树WPL最⼩——哈夫曼树


最佳归并树 WPLmin = (1+2)*4 + 2*3+5*2 + 6*1= 34

读磁盘次数=写磁盘次数=34次;总的磁盘I/O次数 = 68

下面是3路归并的最佳归并树

WPLmin = (2+3+6)*3 + (9+12+17+24+18)*2 + 30*1= 223
归并过程中 磁盘I/O总次数=446次

注意:对于k叉归并,若初始归并段的数量⽆法构成严格的 k 叉归并树,
则需要补充⼏个⻓度为 0 的“虚段”,再进⾏ k 叉哈夫曼树的构造。

例如

http://www.dtcms.com/a/300969.html

相关文章:

  • 设计模式(八)结构型:桥接模式详解
  • k8s的权限
  • Python队列算法:从基础到高并发系统的核心引擎
  • Cline与Cursor深度实战指南:AI编程助手的革命性应用
  • 【Canvas与标牌】优质资产六角星标牌
  • Java面试全方位解析:从基础到AI的技术交锋
  • 力扣刷题(第一百天)
  • 【多模态】天池AFAC赛道四-智能体赋能的金融多模态报告自动化生成part1-数据获取
  • Linux之shell脚本篇(三)
  • 从0开始学linux韦东山教程Linux驱动入门实验班(6)
  • Linux Shell 命令
  • LabVIEW人脸识别
  • k8s pod生命周期、初始化容器、钩子函数、容器探测、重启策略
  • Vue基础(25)_组件与Vue的内置关系(原型链)
  • ESP32-S3学习笔记<7>:GP Timer的应用
  • 力扣热题100----------41.缺少的第一个正数
  • JavaScript单线程实现异步
  • [ The Missing Semester of Your CS Education ] 学习笔记 shell篇
  • 浅谈如何解决多组件系统相互依赖、调用导致接口复杂问题
  • 深入理解Java内存与运行时机制:从对象内存布局到指针压缩
  • 命令行和neovim的git操作软件-lazygit
  • 探索 Vim:Linux 下的高效文本编辑利器
  • Unity Catalog与Apache Iceberg如何重塑Data+AI时代的企业数据架构
  • Windows 11 Qt 5.15.x 源码编译,支持C++20
  • 字节跳动Coze Studio开源了!架构解析
  • 01人工智能中优雅草商业实战项目视频字幕翻译以及声音转译之底层处理逻辑阐述-卓伊凡|莉莉
  • go mod教程、go module
  • docker 自定义网桥作用
  • JavaScript手录07-数组
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-38,(知识点:晶体管放大电路频率特性,下限截止频率)