【内排序 -- 八大排序】

news2024/11/13 15:20:37

目录:

  • 前言
  • 算法实现
    • (一)插入排序
      • 1.直接插入排序
      • 2.希尔排序(缩小增量排序)
    • (二)选择排序
      • 1.选择排序
      • 2.堆排序
    • (三)交换排序
      • 冒泡排序
      • 快速排序1(hoare版)
      • 快速排序2(挖坑版)
      • 快速排序3(前后指针版)
      • 优化
        • ①三数取中
        • ②小区间优化
      • 快速排序4(非递归版)
    • (四)归并排序
      • 递归版
      • 非递归版
    • (五)计数排序
  • 三、性能分析
  • 总结

前言

打怪升级:第12天
在这里插入图片描述

排序:所谓排序,就是使一串记录,根据其中某个或某些关键字的大小,递增或递减地排序起来的操作。
下面在实验中我们使用整形数据来进行操作,并且全部排为升序序列。
在这里插入图片描述


算法实现

(一)插入排序

1.直接插入排序

在讲解直接插入排序之前我请大家回想一下,我们大家平时打牌的时候是怎么拼牌的呢?
如果手中有5,6,8,9,那么如果再起到一张7那大多数人都会将7放到6和8之间吧,而这个插入的过程就是我们的直接插入,
当然我们在程序中当然会将细节打磨的更好。
直接插入,到底应该插到那个位置呢?

基本思想

  1. 将待排序数据(aim)插入到有序序列中;
  2. 大于aim的数据都往后移;
  3. 将aim插入到小于等于它的数据之后。

动图模拟
在这里插入图片描述

算法实现

// 插入排序  时间复杂度:O(n^2)   --  稳定排序
void InsertSort(int* a, int n)
{
	for (int i = 1; i < n; i++)  //   需要向前插入的数据下标
	{
		int aim = a[i];   //  保存目标数据
		int j = 0;
		for (j = i - 1; j >= 0; j--)
		{
			if (a[j] > aim)    //  如果a[j] 大于 aim, 就将a[j] 向后移动一位
				a[j + 1] = a[j];
			else               //   否则就是找到了目标位置,结束循环
				break;
		}
							  //   情况0:第一次就不需要移动,此时aim仍然在原位置,也就是 j+1
		a[j + 1] = aim;       //   情况1:找到了合适位置:在下标j之后的位置,也就是 j+1
							 //    情况2:一直循环到 j == -1 的位置,此时应该将aim放到 j = 0 的位置上,也就是 j+1
	}
}

算法总结
5. 元素集合越接近有序,直接插入算法时间效率越高
6. 时间复杂度:O(N^2)
7. 空间复杂度:O(1)
8. 稳定性:稳定


2.希尔排序(缩小增量排序)

基本思想

  1. 选定一个整数gap = n / 2,然后将数据分成gap组;
  2. 所以间隔为gap的数据分在一组,每组数据进行插入排序(预排序);
  3. 令gap /= 2,重复上述操作;
  4. 直到gap=1,此时所有数据分到一组,进行直接插入排序。 (上面所说肯定会让大家有很多疑问,这里说明一点:gap为数据之间的跨度,在直接插入排序中gap就是1,每次都是和前面间隔为1(gap)的数据进行比较,
    而希尔排序会先扩大gap的值,这样就可以将数据更快地移动到合适的位置)

动图模拟
在这里插入图片描述
如动图所示,刚开始位于第四个位置的数据1如果进行直接插入排序需要进行三次比较才可以移动到最前面,
而我们将gap设为3时,数据1直接与和它前面间隔为3的数据进行比较,只进行一次比较就找到了合适的位置。

算法实现

// 希尔排序    --   预排序   不稳定排序
void ShellSort(int* a, int n)
{
	//  确定跨度
	int gap = n / 2;   //  也可写作:gap = n/3 + 1;  保证最后gap能够变为1
	while (gap >= 1)
	{
		 int k = 0;
		 while (k < gap)
		 {
			 for (int i = gap; i < n; i += gap)  //  插入排序
			{
				int cnt = a[i];
				int j = 0;
				for (j = i - gap; j >= 0; j -= gap)
				{
					if (a[j] > cnt)
						a[j + gap] = a[j];
					else
						break;
				}
	
				a[j + gap] = cnt;
			}
				k++;
		}
		gap /= 2;
	}

}



//  由于四层循环不容易控制,这里我们进行了简化,如果大家有兴趣可以画一画图来加深理解
void ShellSort(int* a, int n)
{
	//  确定跨度
	int gap = n / 2;   //  也可写作:gap = n/3 + 1;  保证最后gap能够变为1
	while (gap >= 1)
	{
		// int k = 0;
		// while (k < gap)   //   可合并化简,省去一层循环(表面上)
		// {
			// for (int i = gap; i < n; i += gap)
		for (int i = gap; i < n; i++)
		{
			int cnt = a[i];
			int j = 0;
			for (j = i - gap; j >= 0; j -= gap)
			{
				if (a[j] > cnt)
					a[j + gap] = a[j];
				else
					break;
			}

			a[j + gap] = cnt;
		}
			//	k++;
	//	}
		gap /= 2;
	}

}

这里我们需要注意:希尔排序只要求我们进行预排序,但是并没有规定应该如何进行预排序,换句话说就是:
gap=n/2 可以, gap=n/4+1 可以,gap=gap-1等等亦可以,也就是说只要能够保证最后gap=1就都是可行的,
因此大家不需要纠结“我们只可以将gap设置为gap=n/2吗”的问题,其实这些在专业领域中也是很有争议的。
也因此:希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的
希尔排序的时间复杂度都不固定,这里我们一般取的n^1.3

算法总结

  1. 时间复杂度:O(N^1.3)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

(二)选择排序

1.选择排序

基本思想

  1. 遍历数据,找到最大数,并记录最大数据的下标;
  2. 将最大值换到最后;
  3. i++,重复上述操作

动图模拟
在这里插入图片描述

算法实现

// 选择排序 - 1     每次选择一个最大数放到最后
void SelectSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int sub = 0;  //  记录最大值下标

		for (int j = 1; j < n - i; j++)
		{
			if (a[j] > a[sub])
				sub = j;
		}

		Swap(a + sub, a + n - 1 - i);
	}
}


// 选择排序 - 2  优化 :一次找两个值
void SelectSort(int* a, int n)
{
	for (int i = 0; i < n / 2; i++)      //   注意 i ,j 的取值范围的改变
	{
		int lsub = i;   //   最小值下标
		int rsub = i;   //   最大值下标

		for (int j = i + 1; j < n - i; j++)
		{
			if (a[j] < a[lsub])
				lsub = j;
			if (a[j] > a[rsub])
				rsub = j;
		}

		if (rsub == i)  //  最大值位于第一个位置,那么下面在将最小值换到第一个位置的时候会将最大值换到lsub的位置,
			rsub = lsub;  //  因此需要更新rsub下标
		Swap(a + i, a + lsub);                     //  交换函数请自行实现
		Swap(a + n - 1 - i, a + rsub);
	}
}

算法总结

  1. 无论数据是否有序,选择排序的时间复杂度都为O(N^2),因此在现实生活中很少使用。
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

2.堆排序

基本思想

  1. 利用完全二叉树的特性,每次从最后一个父亲节点开始与孩子节点进行比较,将最大值换到根节点;(向下调整)
  2. 将最大值换到最后;
  3. 经过了上一步的交换,根节点可能已经不是最大值,就需要将根节点从新与孩子节点进行比较,将最大值换到根节点;
  4. 重复2,3两个步骤。

建堆
在这里插入图片描述在这里插入图片描述


算法实现

typedef int HPDaataTyep;
typedef struct Heap
{
	HPDaataTyep* a;
	int size;
	int capacity;
}Heap;

// 堆排序   --升序排序建大堆
void AdjustDown(int* a, int n, int root)
{
	int parent = root;
	int child = root * 2;

	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;
		}
		else
		{
			break;
		}

	}
}

void HeapSort(int* a, int n)
{
	Heap hp;
	hp.a = a;
	hp.size = hp.capacity = n;
	for (int i = n / 2; i >= 0; i--)  // 从第一个非叶子节点开始向下调整建堆
	{
		AdjustDown(a, n, i);
	}

	while (hp.size > 1)  //  取出最大数据,并对第一个数据向下调整
	{
		Swap(hp.a, hp.a + hp.size - 1);
		hp.size--;
		AdjustDown(hp.a, hp.size, 0);
	}
}

算法总结

  1. 无论数据是有序无序甚至是全部相同,堆排序都需要进行建堆和调整,因此时间复杂度恒为O(n*logn)
  2. 时间复杂度:O(n*logn)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

(三)交换排序

冒泡排序

基本思想
命名来自我们的生活经验:气泡越往上浮就变得越大。

  1. 从前往后进行遍历,遍历过程中将当前数与后一个数进行比较,将大数交换到后边;
  2. 如果有n个数据就遍历 n-1次;
  3. 如果在一轮遍历过程中没有进行交换,说明数据已经有序,可以提前结束排序。

算法实现


// 冒泡排序   --  两两交换
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int aim = 1;  //  1为没有进行交换,0为进行了交换
		for (int j = 1; j < n - i; j++)
		{
			if (a[j] < a[j - 1])
			{
				Swap(a + j, a + j - 1);
				aim = 0;    //  本轮进行了交换
			}
		}

		if (aim)  //  有序就结束
			break;
	}
}

算法总结

  1. 冒泡排序的时间复杂度为O(N^2),但是当数据基本有序时采用冒泡排序还是非常快的(接近O(N),但是在平时并不常用,因为插入排序的同样包含这个特性,并且要更好一些)
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

快速排序1(hoare版)

基本思想

动图模拟
在这里插入图片描述
在这里插入图片描述

算法实现

// 快速排序hoare版本      left 与 right 都为下标
int PartSort1(int* a, int left, int right)
{
	int keyi = left;    //  key值

	while (left < right)
	{
		if (a[right] >= a[keyi])
			right--;
		else
			if (a[left] <= a[keyi])
				left++;

		if (a[right] < a[keyi] && a[left] > a[keyi])
			Swap(a + left, a + right);
	}

	Swap(a + right, a + keyi);   //  将key放到正确的位置
	keyi = right;

	return keyi;
}

void QuickSort(int* a, int begin, int end)
{
	if (end <= begin)   //  下标需要合理
		return;

	int keyi = PartSort1(a, begin, end);
	QuickSort(a, begin, keyi - 1);  //  keyi - 1 有可能 小于 begin,下一个亦然,因此上面需要进行判断
	QuickSort(a, keyi + 1, end);

}

算法总结

  1. 通过类似二分的方式进行递归,每次将一个数据放到它应该在的位置,左边是小于等于x的数据,右边是大于等于x的数据。
  2. 时间复杂度:O(n * logn)
  3. 空间复杂度:O(logn) – 递归深度决定
  4. 稳定性:不稳定

快速排序2(挖坑版)

基本思想

  1. 选取序列第一个位置为坑位,保留坑位数据作为目标值,保留坑位下标;
  2. 从右边开始查找,选择小于key值的数据“填坑”,并将原位置作为新的坑位;
  3. 从左边开始查找,选择大于key值的数据“填坑”,并将原位置作为新的坑位;
  4. 重复2,3步操作直至两指针相遇,相遇点肯定是坑位,将key填入坑位;
  5. 此时key左边的都是不大于key值的,右边都是不小于key值的,对左右两边分别重复上述操作。

动图模拟
在这里插入图片描述在这里插入图片描述

算法实现

// 快速排序挖坑法
int PartSort2(int* a, int left , int right )
{

	int key = a[left ];  //  保留数据,留出坑位
	int keyi = left ;    //  坑位

	while (left < right)
	{
		while (left < right && a[right] >= key)
			right--;

		a[keyi] = a[right];  // 执行到这一步的原因:
		keyi = right;  //  1.找到a[right]<key      2.left == right ,此时相遇在坑位,填不填坑没有影响,因此此处不需要再判断 left < right  

		while (left < right && a[left] < key)  //  不加等号:将相等的数据全部移动到侧
			left++;

		a[keyi] = a[left];
		keyi = left;
	}

	a[keyi] = key;

	return keyi;
}

void QuickSort(int* a, int begin, int end)
{
	if (end <= begin)   //  下标需要合理
		return;

	int keyi = PartSort2(a, begin, end);
	QuickSort(a, begin, keyi - 1);  //  keyi - 1 有可能 小于 begin,下一个亦然,因此上面需要进行判断
	QuickSort(a, keyi + 1, end);

}

算法总结

  1. 通过类似二分的方式进行递归,每次将一个数据放到它应该在的位置,左边是小于等于x的数据,右边是大于等于x的数据。
  2. 时间复杂度:O(n * logn)
  3. 空间复杂度:O(logn) – 递归深度决定
  4. 稳定性:不稳定

快速排序3(前后指针版)

基本思想

  1. 初始化两个指针,一个在起始位置,一个比它前一个位置;
  2. 如果fast所指数据大于key(keyi所指数据),slow指针不动,slow指针以及它之前的数据都是不大于key的;
  3. 前面的指针所指数据如果不大于key,并且不等于++slow(说明slow现在所指数据是大于key的)就交换;
  4. 遍历直到fast大于right;

动图模拟
在这里插入图片描述

算法实现

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{

	int keyi = left;     //  对比值
	int slow = left;
	int fast = left+ 1;

	while (++fast <= right)    //  取值区间[begin, end]
	{
	
		if (a[fast] <= a[keyi] && ++slow != fast)
			Swap(a + fast, a + slow);
	}

	Swap(a + keyi, a + slow);  //  slow以及它左边的都是小于key值的,右边的都是大于key值的
	keyi = slow;

	return keyi;
}

void QuickSort(int* a, int begin, int end)
{
	if (end <= begin)   //  下标需要合理
		return;

	int keyi = PartSort3(a, begin, end);
	QuickSort(a, begin, keyi - 1);  //  keyi - 1 有可能 小于 begin,下一个亦然,因此上面需要进行判断
	QuickSort(a, keyi + 1, end);

}

算法总结

  1. 通过类似二分的方式进行递归,每次将一个数据放到它应该在的位置,左边是小于等于x的数据,右边是大于等于x的数据。
  2. 时间复杂度:O(n * logn)
  3. 空间复杂度:O(logn) – 递归深度决定
  4. 稳定性:不稳定

优化

①三数取中

优化原因:如果是逆序序列,使用快排时间复杂度会大大增加。
优化目的:改善对逆序序列的排序(既对降序序列进行升序排序)。
优化方式:
在选择keyi之前,先在区间中对 a[begin] ,a[end],a[(begin+end)/2]三个数进行比较,
选择中间大小的数据作为key值(将它换到begin位置)。
在这里插入图片描述

//  优化1.1 -- 三数取中
int GetMidIndex(int* a, int begin, int end)
{

	int mid = (begin + end) >> 1;
	if (a[begin] > a[mid])
	{
		if (a[mid] >= a[end])
			return mid;
		else  //  a[mid] < a[end]
		{
			if (a[begin] > a[end])
				return end;
			else
				return begin;
		}
	}
	else  //  a[begin] <= a[mid]
	{
		if (a[mid] <= a[end])
			return mid;
		else  //  a[mid] > a[end]
		{
			if (a[begin] > a[end])
				return begin;
			else
				return end;
		}
	}
}

②小区间优化

优化原因:数据量较小时,使用快排递归次数太多,我们可以使用其他排序进行替代,
这里使用的替代排序可以自行选择。
在这里插入图片描述

if (st._end - st._begin + 1 <= 25)   //   熊猫选择插入排序
			InsertSort(a + st._begin, st._end - st._begin + 1);
		else
		{
			。。。。。。。
		}

快速排序4(非递归版)

基本思想
借助栈结构(深度优先遍历),将待排序数组区间入栈(也可以使用队列 – 广度优先遍历)。
算法实现

test.h

// 支持动态增长的栈
typedef struct interval
{
	int _begin;     //  起始和结束位置下标
	int _end;
}STDataType;
typedef struct Stack
{
	STDataType* _a;
	int _top;		// 栈顶
	int _capacity;  // 容量 
}Stack;

// 初始化栈 
void StackInit(Stack* ps);
// 入栈 
void StackPush(Stack* ps, STDataType data);
// 出栈 
void StackPop(Stack* ps);
// 获取栈顶元素 
STDataType StackTop(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
bool StackEmpty(Stack* ps);
// 销毁栈 
void StackDestroy(Stack* ps);

test.c

// 初始化栈 
void StackInit(Stack* ps)
{
	assert(ps);

	ps->_a = (STDataType*)malloc(sizeof(STDataType) * 3);
	ps->_top = 0;
	ps->_capacity = 3;
}
// 入栈 
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);

	if (ps->_top == ps->_capacity)
	{
		STDataType* tmp = realloc(ps->_a, sizeof(STDataType) * ps->_capacity * 2);
		assert(tmp);

		ps->_a = tmp;
		ps->_capacity *= 2;
	}

	ps->_a[ps->_top++] = data;
}
// 出栈 
void StackPop(Stack* ps)
{
	assert(ps);

	if (StackEmpty(ps))
		exit(-1);

	ps->_top--;
}
// 获取栈顶元素 
STDataType StackTop(Stack* ps)
{
	assert(ps);

	if (StackEmpty(ps))
		exit(-1);

	return ps->_a[ps->_top - 1];
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
bool StackEmpty(Stack* ps)
{
	assert(ps);

	return ps->_top == 0;
}
// 销毁栈 
void StackDestroy(Stack* ps)
{
	assert(ps);

	free(ps->_a);
	ps->_a = NULL;
	ps->_top = ps->_capacity = 0;
}

// 快速排序 非递归实现
void QuickSortNonR(int* a, int begin, int end)
{
	Stack S;
	StackInit(&S);
	STDataType st;
	st._begin = begin;
	st._end = end;
	StackPush(&S, st);

	while (!StackEmpty(&S))
	{
		st = StackTop(&S);  //  每次取栈顶数据,排序该区间
		StackPop(&S);

		if (st._end - st._begin + 1 < 15)
			InsertSort(a + st._begin, st._end - st._begin + 1);
		else
		{
			int mid = GetMidIndex(a, st._begin, st._end);
			Swap(a + st._begin, a + mid);

			int keyi = PartSort3(a, st._begin, st._end);
			if (keyi - 1 > begin)
			{
				st._begin = keyi + 1;    // 右区间入栈
				st._end = st._end;
				StackPush(&S, st);
			}
			if (end > keyi + 1)
			{
				st._begin = st._begin;   //  左区间入栈
				st._end = keyi - 1;
				StackPush(&S, st);
			}
		}
	}

	StackDestroy(&S);
}

算法总结

  1. 非递归实现避免了数据量过大而导致的栈溢出。
  2. 时间复杂度:O(N logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

(四)归并排序

递归版

基本思想

  1. 通过不断递归将数组分为一个个单独的数据;
  2. 逆向合并数据,单个数据可以看做有序,合并两个有序数组;
  3. 通过递归的返回,最终可以将所有数据合并为一个有序数组。
  4. 注意:在合并的时候如果在原数组进行合并可能会覆盖数据,因此需要有一个临时数组存放合并的数据,合并结束后再拷贝回原数组。

动图模拟
在这里插入图片描述

算法实现

// 归并排序递归实现    时间复杂度:O(N*logN)    空间复杂度:O(N)  --   分区间操控,
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (end <= begin)
		return;

	int mid = (begin + end) / 2;  //  将该区间分为两部分继续递归拆分
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	int left1 = begin, right1 = mid;
	int left2 = mid + 1, right2 = end; 
	int t = 0;  //  tmp数组下标

	while (left1 <= right1 && left2 <= right2)  //  合并两个有序数组
	{
		if (a[left1] <= a[left2])
			tmp[t++] = a[left1++];
		else
			tmp[t++] = a[left2++];
	}

	while (left1 <= right1)
		tmp[t++] = a[left1++];

	while (left2 <= right2)
		tmp[t++] = a[left2++];  

	memcpy(a + begin, tmp, sizeof(tmp[0]) * t);  // 将排好序的数据拷贝回原数组
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

算法总结

  1. 归并排序通过不断二分,时间复杂度为O(NlogN),而且归并排序不受数据的影响,每次都会递归到最后之后合并,不过需要一个辅助数组来存放数据,因此空间复杂度为O(N)。
  2. 时间复杂度:O(N logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

非递归版

基本思想
手动设置排序空间,这里需要注意下标越界情况!!!

算法实现

// 归并排序非递归实现   --  手动设置排序区间,可以认为是希尔排序的相反思路 -- 熊猫是这么理解的
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);

	int range = 1; // 排序区间中元素个数
	while (range < n)
	{
		for (int i = 0; i < n; i += 2 * range)
		{
			int left1 = i, right1 = left1 + range - 1;
			int left2 = i + range, right2 = left2 + range - 1;
			int t = 0;  //  tmp数组下标

			//  right1 left2 right2 越界
			if (right1 >= n)  
				break;
			else
				if (left2 >= n)
					break;
				else
					if (right2 >= n)
						right2 = n - 1;
			while (left1 <= right1 && left2 <= right2)  //  合并两个有序数组
			{
				if (a[left1] <= a[left2])
					tmp[t++] = a[left1++];
				else
					tmp[t++] = a[left2++];
			}

			while (left1 <= right1)
				tmp[t++] = a[left1++];

			while (left2 <= right2)
				tmp[t++] = a[left2++];

			memcpy(a + i, tmp, sizeof(tmp[0]) * t);  // 将排好序的数据拷贝回原数组  -- 每次排好序都进行拷贝
		}

		range *= 2;
	}
	
	free(tmp);
}

算法总结
2. 时间复杂度:O(N logN)
3. 空间复杂度:O(N)
4. 稳定性:稳定


(五)计数排序

基本思想
不关注数据的值,只关注数据出现的次数。

画图理解
在这里插入图片描述

算法实现

// 计数排序
void CountSort(int* a, int n)
{
	int max = a[0];
	int min = a[0];
	for (int i = 1; i < n; ++i)
	{
		if (a[i] > max)
			max = a[i];
		if (a[i] < min)
			min = a[i];
	}

	int range = max - min + 1; // 空间区间
	int* cntArr = (int*)calloc(range , sizeof(int));
	// 最小数  -- d - min      最大数   --  d  - min
	for (int i = 0; i < n; ++i)  // 统计个数
	{
		++cntArr[a[i] - min];
	}

	int size = 0; // a数组下标
	for (int i = 0; i < range ; ++i)  // 导出排序数组
	{
		while (cntArr[i]--)
		{
			a[size++] = min + i;
		}
	}

	free(cntArr);
}

算法总结

  1. 计数排序适合数据集中的数据,并且,计数排序只适用于整形。
  2. 时间复杂度:O(N+range)
  3. 空间复杂度:O(range)
  4. 稳定性:不稳定

三、性能分析

稳定性:假定在待排序序列中存在多个具有相同关键字的记录,如经过排序后它们的相对位置不变,既a[i] == a[k],
并且a[i]在a[k]之前,排序后a[i]仍然在a[k]之前,就说明排序是稳定的。
上面的插入排序冒泡排序归并排序都是稳定排序,因为他们排序时都是两两交换的。
内排序:数据元素全部放在内存中的排序。
外排序:数据元素太多不能全部放到内存,根据排序过程的要求不能在内外存之间移动数据的排序。
在这里插入图片描述


总结

以上就是常用的八大排序的全部内容,如果有什么疑问或者建议都可以在评论区留言,感谢大家对在这里插入图片描述的支持。

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

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

相关文章

ARM S5PV210的SD卡启动实战

一、S5PV210的SD卡启动实战1 1、任务&#xff1a;大于16KB的bin文件使用 SD 卡启动 (1) 总体思路&#xff1a;将我们的代码分为 2 部分&#xff1a;第一部分 BL1 ≤ 16KB&#xff0c;第二部分为任意大小。 iROM 代码执行完成后&#xff0c;从 SD 卡启动会自动读取 BL1 到 SRA…

多核缓存一致性问题及解决方案MESI协议《深入浅出计算机组成原理》学习笔记 Day 4

系列文章目录 这是本周期内系列打卡文章的所有文章的目录 《Go 并发数据结构和算法实践》学习笔记 Day 1《Go 并发数据结构和算法实践》学习笔记 Day 2《说透芯片》学习笔记 Day 3 文章目录系列文章目录前言一、多核缓存一致性从何而来&#xff08;What&#xff09;二、怎么解…

学习TinyRenderer

1图形学图形学&#xff0c;简单来讲是将3D数据以各个视觉效果呈现在2D画布&#xff08;屏幕&#xff09;上&#xff1b;2 TinyRendererTinyRenderer从零开始实现了一个渲染器&#xff1b;TinyRenderer代码地址&#xff1a;https://github.com/ssloy/tinyrenderer内容介绍在&…

ThreeDXF预览DXF文件集成到vue项目中

由于网上资料都是html的&#xff0c;而自己需要嵌入到vue项目中&#xff0c;查找资料都是在index.html引入script脚本&#xff0c;在写到Vue文件中&#xff0c;但是我尝试过了&#xff0c;各种报错&#xff0c;找不到&#xff0c;window. 根本无法用&#xff0c;于是改注入main…

主动服务再升级!这个品牌引领智慧生活进入“深度体验”

文|智能相对论作者| 佘凯文1月15日&#xff0c;一档央视新闻的新概念科技节目《KU A &#xff01;酷啊未来 | 中国科技创新之夜》正式播出&#xff0c;来自中国科学院的多领域顶级科学家及许多科技企业、青年科研人员代表&#xff0c;共同分享了科技创新之路上的成果和突破。不…

EDI文件处理失败如何汇总?

知行之桥EDI系统在后台自动运行的时候&#xff0c;有时会遇到处理文件失败的情况&#xff0c;导致失败的原因有很多&#xff0c;部分客户希望把处理失败的文件都汇总起来&#xff0c;便于分析失败原因&#xff0c;减少未来再出现类似的错误&#xff0c;同时也能够方便后期排查&…

ERD Online 4.0.7 在线数据库建模、元数据管理(免费、私有部署)

4.0.7❝ feat(erd): 增加新春火红主题feat(erd): 增加团队协作人员进入、退出提示fix(erd): 修复权限配置页面显示混乱doc(erd): 修改更新通告地址❞变化一览 增加新春火红主题 新春主题所有按钮、菜单、元素由原来的蓝色改为火红色修复权限配置页面显示混乱 团队功能增加团队协…

【算法基础】快速排序

目录 一、快速排序核心思想 二、快速排序步骤 (1)暴力做法 (2)双指针做法 三、代码模板 四、边界问题 五、总结 一、快速排序核心思想 分治&#xff0c;即将一个序列划分成左部分小于等于x,右部分大于等于x 二、快速排序步骤 ①确定一个分界点x。分界点可以是左端 a[l]、右…

【Linux】两个故事带你使用git命令行

目录一.历史故事背景经过git的诞生二.git版本管理1.小故事2.理解版本管理三.git的使用1.仓库的创建2.安装git和仓库克隆3.上传代码三板斧addcommitpushgithub和gitee是代码的托管平台&#xff0c;我们上传代码或文件在其中&#xff0c;来管理我们的代码和不同版本软件。 在多人…

【操作系统】——主流的操作系统(带你快速了解)

&#x1f4dc; “作者 久绊A” 专注记录自己所整理的Java、web、sql等&#xff0c;IT技术干货、学习经验、面试资料、刷题记录&#xff0c;以及遇到的问题和解决方案&#xff0c;记录自己成长的点滴。 &#x1f341; 操作系统【带你快速了解】对于电脑来说&#xff0c;如果说…

【Java IO流】字符集使用详解

文章目录前言ASCIIGBKUnicode为什么会出现乱码前言 上一节关于字节流的文章中&#xff0c;在使用字节流读取本地文件中的数据时&#xff0c;文件中只存放了英文&#xff0c;而并没有存放中文数据。我们还提到了不建议使用字节流读取纯文本文件的数据&#xff0c;否则会出现乱码…

Elasticsearch7.8.0版本高级查询—— 匹配查询文档

目录一、初始化文档数据二、匹配查询文档示例2.1、概述2.2、示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; {"name":"张三","age&…

浅谈php原生类的利用 2(ErrorSoapClientSimpleXMLElement)

除了上篇文章浅谈 php原生类的利用 1(文件操作类)_php spl原生类_葫芦娃42的博客-CSDN博客 里提到的原生利用文件操作类读文件的功能&#xff0c;在CTF题目中&#xff0c;还可以利用php原生类来进行XSS,反序列化&#xff0c;SSRF&#xff0c;XXE。 常用内置类&#xff1a; Dire…

【SAP Abap】X档案:SAP Native SQL 简介及实现方式(EXEC SQL、ADBC、AMDP)

SAP Native SQL 简介及实现方式&#xff08;EXEC SQL、ADBC、AMDP&#xff09;1、SAP Open SQL 与 Native SQL 的特点2、Native SQL 的实现方式方式一&#xff1a;Exec SQL&#xff08;1&#xff09;获取单值&#xff08;2&#xff09;获取多行&#xff08;3&#xff09;游标应…

TCP协议的长连接和短连接详解

一 前言TCP在真正开始进行数据传输之前&#xff0c;Server 和 Client 之间必须建立一个连接。当数据传输完成后&#xff0c;双方不再需要这个连接时&#xff0c;就可以释放这个连接。TCP连接的建立是通过三次握手&#xff0c;而连接的释放是通过四次挥手。所以说&#xff0c;每…

【SpringCloud】Eureka的基本原理与使用

【SpringCloud】Eureka的基本原理与使用 一、Eureka-提供者与消费者 【问】如果服务A调用了服务B&#xff0c;而服务B又调用了服务C&#xff0c;服务B的角色是什么&#xff1f; 二、Eureka的结构和作用 什么是Eureka&#xff1f; Eureka 解决服务调用的问题 order-servic…

博物馆3d数字化全景展示设计方案

作为近几年新兴的营销方式&#xff0c;交互式营销能够让消费者对产品从主动感兴趣到互动体验&#xff0c;甚至自主自发传播&#xff0c;达到“在销售中传播&#xff0c;在传播中销售”的目的。进入数字体验经济时代&#xff0c;当3d数字化展示技术遇上传统行业&#xff0c;3d数…

Redis原理篇(三)通信协议

一、RESP协议 1、定义 Redis是一个cs架构的软件&#xff0c;通信一般分两步&#xff1a; 客户端client向服务端server发送一条命令服务端解析并执行命令&#xff0c;返回响应结果给客户端 因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范&#xff0c;这个规…

HashMap源码学习:JDK1.8版本源码解析

文章导航 HashMap源码学习&#xff1a;红黑树原理详解 HashMap源码学习&#xff1a;JDK1.8版本源码解析 目录文章导航前言正文HashMap重要属性HashMap构造方法HashMap扩容方法HashMap链表迁移HashMap红黑树迁移HashMap链表转红黑树HashMap红黑树转链表HashMap添加数据HashMap移…

让你彻底明白Java SPI与SpringBoot自动配置,内附实例代码演示

一、Java SPI的概念和术语 SPI&#xff1a;全称是Service Provider Interface&#xff1a;是一种基于ClassLoader来发现并加载服务的机制 SPI由三个组件构成&#xff1a;Service、Service Provider、ServiceLoader Service&#xff1a;是一个公开的接口或抽象类&#xff0c;定…