【数据结构】——八大排序(详解+图+代码详解)看完你会有一个全新认识

创作不易,给一个免费的三连吧?! 

 

前言

排序在生活中是非常重要的,所以排序在数据结构中也占有很大的地位,相信大家可能被这些排序弄得比较混淆或者对某个排序原理没有弄清,相信看完本篇会对你有所帮助!


排序的概念与应用

排序概念

排序:排序就是使一串数据,按某个或者某些关键字的大小,递减或者递增排列起来的操作

稳定性:在排序的过程中,可能存在两个或者以上的关键字相等的记录,排序的结果可能会不一样,这里就分成了稳定和不稳定。稳定就是指如果有两个相等的关键字设置为 r1,r2,没有排序的时候,r1在r2的前面,排完以后r1还是在r2的前面,这个排序就是稳定的,反之就是不稳定的(比如1 2 3 5 5,排完以后可能后面的5换到了前面5的前面)

内排序:就是把全部的数据元素放在内存中进行排序;

外排序:数据元素太多不能同时放在内存中,整个排序的过程需要在内外存之间进行多次交换数据才能进行

排序应用

 排序的分类

下面给出排序算法的代码声明

//插入排序
void InsertSort(int* a, int n);
//希尔排序
void ShellSort(int* a, int n);
//冒泡排序
void BubbleSort(int* a, int n);
//选择排序
void SelectSort(int* a, int n);
//双指针快排
void QuickSort(int* a, int left,int right);
//霍尔快排
void HuoerSort(int* a, int left, int right);
//挖坑法
void HoelSort(int* a, int left, int right);
//堆排序
void HeapSort(int* a, int n);
//归并排序
void _MergeSort(int* a, int begin, int end, int* tmp);
//计数排序
void CountSort(int* a, int n);

排序算法实现

1.插入排序

void InsertSort(int* a, int n);

插入排序思想 :把要排序的数,从后往前去插入,插入完后保持有序,知道所有数插入完成,得到一个新的有序序列

 这其实就是和你打扑克摸牌一样

1.1. 直接插入排序

在插入第i个元素时,前面的i-1个元素都已经有序,这个时候,把a[i]这个元素和前面a[i-1],a[i-2]...a[0],进行比较,找到合适位置将其插入,这个时候把后面的元素往后面移动一位就行。

如果考虑最坏的情况则插入的数都要移到到最前面,然后整体往后移,所以时间复杂度为

O(N^2)

 空间复杂度为

O(1)

如果元素越接近有序,那么这个算法的时间效率也就越高。

因为在插入的过程中不会导致相同元素的相对位置发生改变,所以这种算法是稳定的

代码实现

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n-1; i++)
	{
		int end = i;//一开始第一个元素肯定是有序的,所以不用管
		int tmp = a[i + 1];//这里表示第二个元素,开始从后往前插入
		while (end >= 0)
		{
			if (tmp < a[end])//这里排升序。
			{
				a[end + 1] = a[end];//如果要插入的数,比前一个数小
                                    //插入到这个数的前面时,要往后移动一个单位
				end--;
			}
			else
			{
				break;//如果比它大直接退出
			}
		}
		a[end + 1] = tmp;//然后在end+1的位置插入这个数
	}
}

 这里用图来说明

 可以很清楚的看到,在比较完以后,插入的位置是end+1。这里的排序是排好了一部分的,其实完整的应该是从只有一个数据开始排,这里这样操作可以更好的理解这一步的操作

1.2 希尔排序

希尔排序就是对直接插入排序的升级和改良。

希尔排序的基本思想:先对序列进行预处理,使得它接近有序,然后进行直接插入排序,最后变成有序序列

前面就说了直接插入排序对于有序序列的插入效率是很高的,所以希尔排序的速度就会很快

它的整体思想就是取一个整数,把这个序列分成几个组,所有距离为这个整数的序列分在这个组里面,然后分别对这些序列进行插入排序,然后让这个整数减小,再分组,减小再分组,最后当这个整数变为1的时候,也就变成了插入排序

可能这样还是比较难以理解,可以结合下面的图进行推理

从图中可以看出,如果一直往后面走,这个序列会越来越接近有序,最终完成有序

由于它的时间涉及到一个“增量”序列的函数,所有它的时间复杂度非常难计算,这里只能模糊说明一下,在开始的时候由于取的间距比较大,然后比较和交换的次数不是很多,随着这个间距缩小,会导致所需要的时间越来越多,但是后面由于基本有序,用的时间又会越来越短

由此我们可以大概得出时间复杂度为

O(n^3/2)

空间复杂度为

O(1) 

由于它一直在交换,很容易导致前后位置发生改变所以它是一个不稳定的排序

代码实现

 

void ShellSort(int* a, int n)
{
	int gap = n;//先对其赋值
	while (gap > 1)
	{
		gap = gap / 3 + 1;//然后进行缩小,也可以是gap/=2
                          //这里的+1是为了防止出现gap==0的情况
		for (int i = 0; i < n - gap; i++)//i<n-gap,是为了防止越界
		{
			int end = i;
			int tmp = a[end + gap];//后面的操作跟直接插入排序差不多,
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;//这里不是-1,是-gap
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

希尔排序和直接插入排序的代码非常像,可以去对比理解 

 2.交换排序

2.1  冒泡排序

冒泡排序非常容易理解,不想上面的希尔排序,有点复杂

冒泡的思想就是,两两交换,进行一轮以后最大的数就到了最后面去了,然后一直重复,直到把所有的数全部排好

时间复杂度

O(N^2)

空间复杂度

O(1)

在交换的过程中,如果两个数相等,那么不交换,所有他们的相对位置没有发生改变,所有冒泡是一种稳定的排序 

代码实现

void BubbleSort(int* a, int n) 
{
	for (int i = 0; i < n; i++)
	{
		int falg = 1;//
		for (int j = 1; j < n - i; j++)
		{
			if (a[j] < a[j - 1])
			{
				
				int tmp = a[j];
				a[j] = a[j - 1];
				a[j - 1] = tmp;
				falg = 0;
			}
		}
		if (falg)//如果是有序,那么就不用比较了,直接跳出
			break;
	}
}
2.2  快速排序

 快速排序有三种版本

1.hoare

2.双指针

3.挖坑

这三种版本只是代码实现不同,时间效率上都是一样的

快排的基本思想:

在一个序列里,先选出一个数,把大于这个数的放在这个数的右边,小于的放在这个数的左边,因为后面的操作和这里重复,所以这里选择用递归去完成

因为这里的操作和二分有点像,所以时间复杂度大致为

O(NlogN)

空间复杂度为

O(1) 

由于是不断交换位置,所以相对位置也是难以保证的,所以快排是一个不稳定的排序 

代码实现,这里直接给出三个版本以及思想

1.hoare 大致为两边向中间靠近,先选一个key,设置两个指针,一个prev在key的位置,一个cur在最后的位置,一开始cur先走,寻找比key小的,找到以后,prev后走去寻找比key大的,两个都找到以后然后交换,最后两个指针相遇,然后交换prev位置和key位置的值,最终完成一遍,然后再去递归key左边和key右边

下面给图来理解

 由图我们可以去写出代码

void HuoerSort(int* a, int left, int right)
{
	if (left >= right)//递归条件
		return;
	int keyi = left;
	int begin = left, end = right;//这里用设置前后位置
	while (left < right)
	{
		//右边找小
		while (left < right && a[right] >= a[keyi])//这里的>=是为了防止如果是相等的数,那么就会死循环
                                                   //这里的left<right为了防止数组越界
		{
			right--;
		}
		//左边找大
		while (left < right && a[left] <= a[keyi])//和上面一样
		{
			left++;
		}
		Swap(&a[right], &a[left]);//找到了就交换
	}
	Swap(&a[left], &a[keyi]);//这里也可以是right和keyi进行交换,都一样
	keyi = left;。交换以后记得把keyi更新,以便后面的递归
	HuoerSort(a, begin, keyi-1);//左边递归
	HuoerSort(a, keyi + 1, end);//右边递归
}

 从图中我们可以发现左边的都是小于key的,右边的都是大于key的,可能有的人会疑惑为啥与key交换的就是比key小的!!

其实可以这样想,cur要一直找到小于key的才停下,所以如果是prev遇到cur,那么也是小于key的,第二种情况就是cur停止了,prev也停止了,那么两个交换,prev的位置的值就是小于key的,那么cur继续走遇到prev那么也就是小于key的,所以这个恒成立

2.双指针

这里的双指针就是一前一后的指针,和上面不一样,一个是在开头一个是在末尾

基本思想都差不多,这个虽然表现形式不一样,但都是把小于key的值丢到左边,大于的丢到右边,然后递归进行下去直到完成

下面给出图,有助于理解

根据上面的图,我们可以写出代码

void QuickSort(int* a, int left,int right)
{
	if (left >= right)
		return;
	int perv = left, cur = left + 1;
	int keyi = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			perv++;
			Swap(&a[cur], &a[perv]);//无脑交换就行
		}
		cur++;//cur继续走
	}
	Swap(&a[perv], &a[keyi]);走到结束的时候,交换
	keyi = perv;
	QuickSort(a, left, keyi-1);//递归去实现左边
	QuickSort(a, keyi + 1, right);//递归右边
}

 3.挖坑法

挖坑法也是开头一个指针,末尾一个指针,然后先选择一个坑,然后把这个坑的n值保留起来,右边开始找小于key的值,找到了就把这个值丢到这个坑里面,然后自己形成新的坑,然后左边也是一样,多说无益,直接上图

根据图可以写出代码

void HoelSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int key = a[left];
	int hoel = left;
	int begin = left, end = right;//这里就是把开始的位置记下来,然后用left,right去走
	while (left < right)
	{
		while (left<right && a[right]>=key)//这里的条件和第一个方法一样的道理
		{
			right--;
		}
		a[hoel] = a[right];
		hoel = right;
		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[hoel] = a[left];
		hoel = left;
	}
	a[hoel] = key;
	HoelSort(a, begin, hoel -1);,hoel的位置就是最后坑的位置,然后递归左边和右边
	HoelSort(a, end, hoel + 1);

以上就是交换排序了,快排掌握一种就行,这里建议是双指针,因为其他细节处理比较多,容易出错 ,但是画图去写还是可以的,三种效率没有差别,只是表现形式不一样

3.  选择排序

3.1 简单选择排序

 这里的简单现在排序,经典的就是选一个最小数,然后把它和第一个交换,然后一直遍历就行,这里比较好理解,就直接上代码了。

但是这里对它进行一些优化,在遍历数组的时候,选一个最小的,选一个最大的,分别放在头和尾,然后再选次大和次小的,虽然有点优化,但是这对整体的影响很小,不能根本上改变简单选择排序效率问题

void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int mini = begin, maxi = begin;,
		for (int i = begin; i <= end; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if(a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		int tmp1 = a[mini];//最小的值和第一个元素交换
		a[mini] = a[begin];
		a[begin] = tmp1;
		if (begin == maxi)//这里是怕如果begin和maxi重合了,由于上面已经和最小的交换了
                          //那么后面如果直接交换就会出问题,所以特判一下
		{
			maxi = mini;
		}
		int tmp2 = a[maxi];//最大的值和最后一个元素交换
		a[maxi] = a[end];
		a[end] = tmp2;

		end--;//交换以后区间缩小,然后继续选数
		begin++;
	}
}

时间复杂度

O(N^2) 

空间复杂度

O(1) 

因为选择排序可能会导致前后的相对位置改变,所以它是不稳定的排序

比如5 5 1 1,a[2]与a[0]交换,那么5 5的相对位置就改变了

3.2  堆排序

对于堆排序,在之前的文章已经说明了原理,这里就不再说明了,想看原理可以去看这篇博客

这里简单说一下,如果想排升序那么建大堆,想排降序,建小堆,然后建堆向下调整就可以完成排序

这里直接给出代码

 

void AdjustDown(int* a, int n, int parent)//向下调整,我们需要的是最后的父亲结点,所以传父亲过来
{
	int child = parent * 2 + 1;//孩子左边运算的公式
	while (child < n)
	{
		if (child+1<n&&a[child + 1] > a[child])//判断
		{
			child++;
		}
		if (a[child] > a[parent])这里是建大堆
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

由于向上调整的时间复杂度比向下调整要高所以我们都采用向下调整算法,也就是说我们建堆和排序都用向下调整就可以搞定

时间复杂度

O(NlogN) 

空间复杂度

O(1) 

 堆排序这样交换,肯定是不稳定

4. 归并排序

归并排序和快速排序有点像,归并排序就是分成两个区间,然后一直分直到分成1个数一个区间的时候,这个时候就是有序的了,因为一个数就是有序的😁,然后两个区间的数进行比较,把小的数放到一个数组里面去,然后把大的数放到后面,这样这两个区间合并变成一个区间就是有序的了,接着再进行重复操作,可以理解为归并是后序遍历,快排是前序遍历,这样比较方便理解

 下面看图理解

下面给出代码

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;
	int mid = (begin + end) >> 1;//分开两个区间
	_MergeSort(a, begin, mid, tmp);//递归找直到只有一个数有序为止
	_MergeSort(a, mid+1, end, tmp);//一样


	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] > a[begin2])
		{
			tmp[i++] = a[begin2++];//这里把小的数放进tmp数组中
		}
		else
		{
			tmp[i++] = a[begin1++];
		}
	}
//这里处理如果一个区间放完了,后面的区间还有剩余的,那么直接放进去就行了
//因为剩下的肯定都是要比放进去的大
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));//放完以后拷贝回原数组
}

注意拷贝是合并一段拷贝一段,不是排好以后再一起拷贝

 时间复杂度

O(NlogN)

空间复杂度

因为需要用到辅助空间

O(N) 

再合并的过程中,相等的值不会去交换 所以是稳定的排序

5.计数排序

 计数排序的思想和其他的不一样,这个不是交换数据然后排序,这个有点像用数组下表去表示要排序的值

 

这里图就不画了,从别人那找了一张

 有了图的理解那么我们可以很快写出代码,不过我们还要注意的是,如果这个数列是100 101 102 105 104 108 107这类的,我们开辟数组C去记录就会浪费前面的空间,所以我们这里采取映射

映射就是找出最小的数,和最大的数,表示出我们要开辟的空间范围,但如果是最小的数是1,最大的数1000,这也没办法,因为这毕竟是用空间换时间的一种思

那下面直接上代码理解吧

void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	for (int 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* tmp = (int*)malloc(sizeof(int) * range);
	if (tmp == NULL)
	{
		exit(2);
	}
	memset(tmp, 0, sizeof(int)*range);//全部置0
	for (int i = 0; i < n; i++)
	{
		tmp[a[i] - min]++;//这里用映射的方法 比如最小值为100,那107就表示为7;
	}
	int k = 0;
	for (int i = 0; i < range; i++)
	{
		while (tmp[i]--)
		{
			a[k++] = i + min;//统计好以后,再放进a数组里面去,这样就让a数组有序了
                             //这里的i就是a[i]-min;也就是tmp数组的下标
		}
	}

时间复杂度

O(N) 

空间复杂度

O(N) 

由于他不会改变相对位置,所以它也是一种稳定的排序 

 

还有个基数排序因为用的比较少,这里就不展示了

除了以上这些,其实还有快排和归并非递归的版本,这里一起说明一下

快排非递归

先说说思想,这里的非递归我们采用栈来实现,我们可以先把一整个区间放进栈里面然后取出这个区间放进排序里面,排序拍完会返回一个key,然后我们再分别把他们丢进栈里面,再拿出来,重复刚刚的步骤就可以完成递归了

下面代码根据代码来理解

void QuickSortNonR(int* a, int begin, int end)
{
	ST st;
	STInit(&st);//初始化
	STPush(&st, end);//因为栈是后进先出,所以先进end,后进begin
	STPush(&st, begin);

	while (!STEmpty(&st))//如果栈不为空就继续
	{
		int left = STTop(&st);//后进先出,先出的是begin
		STPop(&st);

		int right = STTop(&st);//后出的是end
		STPop(&st);

		int keyi = PartSort1(a, left, right);//这个函数就是三种快排的一种,只不过它有返回值, 
                                              //返回的是key

		// [left, keyi-1] keyi [keyi+1, right]

		if (keyi + 1 < right)//后进先出,我们递归是先递归左边,所以我们这里先进右边,后进左边
		{
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}

		if (left < keyi-1)
		{
			STPush(&st, keyi-1);
			STPush(&st, left);
		}
	}

	STDestroy(&st);//最后销毁栈
}

这里的栈就不演示了,如果实在不明白可以去看之前的博客文章

归并非递归

 这里归并非递归就比较繁琐,这里的思想很简单,就是一个循环就搞定了,但是不好操作

下面看图来分析理解

可能看完图还是不怎么理解,我们上代码看看

 

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);//开辟一个数组,这里是存要拷贝的区间的

	// 1  2  4 ....
	int gap = 1;
	while (gap < n)//gap不能大于n;
	{
		int j = 0;//tmp数组的下表
		for (int i = 0; i < n; i += 2 * gap)//循环去遍历
		{
			// 每组的合并数据
			int begin1 = i, end1 = i + gap - 1;//这里设置区间1
			int begin2 = i + gap, end2 = i + 2 * gap - 1;//区间2

			printf("[%d,%d][%d,%d]\n", begin1, end1, begin2, end2);

			if (end1 >= n || begin2 >= n)//如果越界直接跳出循环就行,不用下面的比较了因为这就 
                                         //是有序区间
			{
				break;
			}

			// 修正
			if (end2 >= n)//可能第二个区间的元素和第一个区间的元素无序,所以这里采用修正
			{
				end2 = n - 1;//修正到最后一个元素
			}

			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+i, tmp+i, sizeof(int)*(end2-i+1));//这里不能用begin1去代替i,因为begin1 
                                                       //在不停的变化
		}
		printf("\n");
		
		//memcpy(a, tmp, sizeof(int) * n);
		gap *= 2;//别忘记gap要变化,不然就死循环了
	}

	free(tmp);
}

 

以上就是这几种排序的全部内容了,希望看到这里可以给一个免费的点赞和关注,多谢支持,其实这里还有一些关于快排和归并的优化没有提到,打算在后面遇到题目的时候说。 希望大家喜欢

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣HOT100 - 41. 缺失的第一个正数

解题思路&#xff1a; 原地哈希 就相当于&#xff0c;让每个数字n都回到下标为n-1的家里。 而那些没有回到家里的就成了孤魂野鬼流浪在外&#xff0c;他们要么是根本就没有自己的家&#xff08;数字小于等于0或者大于nums.size()&#xff09;&#xff0c;要么是自己的家被别…

【报错】AttributeError: ‘NoneType‘ object has no attribute ‘pyplot_show‘(已解决)

【报错】AttributeError: ‘NoneType’ object has no attribute ‘pyplot_show’ 问题描述&#xff1a;python可视化出现下面报错 我的原始代码&#xff1a; import matplotlib.pyplot as pltplt.figure() plt.plot(x, y, bo-) plt.axis(equal) plt.xlabel(X) plt.ylabe…

了解何为vue-cli及其作用

Vue CLI是一个由Vue.js官方提供的命令行工具&#xff0c;用于快速搭建基于Vue.js的项目。它可以帮助开发者快速搭建项目结构、配置构建工具、添加插件等&#xff0c;从而更加高效地进行Vue.js项目的开发。 注&#xff1a;在创建工程前需要 先使用命令行&#xff1a;npm instal…

实战项目——智慧社区(三)之 门禁管理

1、人脸识别 实现思路 ①查询出所有的小区信息&#xff0c;下拉列表显示&#xff0c;用于后续判断人脸信息是否与所选小区匹配 ②人脸识别&#xff1a;调用腾讯人脸识别的API接口&#xff0c;首先判断传入图片是否为一张人脸&#xff1b;其次将这张人脸去服务器的人员库进行…

拥有一台阿里云服务器可以做什么?

阿里云ECS云服务器可以用来做什么&#xff1f;云服务器可以用来搭建网站、爬虫、邮件服务器、接口服务器、个人博客、企业官网、数据库应用、大数据计算、AI人工智能、论坛、电子商务、AI、LLM大语言模型、测试环境等&#xff0c;云服务器吧yunfuwuqiba.com整理阿里云服务器可以…

SpringBoot 中的日志原来是这么工作的

在有些场景&#xff0c;能通过调整日志的打印策略来提升我们的系统吞吐量,你知道吗&#xff1f; 我们以Springboot集成Log4j2为例&#xff0c;详细说明Springboot框架下Log4j2是如何工作的&#xff0c;你可能会担心&#xff0c;如果是使用Logback日志框架该怎么办呢&#xff1…

langchain-chatchat指定一个或多个文件回答,不允许回答内容有其他文件内容,即屏蔽其他文件内容

1.找到langchain-chatchat中的knowledge_base_chat.py 2.knowledge_base_chat.py的api内容加上一个flie_name参数&#xff0c;即传过来你需要指定一个文件名称&#xff0c;或多个文件名称&#xff0c;同时也可以不指定&#xff0c;加上以下代码&#xff1a; flie_name: List …

2024-4-10 群讨论:JFR 热点方法采样实现原理

以下来自本人拉的一个关于 Java 技术的讨论群。关注公众号&#xff1a;hashcon&#xff0c;私信拉你 什么是 JFR 热点方法采样&#xff0c;效果是什么样子&#xff1f; 其实对应的就是 jdk.ExecutionSample 和 jdk.NativeMethodSample 事件 这两个事件是用来采样的&#xff0c…

[SystemVerilog]Simulation and Test Benches

Simulation and Test Benches 测试语言中有很大一部分专门用于测试台和测试。在本章中&#xff0c;我们将介绍为硬件设计编写高效测试台的一些常用技术。 6.1 How SystemVerilog Simulator Works 在深入研究如何编写适当的测试台之前&#xff0c;我们需要深入了解模拟器的工作原…

git查看单独某一个文件的历史修改记录

git查看单独某一个文件的历史修改记录 git log -p 文件具体路径 注意&#xff0c;Windows下默认文件路径分隔符是 \&#xff0c;在git bash 里面需要改成 /。 git基于change代码修改与提交_git change-CSDN博客文章浏览阅读361次。git cherry-pick&#xff1a;复制多个提交comm…

使用 Citavi 和 NVivo 简化您的文献综述和研究分析

NVivo 是一款支持定性研究方法和混合研究方法的软件。它可以帮助您收集、整理和分析访谈、焦点小组讨论、问卷调查、音频等内容。NVivo&#xff08;1.0版&#xff09;是Windows和Mac的主要版本。遵循最新的主要版本NVivo 12&#xff08;Windows和Mac&#xff09;。 NVivo 强大…

Java Reflection(从浅入深理解反射)

本节的代码链接&#xff1a;reflection 1. 反射的由来 反射机制允许程序执行期借助于Reflection API取得任何类的内部信息&#xff0c;如成员变量、构造器、成员方法等&#xff0c;并能操作对象的属性及方法&#xff0c;在设计模式和框架底层都会用到。 1.1 引入需求 编写框…

Scala实战:打印九九表

本次实战的目标是使用不同的方法实现打印九九表的功能。我们将通过四种不同的方法来实现这个目标&#xff0c;并在day02子包中创建相应的对象。 方法一&#xff1a;双重循环 我们将使用双重循环来实现九九表的打印。在NineNineTable01对象中&#xff0c;我们使用两个嵌套的fo…

sql注入之宽字节注入

1.1 宽字节注入原理 宽字节注入&#xff0c;在 SQL 进行防注入的时候&#xff0c;一般会开启 gpc&#xff0c;过滤特殊字符。 一般情况下开启 gpc 是可以防御很多字符串型的注入&#xff0c;但是如果数据库编码不 对&#xff0c;也可以导致 SQL 防注入绕过&#xff0c;达到注入…

7、Qt--QLabel使用小记

前言&#xff1a;QLabel作为QT中基础的控件&#xff0c;功能简单使用方便频繁&#xff0c;主要用于显示文本、图片等信息。笔者这里记录下一些开发使用心路&#xff0c;方便小白快速入手。 一、添加背景图片 首先需要在资源中添加好图片资源&#xff0c;图片资源的添加参考4.1…

ArcGIS Desktop使用入门(三)图层右键工具——组织要素模板

系列文章目录 ArcGIS Desktop使用入门&#xff08;一&#xff09;软件初认识 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——标准工具 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——编辑器 ArcGIS Desktop使用入门&#xff08;二&#x…

每日一练(力扣)

我的思路是暴力枚举: 情况1:相同&#xff0c;就让子串和原串同时后移继续比较 情况2:不相同&#xff0c;就只让原串后移 public int strStr(String haystack, String needle) {if (haystack.length() < needle.length()){return -1;}for (int i 0; i < h…

PointNet++函数square_distance(src, dst):计算两组点之间的欧式距离(代码详解)

文章目录 一、计算两组点之间的欧式距离二、举例三、中间结果输出 一、计算两组点之间的欧式距离 def square_distance(src, dst):"""Calculate Euclid distance between each two points.src^T * dst xn * xm yn * ym zn * zm&#xff1b;sum(src^2, dim-1…

Qt | 对象树与生命期(对象的创建、销毁、擦查找)

一、组合模式与对象树 1、组合模式指的是把类的对象组织成树形结构,这种树形结构也称为对象树,Qt 使用对象树来管理 QObject 及其子类的对象。注意:这里是指的类的对象而不是类。把类组织成树形结构只需使用简单的继承机制便可实现。 2、使用组合模式的主要作用是可以通过…

库、框架、脚手架和IDE一文讲明白

在区分上面几个问题前&#xff0c;咱们先看看几个疑问。 一、常见问题汇总 js css直接复制到服务器 然后引用不就行了么&#xff1f; 为什么还需要安装&#xff1f; 引入js不就是引入了框架了吗&#xff1f;框架就是js&#xff1f; 脚手架和框架都有架&#xff0c;是不是一…