排序 | 冒泡插入希尔选择堆快排归并计数排序

news2024/12/26 22:24:54

排序 | 冒泡插入希尔选择堆快排归并计数排序

文章目录

  • 排序 | 冒泡插入希尔选择堆快排归并计数排序
    • 冒泡排序
    • 插入排序
    • 希尔排序
    • 选择排序
    • 堆排序
    • 快速排序--交换排序
      • 三数取中
      • 快速排序hoare版本
      • 快速排序挖坑法
      • 快速排序前后指针法
    • 快速排序--非递归实现
    • 归并排序
    • 归并排序非递归实现
    • 非比较排序【计数排序】
    • 排序算法复杂度及稳定性分析

我们需要实现的一些功能:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
#include<time.h>
// 打印
void Print_a(int* a, int sz);

// 交换
void Swap(int* p1, int* p2);

// 插入排序
void InsertSort(int* a, int n);

// 冒泡排序
void BubbleSort(int* a, int n);

// 希尔排序
void ShellSort(int* a, int n);

// 堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);

// 选择排序
void SelectSort(int* a, int n);


// ------------------------------------------------------

// 快速排序hoare版本 
int PartSort1(int* a, int begin, int end);

// 快速排序挖坑法
int PartSort2(int* a, int begin, int end);

// 快速排序前后指针法 
int PartSort3(int* a, int begin, int end);

// 排序函数
void QuickSort(int* a, int begin, int end);

//------------------------------------------------------

// 快速排序 非递归实现 
void QuickSortNonR(int* a, int begin, int end);

// 归并排序递归实现 
void MergeSort(int* a, int n);


// 归并排序非递归实现 
void MergeSortNonR(int* a, int n);

// 非比较排序
void CountSort(int* a, int n);

冒泡排序

  • 冒泡排序是一种基本的排序算法,其核心思想是通过多次交换相邻元素的位置,使得每一轮循环都将最大(或最小)的元素移动到序列的最后。这个过程就像气泡逐渐上升到表面一样,因而得名"冒泡排序"。

在这里插入图片描述

在这里插入图片描述
代码实现:

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

优点:

  1. 简单易懂: 冒泡排序的思想非常简单,容易理解和实现,适合初学者学习排序算法的基本概念。
  2. 原地排序: 冒泡排序是一种原地排序算法,不需要额外的空间来存储临时数据,只需要一个常数级的辅助空间。
  3. 稳定性: 冒泡排序是一种稳定的排序算法,相等元素的相对位置不会发生变化。

缺点:

  1. 效率低: 冒泡排序的平均时间复杂度为O(n^2),在处理大规模数据时性能较差,比较和交换的操作太过频繁。
  2. 不适合大规模数据: 冒泡排序的性能不如一些更高效的排序算法,如快速排序、归并排序等,特别是在数据规模较大的情况下。
  3. 对基本有序的序列效率低下: 在实际应用中,如果序列已经基本有序,冒泡排序仍然需要进行多次比较和交换,效率不高。

插入排序

  • 插入排序是一种简单直观的排序算法,其核心思想是将一个元素插入到已经排好序的数组(或子数组)中的合适位置,以达到整体有序的效果。插入排序的工作方式类似于整理扑克牌的过程:手里的牌是已经有序的部分,新摸到的牌则需要插入到适当的位置,保持有序性。

在这里插入图片描述

在这里插入图片描述
代码实现:

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		// 记录每次i的位置
		int end = i;
		// 将i+1的位置保存
		int tmp = a[end + 1];
		// 一次排序
		while (end >= 0)
		{
			// 如果后面的数字小于前面的那一个就进行往后覆盖,
			// 然后end--,继续排序
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				// 如果大于或者等于了就跳出
				break;
			}
		}
		// 因为--end了,所以就要在end+1的位置放刚刚保存的值tmp
		a[end + 1] = tmp;
	}
}

优点:

  1. 简单易理解: 插入排序的实现非常简单,易于理解和实现,适用于小规模数据或部分有序的数据。
  2. 稳定性: 插入排序是一种稳定的排序算法,相等元素的相对位置不会发生变化。
  3. 适应性好: 如果数据已经基本有序,插入排序的性能会比较好,因为大部分元素都已经在正确的位置上,只需少量的比较和移动。
  4. 原地排序: 插入排序是一种原地排序算法,不需要额外的空间来存储临时数据。

缺点:

  1. 效率低: 插入排序的平均时间复杂度为O(n^2),在数据规模较大时,性能不如一些更高效的排序算法,如快速排序、归并排序等。
  2. 对大规模数据排序效率低下: 插入排序在处理大规模数据时性能较差,因为它需要大量的比较和移动操作。
  3. 不适合链表结构: 插入排序需要频繁地移动元素,对于链表结构来说,由于不支持随机访问,插入排序效率较低。

希尔排序

  • 希尔排序(Shell Sort)是一种插入排序的改进版本,也称为缩小增量排序。它通过比较相距一定间隔的元素,逐步减小这个间隔,直到间隔为1时完成最后一轮排序。希尔排序的核心思想是先使数组中任意间隔为h的元素有序,然后逐步减小h,最终使整个数组有序。

代码实现:

void ShellSort(int* a, int n)
{
	int gap = n;
	
	// gap > 1时是预排序,目的让他接近有序
	// gap == 1是直接插入排序,目的是让他有序
	while (gap > 1)
	{
		// gap = gap / 2; // log 2 N
		gap = gap / 3 + 1; // log 3 N

		//每次排gap次
		for (int j = 0; j < n - gap; j++)
		{
			//插入排序
			int end = j;
			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;
		}
	}
}

优点:

  1. 相对于插入排序的改进: 希尔排序是插入排序的改进版本,通过引入间隔(gap)的概念,减少了数据的搬移次数,提高了效率。
  2. 适用于中等规模数据: 希尔排序相对于一些简单的排序算法,对中等规模的数据表现较好,比如对于几千甚至几万个元素的排序。
  3. 不稳定性: 相比较于一些稳定排序算法(如归并排序、插入排序),希尔排序的不稳定性可能在某些情况下是一个优势,特别是在排序过程中需要对元素进行位置交换的场景。

缺点:

  1. 不适用于大规模数据: 希尔排序的性能相对较好,但在处理非常大规模数据时,效率可能不如一些更为高级的排序算法,比如快速排序、归并排序等。
  2. 不稳定性: 尽管不稳定性在某些情况下可以是优势,但在某些应用场景下,需要保持相等元素的相对位置不变,这时候希尔排序的不稳定性可能成为一个缺点。

选择排序

  • 选择排序(Selection Sort)是一种简单直观的排序算法,其基本思想是通过不断选择未排序序列中的最小(或最大)元素,将其与未排序序列的第一个元素交换,从而逐步构建有序序列。

在这里插入图片描述

代码实现:

// 选择排序
void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		// 两头找
		int mini = begin, maxi = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			// 如果i的位置比mini小就更新一下
			if (a[i] < a[mini])
			{
				mini = i;
			}
			//如果i的位置比maxi大就更新一下
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
		// 走到这里就说明小的要和左边交换一下
		Swap(&a[mini], &a[begin]);
		// 注意:这里Eugene左边的和maxi相等了要更新一下maxi
		if (begin == maxi)
		{
			maxi = mini;
		}
		// 然后交换maxi和end
		Swap(&a[end], &a[maxi]);
		++begin;
		--end;
	}
}

优点:

  1. 简单易理解: 选择排序的实现非常简单,易于理解和实现,适合初学者学习排序算法的基本概念。
  2. 原地排序: 选择排序是一种原地排序算法,不需要额外的空间来存储临时数据,只需要一个常数级的辅助空间。
  3. 不稳定性: 选择排序是一种不稳定的排序算法,相等元素的相对位置可能发生变化,但在某些情况下,不稳定性可能是一个优势。

缺点:

  1. 效率低: 选择排序的平均时间复杂度为O(n^2),在处理大规模数据时性能较差,由于每次只能确定一个元素的位置,比较和交换的操作过于频繁。
  2. 对基本有序的序列效率低下: 在实际应用中,如果序列已经基本有序,选择排序仍然需要进行大量的比较和交换,效率不高。
  3. 不适合大规模数据: 选择排序的性能不如一些更高效的排序算法,如快速排序、归并排序等,特别是在数据规模较大的情况下。

堆排序

  • 堆排序是一种基于二叉堆数据结构的排序算法。它利用了堆的性质来进行排序,其中堆分为最大堆和最小堆两种类型。在最大堆中,父节点的值大于或等于其子节点的值;在最小堆中,父节点的值小于或等于其子节点的值。

  • 这里在堆排序章节已经讲过了,这里就不细讲了~~

代码实现:

void AdjustDwon(int* a, int n, int root)
{
	int parent = root;
	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;
		}
	}
}

void HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDwon(a, n, i);
	}

	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDwon(a, end, 0);
		--end;
	}
}

优点:

  1. 稳定性: 堆排序是一种不稳定的排序算法,因为在构建初始堆和进行堆调整的过程中可能破坏相同元素的相对顺序。然而,如果对于相同的元素,可以使用索引来保持其相对顺序,就可以避免这个问题。
  2. 原地排序: 堆排序是一种原地排序算法,不需要额外的存储空间来存储待排序的数据,只需要常数级别的辅助空间。
  3. 时间复杂度: 堆排序的平均、最好和最坏情况下的时间复杂度都是 O(n log n),其中 n 是待排序元素的数量。这使得堆排序在大数据集上表现良好。

缺点:

  1. 非自适应性: 堆排序的时间复杂度在各种情况下都是 O(n log n),无论输入数据的初始顺序如何。因此,它对于部分有序的数据或者小规模数据集的排序效率可能不如一些自适应性较强的算法。
  2. 不稳定: 在排序过程中,堆排序可能破坏相同元素的相对顺序,因此是一种不稳定的排序算法。如果对稳定性有要求,可能需要考虑其他算法。
  3. 不适用于链式存储结构: 堆排序通常需要对数组进行直接访问,而不适用于链式存储结构。因此,如果数据结构采用链表形式存储,需要将其转换为数组再进行排序,这可能引入额外的开销。

快速排序–交换排序

三数取中

int GetMidi(int* a, int left, int right)
{
	//int midi = (begin + end) / 2;
	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 (a[left] < a[right])
			return left;
		else
			return right;
	}
}

快速排序hoare版本

  • 快速排序是一种常用的排序算法,Hoare版本是其中一种实现方式,由Tony Hoare提出。

在这里插入图片描述

代码实现:

int PartSort1(int* a, int begin, int end)
{
	// 三数取中
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

	// 要在最左边开始
	int left = begin, right = end;
	int keyi = begin;

	while (left < right)
	{
		// 右边找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}
		// 左边找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		// 找到了,就交换
		Swap(&a[left], &a[right]);
	}

	// 交换的左边的和keyi,然后更新一下keyi
	Swap(&a[left], &a[keyi]);
	return left;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	// 小区间
	if (end - begin + 1 < 10)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort1(a, begin, end);

		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

优点:

  1. 原地排序: 快速排序是一种原地排序算法,不需要额外的空间来存储临时数据,只需要一个常数级的辅助空间。
  2. 平均情况下具有较好的性能: 在平均情况下,快速排序的时间复杂度为O(n log n),这使得它在实践中具有较好的性能。
  3. 适用于大规模数据: 快速排序在处理大规模数据时通常表现良好,尤其是相对于一些平均时间复杂度较高的排序算法而言。
  4. 对基本有序的数据排序效果好: 在一些情况下,快速排序对基本有序的数据排序效果较好。

缺点:

  1. 不稳定性: 快速排序是一种不稳定的排序算法,相等元素的相对位置可能发生变化,如果需要稳定性,可能需要额外的处理。
  2. 对于极端情况的性能: 在最坏情况下,即已经有序的序列,快速排序的时间复杂度为O(n^2),这时性能可能较差。为了避免这种情况,通常会使用一些优化策略,比如随机化选择枢轴。
  3. 对于小规模数据性能较差: 在小规模数据的排序中,快速排序的递归调用会增加额外的开销,性能可能不如一些简单的排序算法,如插入排序。

快速排序挖坑法

  • 快速排序的挖坑法(也称为Lomuto分区方案)是快速排序的一种实现方式。在挖坑法中,选择一个基准元素,通过不断交换元素将数组分成两个部分,左边的部分都小于基准元素,右边的部分都大于基准元素。接着,对左右两个部分分别递归进行同样的操作。

在这里插入图片描述

代码实现:

int PartSort2(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);

	int key = a[begin];
	int holei = begin;

	while (begin < end)
	{
		// 右边找小
		while (begin < end && a[end] >= key)
		{
			--end;
		}

		a[holei] = a[end];
		holei = end;

		// 左边找大
		while (begin < end && a[begin] <= key)
		{
			++begin;
		}
		a[holei] = a[begin];
		holei = begin;
	}

	a[holei] = key;
	return holei;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	// 小区间
	if (end - begin + 1 < 10)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort2(a, begin, end);

		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

优点:

  1. 原地排序: 挖坑法是一种原地排序算法,不需要额外的空间来存储临时数据,只需要一个常数级的辅助空间。
  2. 简单直观: 挖坑法实现相对较简单,容易理解和实现,适用于教学和学习排序算法。

缺点:

  1. 不稳定性: 挖坑法是一种不稳定的排序算法,相等元素的相对位置可能发生变化,如果需要稳定性,可能需要额外的处理。
  2. 最坏情况下的性能: 在最坏情况下,即已经有序的序列,挖坑法的性能可能较差。这时的时间复杂度为O(n^2),因为每次分区只能使序列中的一个元素有序。
  3. 对于小规模数据性能较差: 在小规模数据的排序中,挖坑法的递归调用会增加额外的开销,性能可能不如一些简单的排序算法,如插入排序。

快速排序前后指针法

  • 快速排序的前后指针法(也称为Hoare分区方案)是另一种实现方式。在这个方法中,通过两个指针从数组的两端分别向中间移动,交换不符合排序条件的元素,最终将数组分为两个部分,左边部分小于基准元素,右边部分大于基准元素。

在这里插入图片描述

代码实现:

int PartSort3(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[begin], &a[midi]);

	int prev = begin;
	int cur = prev + 1;
	int keyi = begin;
	while (cur <= end)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[prev], &a[cur]);

		++cur;
	}

	Swap(&a[keyi], &a[prev]);
	keyi = prev;
	return prev;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;

	// 小区间
	if (end - begin + 1 < 10)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort3(a, begin, end);

		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

优点:

  1. 原地排序: 前后指针法是一种原地排序算法,不需要额外的空间来存储临时数据,只需要一个常数级的辅助空间。
  2. 相对较好的性能: 在平均情况下,快速排序的前后指针法具有较好的性能,时间复杂度为O(n log n)。
  3. 不需要额外的空间: 与挖坑法相比,前后指针法在实际的操作中,不需要额外的元素用于填坑,从而减少了一些操作。

缺点:

  1. 不稳定性: 前后指针法是一种不稳定的排序算法,相等元素的相对位置可能发生变化,如果需要稳定性,可能需要额外的处理。
  2. 最坏情况下的性能: 在最坏情况下,即已经有序的序列,前后指针法的性能可能较差。这时的时间复杂度为O(n^2),因为每次分区只能使序列中的一个元素有序。
  3. 对于小规模数据性能较差: 在小规模数据的排序中,前后指针法的递归调用会增加额外的开销,性能可能不如一些简单的排序算法,如插入排序。

快速排序–非递归实现

  • 我们这里使用栈来解决这个问题~~
  • 先入栈,然后再进行分割
  • 注意: 如果是先入右后入左,那么出的时候就要先出左后出右
  • 栈不为空就继续,然后分割排左边和右边

代码实现:

#include"Stack.h"
// 快速排序 非递归实现 
void QuickSortNonR(int* a, int begin, int end)
{
	ST s;
	StackInit(&s);
	// 先入右后入左
	StackPush(&s, end);
	StackPush(&s, begin);

	while (!StackEmpty(&s))
	{
		// 先出左后出右
		int left = StackTop(&s);
		StackPop(&s);
		int right = StackTop(&s);
		StackPop(&s);

		// 排序
		int keyi = PartSort3(a, left, right);
		// [left keyi-1] keyi [keyi+1 right]
		if (left < keyi - 1)
		{
			StackPush(&s, keyi - 1);
			StackPush(&s, left);
		}
		if (keyi + 1 < right)
		{
			StackPush(&s, right);
			StackPush(&s, keyi + 1);
		}
	}

	StackDestroy(&s);
}

归并排序

  • 归并排序(Merge Sort)是一种分治算法,它的基本思想是将待排序的数组分成两个相等大小的子数组,然后分别对这两个子数组进行排序,最后将排序好的子数组合并成一个有序的数组。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现:

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;

	// 分割  // 这里右移一位相当于 /2 
	int mid = (begin + end) >> 1; 
	
	// 递归
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);

	// [begin mid][mid+1 end]  归并
	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[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	// 拷贝回原数组

	for (int i = begin; i <= end; ++i)
	{
		a[i] = tmp[i];
	}

	//memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin) + 1);
}

// 归并排序递归实现 
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail!\n");
		return;
	}

	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

优点:

  1. 稳定性: 归并排序是一种稳定的排序算法,即对于相等的元素,它们在排序后的相对位置保持不变。
  2. 适用于链表: 归并排序对于链表等非随机访问结构的数据也非常有效,因为它不涉及随机访问,只涉及指针操作。
  3. 适用于外部排序: 归并排序在外部排序(需要对外部存储进行排序的情况)中表现良好,因为它可以很容易地通过合并有序的外部文件来实现。
  4. 稳定的时间复杂度: 归并排序的时间复杂度是稳定的,不受输入数据的影响,总是O(n log n)。

缺点:

  1. 额外空间需求: 归并排序需要额外的内存空间来存储临时数组,这使得它的空间复杂度相对较高,是O(n)。
  2. 非原地排序: 归并排序是一种非原地排序算法,即它需要额外的空间来存储临时数组,而不是在原始数组上进行排序。这对于内存受限的情况可能不太理想。
  3. 常数因子较大: 归并排序的常数因子较大,因此在实际应用中可能被一些其他排序算法(如快速排序)超越。

归并排序非递归实现

  • 思想和上面的归并排序差不多~~

代码实现:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);

	int gap = 1; // 每组数据个数
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			// [i, i+gap-1] [i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			// 归并过程中右半区间可能就不存在
			if (begin2 >= n)
				break;

			// 归并过程中右半区间算多了, 修正一下
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			int index = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}

			// 拷贝回去
			for (int j = i; j <= end2; ++j)
			{
				a[j] = tmp[j];
			}
		}
		gap *= 2;
	}

	free(tmp);
}

非比较排序【计数排序】

  • 计数排序(Counting Sort)是一种非比较性的整数排序算法,它通过确定每个元素在输出序列中的位置来实现排序。

在这里插入图片描述

代码实现:

void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)
			max = a[i];
		if (a[i] < min)
			min = a[i];
	}
	int range = max - min + 1;

	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail!\n");
		return;
	}

	memset(count, 0, sizeof(int) * range);
	//统计次数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

	int i = 0;
	for (int j = 0; j < range; j++)
	{
		while (count[j]--)
		{
			a[i++] = j + min;
		}
	}

	free(count);
}

优点:

  1. 线性时间复杂度: 计数排序是一种具有线性时间复杂度的排序算法,其时间复杂度为O(n + k),其中n是输入元素的数量,k是输入范围的大小。在输入范围较小的情况下,计数排序通常比其他O(n log n)的排序算法更快。
  2. 稳定性: 计数排序是一种稳定的排序算法,即相等元素的相对顺序在排序后仍然保持不变。
  3. 适用于整数排序: 计数排序适用于整数排序,尤其是在知道输入范围不太大的情况下。它不依赖于比较操作,因此在某些情况下可能比基于比较的排序算法更高效。

缺点:

  1. 空间复杂度: 计数排序的主要缺点是它需要额外的空间来存储计数数组。如果输入范围很大,可能需要较大的额外空间,这可能导致空间复杂度较高。
  2. 仅适用于整数: 计数排序仅适用于整数排序,因为它依赖于将元素映射到计数数组的索引。对于浮点数或其他数据类型,需要进行额外的转换。
  3. 对输入范围的限制: 计数排序要求输入的元素必须在已知范围内,否则需要进行范围的确定和调整,增加了实现的复杂性。

排序算法复杂度及稳定性分析

在这里插入图片描述

在这里插入图片描述

本期内容就到这里了,感谢大家的收看,欢迎三连~~

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

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

相关文章

「Swift」Xcode多Target创建

前言&#xff1a;我们日常开发中会使用多个环境&#xff0c;如Dev、UAT&#xff0c;每个环境对应的业务功能都不同&#xff0c;但每个环境之间都只存在较小的差异&#xff0c;所以此时可以使用创建多个Target来实现&#xff0c;每个Target对应这个一个App&#xff0c;可以实现一…

mac视频调色 DaVinci Resolve Studio 18 中文 for Mac

DaVinci Resolve Studio 18是一款功能强大、专业可靠的视频编辑软件&#xff0c;适用于各种规模的媒体项目制作。无论是独立制片人还是大型制片公司&#xff0c;都可以借助该软件进行高质量的视频创作和后期制作。 得编辑工作更加高效和灵活。 调色和色彩校正&#xff1a;软件…

【亚马逊云科技】使用Vscode Amazon-Q完成GUI界面粉笔脚本开发

本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 前言 亚马逊云科技-Q&#xff0c;可以快速获得紧迫问题的相关答案&#xff0c;解决问题…

Java研学-MyBatis框架

一 MyBatis框架 1 框架介绍 框架&#xff1a;对基础代码进行封装并提供相应的API&#xff0c;调用API可省去一些代码的编写&#xff0c;从而提高效率。一个好的框架一定是经过测试&#xff0c;自身的功能已经实现&#xff0c;可以完成特定的功能。 2 MyBatis 框架 MyBatis 框…

十四 动手学深度学习v2计算机视觉 ——转置矩阵

文章目录 基本操作填充、步幅和多通道再谈转置卷积不填充&#xff0c;步幅为1填充为p&#xff0c;步幅为1填充为p&#xff0c;步幅为s 基本操作 填充、步幅和多通道 填充&#xff1a; 与常规卷积不同&#xff0c;在转置卷积中&#xff0c;填充被应用于的输出&#xff08;常规卷…

ShellCode注入程序

程序功能是利用NtQueueApcThreadEx注入ShellCode到一个进程中&#xff0c;程序运行后会让你选择模式&#xff0c;按1为普通模式&#xff0c;所需的常规API接口都是使用Windows原本正常的API&#xff1b;在有游戏保护的进程中Windows原本正常的API无法使用&#xff0c;这时候需要…

在windows系统搭建LVGL模拟器(codeblock工程)

1.codeblock准备 下载codeblock(mingw)&#xff0c;安装。可参考网上教程。 2.pc_simulator_win_codeblocks 工程获取 仓库地址&#xff1a;lvgl/lv_port_win_codeblocks: Windows PC simulator project for LVGL embedded GUI Library (github.com) 拉取代码到本地硬盘&…

Fabric使用自己的链码进行测试-go语言

书接前文 Fabric链码部署-go语言 通过上面这篇文章&#xff0c;你可以部署好自己的链码 &#xff08;后面很多命令是否需要修改&#xff0c;都是根据上面这篇文章来的&#xff0c;如果零基础的话建议先看上面这篇&#xff09; 就进行下一步 在测试网络上运行自己的链码 目…

STM32单片机项目实例:基于TouchGFX的智能手表设计(5)硬件驱动层程序设计

STM32单片机项目实例&#xff1a;基于TouchGFX的智能手表设计&#xff08;5&#xff09;硬件驱动层程序设计 目录 一、 概述 二、 新建工程与外设配置 三、 TouchGFX配置 四、 增加TouchGFX关键驱动 一、 概述 本文内容主要进行工程新建&#xff0c;硬件外设的配置以及添加…

《PySpark大数据分析实战》-10.独立集群模式的代码运行

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

03. 医院设置_后端

1、Swagger2 测试工具 编写和维护接口文档是每个程序员的职责&#xff0c;根据Swagger2可以快速帮助我们编写最新的API接口文档&#xff0c;再也不用担心开会前仍忙于整理各种资料了&#xff0c;间接提升了团队开发的沟通效率。 swagger通过注解表明该接口会生成文档&#xf…

CSC公派研究生项目|电气工程在读博士谈丹麦奥尔堡大学联培体会

2023年已近兔尾&#xff0c;很多人已经开始新一年的规划&#xff0c;对于国内在读博士而言&#xff0c;申请国家留学基金委&#xff08;CSC&#xff09;公派研究生项目也开始列入议事日程&#xff0c;然而&#xff0c;如何申请&#xff1f;在国外学习收获如何&#xff1f;本篇知…

IDEA小技巧

目录 1. IDEA自动添加注释 创建类的时候自动添加注释 创建函数、方法的注释 1. IDEA自动添加注释 参考文档&#xff1a;idea java 自动添加文件注释 idea新建类自动注释_mob6454cc73c728的技术博客_51CTO博客 【操作工具】IDEA创建类及已有类添加注释-详细操作_idea设置创建…

【状态机FSM 序列检测 饮料机_2023.12.1】

同步状态机 概念 同步状态机&#xff08;同一脉冲边沿触发&#xff09;&#xff1a;有限个离散状态及某状之间的转移 异步状态机无法综合 分类 Moore状态机 只和状态有关&#xff0c;与输入无关 Mealy状态机 和状态和输入都有关 Mealy型比Moore型少一个状态 结构 由状态寄…

AI日报:OpenAI向新用户重新开放ChatGPT Plus订阅

欢迎订阅专栏 《AI日报》 获取人工智能邻域最新资讯 文章目录 总览Chatgptplus重新开放订阅#暂停原因功能 OpenAI的1000万美元安全人工智能拨款拨款初衷学术捐赠 总览 ChatGPT Plus再次向新用户开放&#xff0c;但目前每三小时限制发送40条消息。 OpenAI还宣布拨款1000万美元…

喜报!Coremail荣获2023信创“大比武”优秀生态融合奖

近期&#xff0c;2023信创“大比武”金融业务创新应用赛道&#xff08;简称金融赛道&#xff09;活动正式落下帷幕。经过赛程的层层考核&#xff0c;中泰证券股份有限公司&#xff08;简称“中泰证券”&#xff09;与Coremail联合组成的“中泰证券CACTER邮件安全保卫队”最终在…

Linux篇:信号

一、信号的概念&#xff1a; ①进程必须识别能够处理信号&#xff0c;信号没有产生&#xff0c;也要具备处理信号的能力---信号的处理能力属于进程内置功能的一部分 ②进程即便是没有收到信号&#xff0c;也能知道哪些信号该怎么处理。 ③当进程真的受到了一个具体的信号的时候…

2021实战面试

1、Rem , em , px , % , vw 之间的区别 PX: px像素&#xff08;Pixel&#xff09;。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。 em: 1,子元素字体大小的em是相对于父元素字体大小 2,元素的width/height/padding/margin用em的话是相对于该元素的font-size rem:1rem是…

【回眸】Tessy 单元测试软件使用指南(三)怎么打桩和指针测试

目录 前言 Tessy 如何进行打桩操作 普通桩 高级桩 手写桩 Tessy单元测试之指针相关测试注意事项 有类型的指针&#xff08;非函数指针&#xff09;&#xff1a; 有类型的函数指针&#xff1a; void 类型的指针&#xff1a; 结语 前言 进行单元测试之后&#xff0c;但凡…

免费且强大卸载软件工具-Geek Uninstaller

Geek Uninstaller是一款用于Windows操作系统的免费卸载软件。它提供了一种比Windows内置卸载工具更彻底的卸载程序的方法。界面简单没有广告&#xff0c;操作也十分的简单。 特点 完全的程序卸载&#xff1a;Geek Uninstaller 被设计为彻底卸载程序&#xff0c;包括删除剩余…