【数据结构】排序

目录

1、冒泡排序

2、直接插入排序

3、希尔排序

4、选择排序

5、堆排序

6、快速排序

6.1 Hoare版本

6.2 挖坑法

6.3 前后指针法

6.4 快速排序的非递归

7、归并排序

7.1 递归版本

7.2 非递归版本

8、计数排序(非比较排序)

9、各个排序时空复杂度、稳定性比较


假设有一个n个数的乱序数组,排升序为例.

1、冒泡排序

冒泡排序就是从第一个元素开始,将相邻的元素逐个比较,一趟下来就可以将一个元素排到正确的位置,因为是排升序,所以第一趟就是将最大的元素排到正确的位置,然后再进行第二趟,总共需要进行n-1趟,因为将n-1个元素排到正确的位置后,剩下的一个元素也在正确的位置

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

外层循环是要比较的趟数,一个数组有n个元素,则需要比较n-1趟,因为当n-1趟后这n-1个数都在正确的位置了,剩下一个数肯定也在正确的位置 
内层循环是控制每一趟要比较几个数,若数组有n个元素,则每一趟要比较n-1,n-2,n-3,...,1 

会发现实际上并不需要真的到n-1趟,所以用一个flag来记录,如果一趟下来一个数都没交换,说明已经有序,直接出来即可

冒泡排序的时间复杂度

2、直接插入排序

插入排序就是让i从下标为1的地方开始找(因为默认一个数是有序的),让i指向的这个数与他前一个比较,如果无序(即i指向的数更小),用tmp保存i指向的这个数,然后让j从i-1开始,将数组中每个数都往后挪一位,直到找到a[j]小于tmp的,找到时,让a[j+1]的值赋为tmp即可 

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

直接插入排序的时间复杂度

最坏的情况:当数组逆序时,每一趟都要插入到最前面,时间复杂度是O(N^2)

最好的情况:当数组有序或接近有序时,时间复杂度是O(N)

所以会发现,直接插入排序在不同情况下时间复杂度是不同的,当数组接近有序时,时间复杂度的提升很大,那么可不可以先将数组排成接近有序,然后再对数组进行直接插入排序呢?

3、希尔排序

希尔排序就是再直接插入排序的基础上,先将数组进行预排序,将数组排成接近有序后,再对数组进行直接插入排序。

其做法是,首先确定一个整数gap,将数组分成gap个组,所有距离为gap的数分在一个组内,并对每一组内的数进行直接插入排序,一轮完成以后,缩小gap,直到gap缩小到1,这时候就是直接插入排序。

void ShellSort(int* a, int n)
{
	int gap = n;
	int i = 0, j = 0;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (i = gap; i < n; i++)
		{
			if (a[i] < a[i - gap])
			{
				int tmp = a[i];
				for (j = i - gap; j >= 0 && a[j] > tmp; j-=gap)
				{
					a[j + gap] = a[j];
				}
				a[j + gap] = tmp;
			}
		}
	}
}

这里面gap=gap/3+1是比较好的,其中的+1是为了确保gap能够等于1

在gap=gap/3+1的前提下,希尔排序的时间复杂度大约是O(N^1.3)

4、选择排序

选择排序就是遍历数组,选出其中最小的和最大的元素的下标,并与数组首元素和末尾元素交换,然后缩小范围,选出次小和次大的元素的下标,与数组的第二个元素和倒数第二个元素交换

void SelectSort(int* a, int n)
{
	int left = 0, right = n - 1;
	while (left < right)
	{
		int mini = left, maxi = left;
		for (int i = left + 1; i <= right; i++)
		{
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;
		}
		Swap(&a[mini], &a[left]);
		Swap(&a[maxi], &a[right]);
		left++;
		right--;
	}
}

此时为什么会出现这样子的情况呢?

所以需要判断一下maxi和left是否相等,若相等,交换了一次之后,maxi原来的值已经到了mini的位置了,需要将mini赋值给maxi

void SelectSort(int* a, int n)
{
	int left = 0, right = n - 1;
	while (left < right)
	{
		int mini = left, maxi = left;
		for (int i = left + 1; i <= right; i++)
		{
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;
		}
		Swap(&a[mini], &a[left]);
		if (maxi == left)
			maxi = mini;
		Swap(&a[maxi], &a[right]);
		left++;
		right--;
	}
}

选择排序时间复杂度是O(N^2)

5、堆排序

利用大堆的堆顶元素是整棵树最大的,小堆的堆顶元素是整棵树最小的,可以用堆来进行排序.

以建小堆为例,只需要每次都进行建堆,得到堆顶元素,然后将堆顶元素与堆的最末尾的元素进行交换,然后将堆的大小减小1,这样子堆中最小的元素就到了数组的最后面,因为此时堆的大小减小了1,所以最小的元素不会再被挪动,已经到了正确的位置,然后再依次选次小的,直到堆只剩下一个元素。

排升序,建大堆;排降序,建小堆


//前提:左右子树是小堆,因为现在实现的是小堆,如果实现的是大堆,那前提就是大堆
void AdjustDown(HPDataType* a, int n, int root)//向下调整算法的实现
{
	//找出左右孩子中小的哪一个
	int parent = root;
	int child = parent * 2 + 1;//直接默认左孩子小,然后再比较,这样子写会比直接定义一个LeftChild,一个RightChild更好
	while (child < n)//注意此时有一种极端情况可能会导致越界,即此时的结点有左孩子,但没有右孩子,所以第一个if还要加一个判断条件
	{
		//找出左右孩子中小的哪一个
		if (child + 1 < n && a[child + 1] < a[child])//此时若child+1>=n,也就是说这个结点只有左孩子,直接拿左孩子与父亲比较就可以了,若比较了child+1
			//则会造成数组越界
		{
			child++;
		}
		//如果孩子小于父亲就交换
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;//这个时候的child是左孩子
		}
		else//如果孩子都大于父亲,那这个时候就是小堆,直接结束(此时是调到中间就结束了)
		{
			break;
		}
	}
}
void HeapSort(HPDataType* a, int n)
{
	//1、建堆
	//for(int i = n-1;i>=0;i--);这个是把堆中每个数都用一次向下调整算法,但注意,这个的时间复杂度不是n*logn,而是O(n)
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
    //2、将第一个数据与最后面的数据交换
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		//继续向下选次小的
		AdjustDown(a, end, 0);
		end--;
	}
}

堆排序的时间复杂度

建堆的时间复杂度是O(N),

一共有N-1次交换,每次交换会从堆顶开始一次向下调整,所以时间复杂度是O(N*logN)

所以堆排序的时间复杂度是O(N*logN)

6、快速排序

6.1 Hoare版本

任取待排序数组中的某一个元素为基准值(一般选取数组第一个元素,这里以选取第一个元素为基准值为例),让right先从右边开始走,找到一个比基准值大的元素,停下,再让left从左边开始走,找到一个比基准值小的元素,停下,让left和right所指向的值交换,再重复上诉步骤,直到left与right相等,然后就将相遇位置的数与数组第一个元素交换(为什么相遇位置一定比基准值小?),结束后,基准值就到了它应该在的位置,再递归基准值的左子数组和右子数组,直到数组都只剩下一个元素,排序完成。

误区一

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left, end = right;
	int key = a[left];
	while (begin < end)
	{
		if (a[end] > a[keyi])
			end--;
		if (a[begin] < a[keyi])
			begin++;
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], key);
	QuickSort(a, left, begin - 1);
	QuickSort(a, begin + 1, right);
}

此时是将数组首元素的值赋值给了key,最后并没有真正的交换相遇的元素和第一个元素

修改后

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left, end = right;
	int keyi = left;
	while (begin < end)
	{
		if ( a[end] > a[keyi])
			end--;
		if ( a[begin] < a[keyi])
			begin++;
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[keyi]);
	QuickSort(a, left, begin - 1);
	QuickSort(a, begin + 1, right);
}

误区二

此时是找比基准值大于或等于的元素,但是倘若左右都有与基准值相等的元素,那么就会进入死循环,因为交换后依然没有改变

改正的方法为,将找基准值改为单纯找大于或小于,若等于,直接跳过,因为往下递归的过程中依然可以排号

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left, end = right;
	int keyi = left;
	while (begin < end)
	{
		if ( a[end] >= a[keyi])
			end--;
		if ( a[begin] <= a[keyi])
			begin++;
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[keyi]);
	QuickSort(a, left, begin - 1);
	QuickSort(a, begin + 1, right);
}

误区三

此时仍然会有错误,当基准值就是整个数组最小的元素的时候,end从右边开始找,会一直减到小于0,造成错误,所以应该要判断一下,当end<=begin时,就没必要再减了,此时也能保证出循环后begin和end在同一个位置

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int begin = left, end = right;
	int keyi = left;
	while (begin < end)
	{
		if (begin < end && a[end] >= a[keyi])
			end--;
		if (begin < end && a[begin] <= a[keyi])
			begin++;
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[keyi]);
	QuickSort(a, left, begin - 1);
	QuickSort(a, begin + 1, right);
}

为什么相遇的位置一定比基准值小?

注意上面的前提左边做基准值,所以是右边先走

若让右边做基准值,则需要让左边先走,才能保证相遇点比key大

快速排序的时间复杂度是多少?

在前面,我们选取基准值都是选取数组的第一个或最后一个,这个时候没办法判断选取到的基准值是否是数组中最小的,有什么办法可以优化呢?

快速排序的优化一(随机数法)

可以在数组中随机选取一个数,然后把这个数换到数组开头,再让数组开头的数作为key

void QuickSort(int* a, int left, int right)//Hoare法
{
	if (left >= right)
		return;
	int begin = left, end = right;
	//随机选key
	int randi = rand() % (right - left + 1);
	randi += begin;
	Swap(&a[left], &a[randi]);
	int keyi = left;
	while (begin < end)
	{
		if (begin < end && a[end] >= a[keyi])
			end--;
		if (begin < end && a[begin] <= a[keyi])
			begin++;
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[keyi]);
	QuickSort(a, left, begin - 1);
	QuickSort(a, begin + 1, right);
}

快速排序的优化二(三数取中法)

可以在a[left],a[right],a[mid]中选取大小在中间的数作为key,其中mid=(left+right)/2

int GetMidi(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left] > a[right])
			return left;
		else
			return right;
	}
	else
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[left] < a[right])
			return left;
		else
			return right;
	}
}
void QuickSort(int* a, int left, int right)//Hoare法
{
	if (left >= right)
		return;
	int begin = left, end = right;
	//三数取中
	int midi = GetMidi(a, left, right);
	Swap(&a[left], &a[midi]);
	int keyi = left;
	while (begin < end)
	{
		if (begin < end && a[end] >= a[keyi])
			end--;
		if (begin < end && a[begin] <= a[keyi])
			begin++;
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[keyi]);
	QuickSort(a, left, begin - 1);
	QuickSort(a, begin + 1, right);

}

此时若数组有序,直接变成最好

快速排序的优化三(小区间优化)

快速排序中会使用到递归,但是底层的递归有损耗很多的空间,所以当数据较多时,可以往下递归,当递归到数据较少时,没必要往下递归,可以直接使用插入排序来对剩余元素进行排序,以减少递归层数太多带来的损耗

void QuickSort1(int* a, int left, int right)//Hoare法
{
	if (left >= right)
		return;
	//小区间优化:小区间选择走插入,可以减少90%左右的递归
	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	else
	{
		int begin = left, end = right;
		//三数取中
		int midi = GetMidi(a, left, right);
		Swap(&a[left], &a[midi]);
		int keyi = left;
		while (begin < end)
		{
			if (begin < end && a[end] >= a[keyi])
				end--;
			if (begin < end && a[begin] <= a[keyi])
				begin++;
			Swap(&a[begin], &a[end]);
		}
		Swap(&a[begin], &a[keyi]);
		QuickSort1(a, left, begin - 1);
		QuickSort1(a, begin + 1, right);
	}
}

因为快速排序使用了这些优化之后,一定不会出现最坏的情况,基本上都是最好的情况,所以时间复杂度就是O(N*logN)

6.2 挖坑法

挖坑法就是使用三数取中或随机数法选定一个基准值,并将基准值与数组第一个元素交换,将其保存进变量key中,右边先走,找到比key小的就与左边交换,交换完后左边再走,找到比key大的与右边交换,重复此步骤,直到左右指针相遇,将相遇点的值赋值为key

void QuickSort2(int* a, int left, int right)//挖坑法
{
	if (left >= right)
		return;
	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	else
	{
		//三数取中
		int midi = GetMidi(a, left, right);
		Swap(&a[left], &a[midi]);
		int key = a[left];
		int begin = left, end = right;
		while (begin < end)
		{
			while (begin < end && a[end] >= key)
			{
				end--;
			}
			if (begin < end)
				a[begin] = a[end];
			while (begin < end && a[begin] <= key)
			{
				begin++;
			}
			if (begin < end)
				a[end] = a[begin];
		}
		a[begin] = key;
		QuickSort2(a, left, begin - 1);
		QuickSort2(a, begin + 1, right);
	}
}

6.3 前后指针法

前后指针法就是使用三数取中或随机数法选定一个基准值,并将基准值与数组第一个元素交换。初始时让prev指向数组第一个元素,cur指向数组第二个元素。当cur指向的元素大于等于基准值时,++cur,当小于时,++prev,交换prev和cur的值,++cur。直到cur>right,出循环后再将数组首元素与prev指向的值交换

void QuickSort3(int* a, int left, int right)//前后指针法
{
	if (left >= right)
		return;
	if (right - left + 1 < 10)
	{
		InsertSort(a + left, right - left + 1);
	}
	else
	{
		//三数取中
		int midi = GetMidi(a, left, right);
		Swap(&a[left], &a[midi]);
		int keyi = left, prev = left, cur = left + 1;
		while (cur <= right)
		{
			if (a[cur] < a[keyi])
			{
				++prev;
				Swap(&a[prev], &a[cur]);
			}
			++cur;
		}
		Swap(&a[keyi], &a[prev]);
		QuickSort3(a, left, prev - 1);
		QuickSort3(a, prev + 1, right);
	}
}

6.4 快速排序的非递归

快速排序的非递归需要利用栈后进先出的特点,首先将一组left和right放入栈中,只要栈不为空,就将栈顶的一组left和right取出,将这一组left和right之间的数组利用前面的快速排序方法排序,然后将被排序好的那个位置的左右半边的两组left和right分别放入栈,若左右半边都只有一个数则不用进栈,重复上诉过程,直到栈为空

本质上就是利用栈来保存left和right,类似于递归一样将左右半区往栈中放,再一组一组取出,然后将取出的这一组进行快速排序

void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);
	while (!StackEmpty(&st))
	{
		int begin = StackTop(&st);
		StackPop(&st);
		int end = StackTop(&st);
		StackPop(&st);
		//单趟
		if (end - begin + 1 < 10)
		{
			InsertSort(a + begin, end - begin + 1);
		}
		else
		{
			//三数取中
			int midi = GetMidi(a, begin, end);
			Swap(&a[begin], &a[midi]);
			int keyi = begin, prev = begin, cur = begin + 1;
			while (cur <= end)
			{
				if (a[cur] < a[keyi])
				{
					++prev;
					Swap(&a[prev], &a[cur]);
				}
				++cur;
			}
			Swap(&a[keyi], &a[prev]);
			//[begin,prev-1]prev[prev+1,end]
			if (prev + 1 < end)
			{
				StackPush(&st, end);
				StackPush(&st, prev+1);
			}
			if (begin < prev - 1)
			{
				StackPush(&st, prev - 1);
				StackPush(&st, begin);
			}
		}
	}
	StackDestory(&st);
}

7、归并排序

7.1 递归版本

归并排序就是将数组有序的两部分合并在一起的排序。一个数组先将其往下分解,直到都只有一个,因为一个被认为是有序,需要创建一个临时数组,用于临时保存排序好的元素,最后再复制回原数组。当两组中的数都是有序时,定义begin1,end1,begin2,end2分别指向第一组、第二组的开头和结尾,只要begin1<=end1并且begin2<=end2就一直循环,将begin1和begin2指向的数中小的哪一个往tmp数组中尾插,出循环后,再将begin!=end的那一组往tmp中尾插,此时tmp数组中,[begin,end]的区间内的数组就是原先两组合并后的有序数组,结合在一起以后,新的更多元素的有序数组,再将tmp中[begin,end]区间内的数据拷贝回原数组中,就是实现了原数组的部分排序完成,继续往后排,则可完成整个数组的排序。

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin == end)
		return;
	int mid = (begin + end) / 2;
	//[begin,mid]  [mid+1,end]
	_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;
	//依次比较,取小的往tmp尾插
	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 + 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");
		return;
	}
	_MergeSort(a, 0, n - 1, tmp);
}

归并排序时间复杂度的分析

7.2 非递归版本

在快速排序的非递归实现中,我们使用了栈,但是在归并排序的非递归实现中,只使用一个栈是没办法实现的,因为我们在把数组分解时已经把下标从栈中pop了,在要合并时已经找不到下标了,此时使用两个栈是可以的,但是这样过于麻烦,可以直接使用循环

此时任然开辟一个tmp数组,定义一个gap,gap是1组待归并的数据个数,一次归并两组gap个的数据,gap从1开始,每次gap*2,直到大于n就结束。内层的j表示每一轮第一组的起始位置.

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		for (int j = 0; j < n; j += 2 * gap)//j表示每一轮第一组的起始位置,所以是+=2*gap
		{
			int begin1 = j, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			int i = j;
			// 依次比较,取小的尾插tmp数组
			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 + j, tmp + j, sizeof(int) * 2 * gap);
		}
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

此时只能针对于数组元素个数是2的n次方的情况,如果不是,会造成数组越界.

因为begin1是等于j的,所以begin1不可能越界,所以此时只需处理另外三个越界的情况即可。

end1越界:这一小组就不归并了,因为begin1到n-1是有序的

begin2越界:这一小组就不归并了,因为此时begin1到end1是有序的

end2越界:此时仍需要归并,因为[begin1,end1] [begin2,n-1]这两组不一定有序

此时完善了越界问题后,下面的memcpy拷贝一次的字节数也需要修改,因为若这三个其中之一被修改了,那么此时这两组之间的数据个数就不再是2*gap了

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			int begin1 = j, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			// 越界的问题处理
			if (end1 >= n || begin2 >= n)
				break;
			if (end2 >= n)
				end2 = n - 1;
			int i = j;
			// 依次比较,取小的尾插tmp数组
			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 + j, tmp + j, sizeof(int) * (end2 - j + 1));
		}
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}

8、计数排序(非比较排序)

计数排序就是开辟一个临时数组统计元素出现的个数,然后再放回原数组的排序方法。

优势:计数排序适用于数据较为集中的数组排序。

局限性:只适用整型的排序

void CountSort(int* a, int n)
{
	int min = a[0], max = 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* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		return;
	}
	memset(count, 0, sizeof(int) * range);//将count的所有值都初始化为0
	// 统计次数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
	// 排序
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			a[j++] = i + min;
		}
	}
}

计数排序的时间复杂度分析

计数排序只需遍历一遍数组,统计元素出现的次数,所有时间复杂度是O(N)

9、各个排序时空复杂度、稳定性比较

直接上结论

稳定性:数组中数值相同的值,排好序后相对位置会不会改变,不改变则稳定,反之亦然。

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值