八大排序汇总

2023-11-13

1.插入排序

  • 插入排序其实我们都并不陌生

  • 我们玩扑克牌比如斗地主的时候,整理牌的时候用的就是插入排序

  • 现在给我们一个无序的数组,如何实现插入排序呢

  • 首先一个元素肯定是有序的,我们就认为数组的第一个元素有序

  • 然后数组的第二个元素数组往前面有序的部分插入元素,让前面有序的部分仍保持有序

  • 如下图,先把5插到3后面,3,5就有序了

  • 然后再把第三个元素1插到3前面,1,3,5也有序了

  • 如此反复,直到把最后一个元素插入,整个数组就有序了
    请添加图片描述

  • 思想并不难理解,我们写代码的时候就可以定义end指向数组有序部分的末端,tmp变量存储要插入的值

  • 将tmp与a[end]比较,如果tmp<a[end],说明tmp还要继续往前走

  • 就将a[end+1]=a[end]

  • 直到tmp>=a[end],说明tmp就改在此位置之后插入了

  • 于是我们就让a[end+1]=tmp,完成插入

  • 插入分为两种情况,一是在中途tmp>=a[end],提前结束循环

  • 二是end到头仍未发现比tmp大的值,此时end=-1,我们就将tmp插入到a[0],也满足a[end+1]=tmp.

  • 以上是一次循环的过程

  • 我们让end从0到n-2(n为数组长度),为什么end不到n-1也就是为数组最后一个元素呢

  • 很简单,因为end后面还需要有一个待插入的元素tmp,所以end从0到n-2

  • 稳定性:稳定

  • 时间复杂度:最好O(N)(有序),最差O(N2)(逆序)

  • 空间复杂度:O(1)

  • 代码如下

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n-1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end+1] = tmp;
	}
}

2.希尔排序

  • 理解了插入排序,那么理解希尔排序就不会很困难
  • 希尔排序其实是插入排序的优化,它分两步走
  1. 预排序,让数组接近有序(先分组,对分组的数据进行插入排序)
  2. 直接插入排序
  • 为什么要进行预排序呢?看过上文的插入排序可以知道,数组越接近有序,插入排序的时间复杂度越低,也就是效率越高
  • 我们通过预排序让数组接近有序,之后再进行插入排序,就可以提高效率
  • 不妨举个例子,假设数组长度为n,我们将数组间隔为3的个元素分一组,就有3组,假设n为300
  • 每组挪动一次数据,就是进行一次插入排序,就能跨越3个元素的位置,也就是让小的元素更快到达数组前面,让大的元素更快到达数组的后面
  • 请添加图片描述
    请添加图片描述
  • 如图,图中每3个数为一组,分为红橙绿三组
  • 对红色的这组进行一次排序,5就往前挪动了3位,9就向后挪动了3位
  • 我们设分组的间隔为gap,可知gap为1时就是上文的插入排序
  • gap越大,大的和小的数就能更快的挪到相应的位置,gap越小挪动越慢
  • gap越大,数组越接近无序,gap越小越有序
  • 如何选取gap的值又成了一个问题
  • 一般来说,gap一开始可以选择数组长度的三分之一,之后每次除以3,直到gap为1
  • 代码书写起来也很简单,将直接1的位置换为gap就足够了
  • 令gap=gap/3+1是为了保证gap最后一次取值为1
  • end的遍历范围也从n-2变成了n-gap-1,因为最后会剩下来gap个数分别为gap组的最后一个tmp取值
  • 稳定性:不稳定(预排序时相同的数可能分到不同的组)
  • 时间复杂度:因为gap能取不同的值,所以没有确定的复杂度,可当成n的1.3次方
  • 空间复杂度:O(1)
    请添加图片描述
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

3.选择排序

  • 选择排序的思想就是每次遍历找出最大值,然后放到数组的末尾
  • 再从剩下的数组找最大放到数组倒数第二个位置,如此反复
  • 我们可以再优化一点,同时找出最大值和最小值
  • 具体实现如下
  1. 让left=0,right=n-1(n为数组长度)
  2. 从left遍历到right,找出max,min的下标maxindex,minindex
  3. 将a[minindex]和a[left]交换,a[maxindex]和a[right]交换
  4. 将left++,right–,当left>=right时循环结束
  5. **如果left==maxindex,我们需要修正maxindex的值,否则将a[minindex]和a[left]交换,原本最大值所在的位置变为最小值所在的位置,后续再让a[maxindex]和a[right]交换会出问题,因为此时的a[maxindex]其实是最小值,而a[left(maxindex)]和a[mindex]交换了,所以此时a[minindex]指向的才是最大值,我们需要让maxindex=minindex;
  • 稳定性:不稳定,这里很容易误以为是稳定的,因为想着只要从后往前找最大值,然后严格大于才作为max与right交换,就可以不改变相对顺序,但其实这是错的

  • 因为你不确定你right位置的值和max位置的值交换了后会不会改变和right位置的值相等的的值和right的相对顺序
    请添加图片描述

  • **时间复杂度:O(N2)7

  • 空间复杂度:O(1)

void SelectSort(int* a, int n)
{
	int left = 0,right=n-1;
	while (left < right)
	{
		int minIndex = left, maxIndex = left;
		for (int i = left;i <= right; i++)
		{
			if (a[i] < a[minIndex])
			{
				minIndex = i;
			}
			if (a[i] > a[maxIndex])
			{
				maxIndex = i;
			}
		}
		Swap(&a[left], &a[minIndex]);
		//如果left和maxIndex重合,left换了会导致maxIndex不指向最大值改变,要修正
		if (left == maxIndex)
		{
			maxIndex = minIndex;
		}
		Swap(&a[right], &a[maxIndex]);
		left++;
		right--;
	}

4.堆排序

堆排序可以看我之前的博客:从二叉树到堆排序

5.冒泡排序

  • 冒泡排序应该是大多数人最先学的排序了,因为简单易懂
  • 思想也十分简单,有n个数,需要n-1趟。每一趟将最大的数冒到最后
  • 然后将次大的数冒到最后,直到n-1趟走完或者其中有一趟没有发生交换,说明数组已经有序,就该提前结束了
  • 每一趟就是将数组从第一个数遍历到没有被冒过的数,如果这个数的下一个数比这个数大(a[j+1]>a[j]),就将a[j+1]和a[j]交换,这样大的数就被放到了后面,反复进行,大 的数就会不断往数组的后面走,所以说叫冒泡排序
  • 稳定性:稳定
  • 时间复杂度:最好O(N)(有序),最差O(N2)(逆序)
  • 空间复杂度:O(1)

请添加图片描述

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int exchange = 0;
		for (int j = 0; j < n - i - 1; j++)
		{
			
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
				exchange = 1;
			}
		}
		if (exchange==0)
		{
			break;
		}
	}
}

6.快速排序

总体思想

  • 快速排序的总体思想就是先让数组中的一个数左边的数都小于它,右边的数都大于它
  • 那么这样它在最后有序数组中的位置也就定了,也就说我们排好了一个数
  • 之后再从头到它的左边,它的右边到数组尾,继续这样排,也就是递归
  • 到最后每个数的位置都确定了这个数组也就有序了,这就是快排
  • 实现使一个数左边都小于它,右边都大于它的这一步我们可以称它为partsort,有三种实现方式

(1)左右指针法

  1. 选出一个keyi,一般是最左或最右,a[keyi]这个数就是我们最终要让它左边都小于它,右边都大于它的数
  2. 假设选数组最左做keyi,那么right就要先遍历
  3. 定义两个变量left和right,一开始分别指向数组头尾,right找到比a[keyi]要小的数的下标,left找到比a[keyi]要大的数的下标,然后交换a[left]和a[right]
  4. 当left和right相遇,就结束循环,meeti为left和right相遇的位置,再将a[keyi]和a[meeti]交换
    请添加图片描述
  • 或许你不能明白为什么选最左做keyi,right就要先遍历,那么你可以看看如果先让left遍历,会发生什么
    请添加图片描述
  • 可以看到,如果left先走,那么left和right相遇的位置会比a[keyi]大,因为left会先找到一个比a[keyi]大的值然后停下,之后right再和left相遇,相遇的值肯定比a[keyi]大,再让a[meeti]和a[keyi]交换就不能满足我们的需求了
  • 而如果让right先走,right要找到比a[keyi]小的数才停下,而没找到就和left相遇,此时left还指向上次和right交换完的值,也肯定比a[keyi]小
  • 这里前两行代码的用意后面会说,看的时候可以先跳过
int PartSort1(int* a, int left, int right)
{
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);
	int keyi = left;
	while (left < right)
	{
		//找小
		while (left < right && a[right] >= a[keyi])
		{
			right--;
		}
		//找大
		while (left < right && a[left] <= a[keyi])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	int meeti = right;
	Swap(&a[keyi], &a[meeti]);
	return meeti;
}

(2)挖坑法

  • 第二种方法是挖坑法,也比较好理解,我们需要一个key保存刚开始数组最左边的值
  • 让一开始left在的位置也就是key的位置为坑,right找到比key小的值就填进去,然后right就成新的坑
  • left再找比key大的值,找到了填入right所在的坑
  • 当left和right相遇,再将key填入这个坑,就完成了
    请添加图片描述
int PartSort2(int* a, int left, int right)
{
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);
	int key = a[left];
	while (left < right)
	{
		//左边是坑,找小放到左边的坑
		while (left < right && a[right] >= key)
		{
			right--;
		}
		//找到小,放左坑,右边成为新的坑
		a[left] = a[right];
		//找大
		while (left < right && a[left] <= key)
		{
			left++;
		}
		//找到大,放右坑,左边变成新的坑
		a[right] = a[left];
	}
	//相遇为坑,将key放入最后的坑
	a[right] = key;
	return right;
}

(3)前后指针法

  • 前后指针法的代码很简洁,思想也不复杂
  • 就是遍历数组找到比a[keyi]小的值就放在它后面,最后让a[keyi]和最后一个比它小的值交换,这样它左边的值就都比它小,右边的值都比它大了
  • 我们定义cur和prev,让cur找到比a[keyi]要小的值,然后让prev+,如果prev!=cur,再让a[cur]和a[prev]交换
  • 最后cur遍历完后说明已经没有比a[keyi]小的值了,让a[keyi]和a[prev]交换,大功告成
    注意,三种方法排序完之后数组有可能是不一样的
    请添加图片描述
int PartSort3(int* a, int left, int right)
{
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);
	int keyi = left, prev = left, cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}

(4)时间复杂度与三数取中

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	int meeti = PartSort3(a, begin, end);
	QuickSort(a, begin, meeti - 1);
	QuickSort(a, meeti + 1, end);
}
  • 写好了Partsort中的任意一个,快速排序就手到擒来了
  • 递归每次meeti的左和右,就完成了快速排序
  • 那么让我们看看快速排序的时间复杂度
  • 有意思的是,快速排序的时间复杂度也是不确定的
  • 当每次meeti都在中间时,一共需要进行log2N次partsort,一次partsort的复杂度为O(N),所以快排的时间复杂度为O(N*log2N),空间复杂度为O(log2N)
  • 但是数组要是有序,每次partsort之后meeti都在最左边,每次只能递归右半部分,时间复杂度就变成了O(N2),空间复杂度变成了O(N)
  • 这太恐怖了,时间复杂度这么高,还敢叫快速排序?叫龟速排序比较适合吧
  • 所以我们还需要对快排进行优化
    请添加图片描述
    请添加图片描述
    请添加图片描述
  • 可以看到,让快排排序有序的数组速度十分的慢(测试用例为十万个随机数)
  • 三数取中来咯,啊哈哈哈哈
  • 为了避免上图右边的情况,我们将最左边的key的值换成a[left],a[mid],a[right]中的中间大的那个值
  • 那样如果数组有序,可以直接将其变为最好的情况,这就优化了快排
int GetMidIndex(int* a, int left, int right)//三数取中优化快排
{
	int mid = (left + right) >> 1;
	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else //a[left] <= a[mid]
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (left > right)
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

请添加图片描述

  • 加了三数取中以后,快排排有序数组反而是最快的了,避免了最坏的情况

(5)小区间优化

  • 这个优化效果其实不明显,但是还是加上比较好,根据数据大小的不同自己调整参数可以再一步优化快排
  • 我们知道递归要创建函数栈帧,递归多了会很慢,所以我们可以在快排只剩很少数据时,比如10个时终止递归,用别的排序算法帮它排好序
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if (end - begin > 10)
	{
		int meeti = PartSort3(a, begin, end);
		QuickSort(a, begin, meeti - 1);
		QuickSort(a, meeti + 1, end);
	}
	else
	{
		HeapSort(a + begin, end - begin + 1);
	}
}

请添加图片描述
请添加图片描述

  • 上图为没有加小区间优化排序100万无序数据
  • 下图为加了小区间优化大于100时采用堆排序,排序100万无序数据

(6) 针对所有数据重复的优化

  • 上面几种方法优化后的快排已经十分优秀,但是还是架不住某些测试用例,比如一亿个2什么的
  • 这时上面三种partsort的方法全部退化成了O(N2),我们需要针对这种情况优化
  • 这里并没有采取前文提到的三数取中优化,而是采取了随机值,写起来更为简洁效果也更好
  • 前面三种方法对于相同数据失效的原因是在我们找到和key相等的值后我们选择继续往前或往后找
  • 这样如果所有数据相同我们就会遍历数组一遍,并在每次递归中也一直在遍历数组
  • 这样我们的复杂度就会变成O(N2),故我们不把数组分为大于等于和小于等于key两部分
  • 而是将数组分为三部分,一部分严格小于,一部分等于,一部分严格大于
  • 这里我们需要三个指针或者三个变量,我们将其称为cur,prev,end
  • cur是用于遍历数组,prev记录比key小的值的位置,end记录比key大的值的位置
  • 当cur遇到比key小的值就与prev所在的值交换,并让prev++
  • 遇到比key大的值就与end所在的值交换,并让end++
  • 遇到和key相等的值时直接让cur++,开始下一次查找
  • 注意:无论是遇到比a[cur]大的值还是比a[cur]小的值,交换后cur都不能自增1
  • 因为交换过来的值仍然可能大于或小于a[cur],可能需要再次交换
  • 结束条件是cur>end,即cur==end时还需要判断需不需要交换
  • 因为此时end所在的位置还没判断过,不确定它真正的位置
 void quick_sort(int* a,int left,int right)
  {
       if(left>=right) return;
       int mid=rand()%(right-left+1)+left;
       swap(&a[left],&a[mid]);
       int val=a[left];
       int cur=left,prev=left,end=right;
       while(cur<=end)
       {
           if(a[cur]==val) cur++;
           else if(a[cur]<val)
           {
               swap(&a[prev],&a[cur]);
               prev++;
           }
           else
           {
               swap(&a[end],&a[cur]);
               end--;
           }
       }
       quick_sort(a,left,prev-1);
       quick_sort(a,end+1,right);
  }

请添加图片描述
请添加图片描述

请添加图片描述

请添加图片描述

(7)非递归实现快排

  • 非递归实现快速排序的话需要借助我们的栈
  • 我们递归的目的就是将每次partsort后的左和右继续partsort
  • 如果不递归,我们可以借助栈来存储每次的左右区间,partsort后pop掉,再push新的左右区间,直到栈为空,这样我们就可以不通过递归实现栈了
void QuickSortNonR(int* a, int begin, int end)
{
	ST st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);
	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);
		int keyi = PartSort1(a, left, right);
		if (left < keyi - 1)
		{
			StackPush(&st, left);
			StackPush(&st, keyi-1);
		}
		if (right > keyi + 1)
		{
			StackPush(&st, keyi+1);
			StackPush(&st, right);
		}
	}
	StackDestroy(&st);
}

(8)稳定性

  • 快速排序是一个不稳定的算法,看看下面的例子
  • 在partsort中相同值的相对顺序可能会被改变
    请添加图片描述

7.归并排序

(1)递归版

  • 先看看下面这张图,帮助你理解一下归并排序
    请添加图片描述
  • 实际上归并排序就是将数组不断划分到一个个小的有序数组,我们不知道划分后的数组是否有序,所以直接划分成只有一个数的数组,就一定有序了
  • 之后再把它们归并进一个临时数组,然后将这个临时数组拷贝回原数组
  • 归并的过程也不复杂,因为现在两个数组都是有序的了
  • 我们从两个数组的头开始比较两个数组的值,设为begin1和begin2,a[begin1]和a[begin2]中小的那个进临时数组,然后让begin1或begin2往后走
  • 当begin1或者begin2走到头时,就结束循环,再让有剩余元素的数组中的元素进入临时数组
    请添加图片描述
  • 用来拷贝的数组只用定义一次,不能写在递归里面,所以我们写个子函数在子函数中进行递归
  • 代码整体思想就是你原本左右两个数组是无序的,那就继续用归并排序把它变成有序的,然后在来归并
void _MergeSort(int* a, int left, int right,int* tmp)
{
	if (left >= right)
	{
		return;
	}
	int mid = (left + right) >> 1;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	//归并完成以后,拷贝回到原数组
	memcpy(a + left, tmp + left, sizeof(a[0])*(right - left+1));
	/*for (int j = left; j <= right; j++)
	{
		a[j] = tmp[j];
	}*/

}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(a[0]) * n);
	if (tmp == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	_MergeSort(a, 0, n - 1,tmp);
	free(tmp);
}
  • 稳定性:稳定
  • 时间复杂度:一共会被划分为log2(N)层,每层归并一共都有N个数,复杂度为O(N),相乘为O(N*log2(N))
  • 空间复杂度:O(N),需要创建长度为N的临时数组,递归深度为log2(N),取大的就是O(N)

(2)非递归版

  • 归并排序不借助递归也能够实现,我们可以直接在原数组上以gap==1开始归并,之后每次让gap*2,再归并就可以了
  • 这样左数组开始为i,结束为i+gap-1
  • 右数组开始为i+gap,结束为i+2*gap-1
  • 但是这样会有些小问题,不是每次归并第一个和第二个数组都是够gap个的,
  • 可以分为三种情况
  1. 第一个区间刚好够gap个,第二个区间不存在,就是第二个数组有0个元素,此时第一个数组已经有序,不用归并
  2. 第一个区间不够gap个,已经有序也不用归并
  3. 第二个区间存在,但是不够gap个,调整区间大小
    请添加图片描述
    请添加图片描述
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(a[0]) * n);
	if (tmp == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int left = begin1, right = end2;
			//如果第二个区间不存在,或者第一个区间不够gap个,就不进行归并
			if (begin2 >= n)
			{
				break;
			}
			//如果第二个区间存在,但是不够gap个,调整区间大小
			if (end2>=n)
			{
				right=end2 = n - 1;
			}
			int j = left;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			//归并完成以后,拷贝回到原数组
			memcpy(a + left, tmp + left, sizeof(a[0]) * (right - left + 1));
		}
		gap *= 2;
	}
	free(tmp);
}

8.计数排序

  • 计数排序是个比较好理解的排序
  • 需要排序的数组为a,用来计数的数组为count
  • count数组用来统计数组a中每个数出现的次数,比如a中有两个2
  • 那么count[2]=2
  • 总而言之,a[i]是几就对count数组的这个位置加一
  • 之后再把count数组中的值依次拷贝回数组a,就完成了排序
  • 这是绝对映射,我们可以考虑优化一下使用相对映射
  • 否则要是a中的值都是一万以上的数,那么count前面一万个位置就白费了
  • 我们可以取出a中的最大和最小值,max-min=range,创建大小为range的count数组
  • 然后让count[a[i]-min]++,就可以避免浪费
    请添加图片描述
void CountSort(int* a, int n)
{
	int i,j,min = 0,max=0;
	for (i = 0; i < n; i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}
		if (a[i] > max)
		{
			max = a[i];
		}
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	memset(count, 0, sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	for (i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
	j = 0;
	for (i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			a[j++] = i+min;
		}
	}
	free(count);
}
  • 稳定性:不稳定
  • 时间复杂度O(N+range)
  • 空间复杂度O(range)
  • 虽然计数排序的时间复杂度一般情况下优于其它比较算法,但是缺点也很明显
  • 它只适合数据较为集中时使用,并且只能排序整数
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

八大排序汇总 的相关文章

随机推荐

  • 性能优化:虚拟列表,如何渲染10万条数据的dom,页面同时不卡顿

    最近做的一个需求 当列表大概有2万条数据 又不让做成分页 如果页面直接渲染2万条数据 在一些低配电脑上可能会照成页面卡死 基于这个需求 我们来手写一个虚拟列表 思路 列表中固定只显示少量的数据 比如60条 在列表滚动的时候不断的去插入删除d
  • GMP初探

    G Goroutine 协程 用户级的轻量级线程 M 对内核线程的封装 P 为G和M的调度对象 主要用途是用来执行goroutine 维护了一个goroutine队列 即runqueue 由来 单进程时代 这个时代不需要调度器 早起的操作系
  • PMS-adb install安装应用流程(Android L)

    第一次画流程图画的不好 通过adb install安装应用时对framework来说会首先调用Pm java的runInstall 方法 private int runInstall int installFlags 0 int userI
  • mesa调试技巧

    技术关键字 mesa log系统 环境变量 目录 前言 一 gdb或vscode的断点调试 二 mesa log 系统的使用 总结 前言 软件调试技术是要求软件开发人员必备的一项技能 不同的问题具有不同的调试手段和方法 本文从mesa库的实
  • xcode报错:Cycle inside *******

    xcode报错 Cycle inside building could produce unreliable results This usually can be resolved by moving the target s Heade
  • 基于pytorch实现的Auto-encoder模型

    最近因为在自己论文当中可能要用到Auto encoder 这个东西 学了点皮毛之后想着先按照别人的解释实现一下 然后在MNIST数据集上跑了下测试看看效果 话不多说直接贴代码 Author Media 2020 10 23 import t
  • ci/cd 流程图_如何在整个CI / CD工作流程中衡量软件的可靠性

    ci cd 流程图 克服具有持续可靠性的CI CD工作流程中保持代码质量的挑战 CI CD的做法鼓励在开发中频繁进行代码集成 加快新版本的准备工作并自动化部署 借助这种新工具 软件开发生命周期的这些部分都得到了改善和加速 同时 我们用于评估
  • Spring MVC(Boot) Servlet 3.0异步处理,DeferredResult和Callable(续篇)

    目录 背景 意外发现 结论 背景 上篇Spring MVC Boot Servlet 3 0异步处理 DeferredResult和Callable 我把WebMvcConfig 代码 继承WebMvcConfigurationSuppor
  • 搜索引擎和知识图谱那些事 (上).基础篇

    这是一篇基础性文章 主要介绍搜索引擎和知识图谱的一些原理 发展经历和应用等知识 希望文章对你有所帮助 如果有错误或不足之处 还请海涵 参考资料见后 一 搜索引擎 一 搜索引擎的四个时代 根据张俊林大神的 这就是搜索引擎 这本书中描述 推荐大
  • C++ stl容器

    1 std string string constructor include
  • 有哪些通俗易懂的例子可以解释 IaaS、PaaS、SaaS 的区别?

    首先 什么 云 很多年前 我们家住一小平房 喝水就喝院子里的井水 冬天取暖自己烧煤炉 后来经济状况好了 搬进了楼房 喝水喝上了集中供应的自来水 冬季用上了集中供应的暖气 再也不用打水和掏黑煤球 这就是 云 的基本概念 过去企业数据维护需要恒
  • 声纹识别中pooling总结

    1 Statistics Pooling http danielpovey com files 2017 interspeech embeddings pdf The statistics pooling layer calculates
  • Python手册(Standard Library)--math+random

    文章目录 math random math math import math math truck x 取整 math ceil x 天花板 math floor x 地板 math exp x math log x b math e 计算
  • 机器学习与数学基础知识(一)

    最近 朋友分享给我一套 七月在线 的机器学习视频 我几经思量之后 决定从视频量最少的数学基础部分开始看起 今天学习完了第一个视频 长达2小时 感觉老师讲的挺不错的 以前自己就对机器学习很感兴趣 做了一些了解和尝试性地学习 也看了一点经典的林
  • 专升本数学——极限与连续(二)笔记

    一 无穷大量与无穷小量 1 定义 无穷小量 如果 lim f x 0 则称 f x 是此极限条件下的无穷小量 本质 以 0 为极限的函数 x 2 是 x gt 0 时的无穷小量 无穷大量 如果 lim f x 无穷 则称 f x 是此极限条
  • 详解grep(一)grep基础、语法格式、常用选项与退出状态码的案例解析

    目录 一 GREP基础 1 1 grep的全称 1 2 grep语法格式 二 grep常用选项 2 1 长短选项的说明 2 2 显示常用信息 2 3 控制匹配模式的选项 2 4 控制输出内容的选项 2 5 控制输出行前缀的选项 2 6 控制
  • java学习笔记——JDBC 中 ResultSet、ResultSetMetaData配置对象的属性、批处理

    使用ResultSet ResultSetMetaData操作数据表 SELECT public void test1 1 获取连接 Connection conn null PreparedStatement ps null 4 执行 S
  • SpeechSynthesisUtterance 语音合成使用 文字语音播报

    一 关于HTML5语音Web Speech API HTML5中和Web Speech相关的API实际上有两类 一类是 语音识别 Speech Recognition 另外一个就是 语音合成 Speech Synthesis 这两个名词听上
  • ESP32(MicroPython) 编码器电机闭环控制

    本人最近查找资料时 发现ESP32上的使用MicroPython的编码器电机相关程序较少 闭环控制程序都是Pyboard上的 与ESP32不完全兼容 本人通过micropython编程 esp32 drv8833 霍尔编码器 micropy
  • 八大排序汇总

    目录 1 插入排序 2 希尔排序 3 选择排序 4 堆排序 5 冒泡排序 6 快速排序 总体思想 1 左右指针法 2 挖坑法 3 前后指针法 4 时间复杂度与三数取中 5 小区间优化 6 针对所有数据重复的优化 7 非递归实现快排 8 稳定