【初阶数据结构】11.排序(2)

news2024/11/16 15:49:11

文章目录

    • 2.3 交换排序
      • 2.3.1 冒泡排序
      • 2.3.2 快速排序
        • 2.3.2.1 hoare版本
        • 2.3.2.2 挖坑法
        • 2.3.2.3 lomuto前后指针
        • 2.3.2.4 非递归版本
    • 2.4 归并排序
    • 2.5 测试代码:排序性能对比
    • 2.6 非比较排序
      • 2.6.1 计数排序
  • 3.排序算法复杂度及稳定性分析


2.3 交换排序

交换排序基本思想:

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置

交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动

2.3.1 冒泡排序

前面在算法题中我们已经接触过冒泡排序的思路了,冒泡排序是一种最基础的交换排序。之所以叫做冒泡排序,因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。

90220543959701b95a30f985f38ce559

代码实现:

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

冒泡排序的特性总结

  1. 时间复杂度:O(N2 )
  2. 空间复杂度:O(1)

2.3.2 快速排序

快速排序是Hoare1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

快速排序实现主框架:

//快速排序
void QuickSort(int* a, int left, int right) 
{
    if (left >= right) {
        return;
    }
    //_QuickSort用于按照基准值将区间[left,right)中的元素进行划分
    int meet = _QuickSort(a, left, right);
    QuickSort(a, left, meet - 1);
    QuickSort(a, meet + 1, right);
}

将区间中的元素进行划分的 _QuickSort 方法主要有以下几种实现方式:

2.3.2.1 hoare版本

算法思路 :

  1. 创建左右指针,确定基准值

  2. right从右向左找出比基准值小的数据,left从左向右找出比基准值大的数据,左右指针数据交换,进入下次循环

问题1:为什么跳出循环后right位置的值一定不大于key

left > right 时,即right走到left的左侧,而left扫描过的数据均不大于key,因此right此时指向的数据一定不大于key

f802ebaaee1af75ab282651dcffa92bf

问题2:为什么leftright指定的数据和key值相等时也要交换?

相等的值参与交换确实有一些额外消耗。实际还有各种复杂的场景,假设数组中的数据大量重复时,无法进行有效的分割排序。

a1c57dd3e7292dd607123c05b75540ce

key是基准值


例如:

6 1 2 7 9 3

key指向6,left指向1,right指向3

left从左向右找出比基准值大的数据,right从右向左找出比基准值小的数据,左右指针数据交换,进入下次循环

left指向7,right指向3

交换leftright

left指向的数据7变成数据3,right指向的数据3变成数据7


6 1 2 3 9 7

key指向6,left指向3,right指向7

left从左向右找出比基准值大的数据,right从右向左找出比基准值小的数据,左右指针数据交换,进入下次循环

left指向9,right指向3

此时left>right

left > right的时候,right和基准值key交换

交换keyright的数据

3 1 2 6 9 7

然后返回基准值6

代码实现:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>

//打印
void PrintArr(int* arr, int n);
//快速排序
//hoare版本
void QuickSort(int* arr, int left, int right);

Sort.c

#include"Sort.h"

//打印
void PrintArr(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//交换
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

//快速排序找基准值
int _QuickSort(int* arr, int left, int right)
{
	int keyi = left;
	++left;

	while (left <= right)//left和right相遇的位置的值比基准值要大
	{
		//right找到比基准值小的
		while (left <= right && arr[right] > arr[keyi])
		{
			right--;//right从右往左找比基准值keyi要小的,当前位置不满足就向前一位
		}
		//left找到比基准值大的
		while (left <= right && arr[left] < arr[keyi])
		{
			left++;//left从左往右找比基准值keyi要大的,当前位置不满足就向后一位
		}
		//right和left互相交换
		//因为left找到比基准值大的,right找到比基准值小的
		//left比right小的时候,自然要交换数据
		if (left <= right)
		{
			Swap(&arr[left++], &arr[right--]);
		}
	}
	//left > right的时候,right和基准值keyi交换
	Swap(&arr[keyi], &arr[right]);

	return right;//返回基准值
}

//快速排序
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//[left,right]--->找基准值keyi
	int keyi = _QuickSort(arr, left, right);
	//左子序列:[left,keyi-1]
	QuickSort(arr, left, keyi - 1);
	//右子序列:[keyi+1,right]
	QuickSort(arr, keyi + 1, right);
}

test.c

#include"Sort.h"

int main()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	int n = sizeof(a) / sizeof(int);
	printf("排序前:");
	PrintArr(a, n);

	QuickSort(a, 0, n - 1);

	printf("排序后:");
	PrintArr(a, n);

	return 0;
}

2.3.2.2 挖坑法

思路:

创建左右指针。首先从右向左找出比基准小的数据,找到后立即放入左边坑中,当前位置变为新的坑,然后从左向右找出比基准大的数据,找到后立即放入右边坑中,当前位置变为新的坑,结束循环后将最开始存储的分界值放入当前的坑中,返回当前坑下标(即分界值下标)

7f127d97b2d93319bb37d19159e3094e

代码:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>

//打印
void PrintArr(int* arr, int n);
//快速排序
//挖坑法
void QuickSort(int* arr, int left, int right);

Sort.c

#include"Sort.h"

//打印
void PrintArr(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//快速排序找基准值
//挖坑法
int _QuickSort2(int* arr, int left, int right)
{
	int hole = left;//找到坑位
	int key = arr[hole];//存储第一个坑位的值

	while (left < right)//left = right就跳出循环
	{
		while (left < right && arr[right] > key)//找到arr[right] < key的right
		{
			--right;
		}
		arr[hole] = arr[right];//填坑
		hole = right;//确定新的坑位

		while (left < right && arr[left] < key)//找到arr[left] > key的left
		{
			++left;
		}
		arr[hole] = arr[left];//填坑
		hole = left;//确定新的坑位
	}
	arr[hole] = key;//最后的坑位放上初始值
	return hole;//返回基准值
}

//快速排序
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//[left,right]--->找基准值keyi
	int keyi = _QuickSort(arr, left, right);
	//左子序列:[left,keyi-1]
	QuickSort(arr, left, keyi - 1);
	//右子序列:[keyi+1,right]
	QuickSort(arr, keyi + 1, right);
}

test.c

#include"Sort.h"

int main()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	int n = sizeof(a) / sizeof(int);
	printf("排序前:");
	PrintArr(a, n);

	QuickSort(a, 0, n - 1);

	printf("排序后:");
	PrintArr(a, n);

	return 0;
}

当然这里会有一点小问题的。比如数据是升序的时候,基准值找起来会有点问题。但因为这里是初阶数据结构,所以先不讨论基准值。后面的高阶数据结构里面会讲三数取中


2.3.2.3 lomuto前后指针

创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。

1758c026cc6d45644b6526fe9ac5dd67

思路:

定义两个变量prevcur

cur指向位置的数据和key值比较

arr[cur] < arr[keyi]prev往后走一步并和cur交换

arr[cur] > arr[keyi]cur继续往后

arr[cur]越界后,将key里的值和arr[prev]的值互换。

代码:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>

//打印
void PrintArr(int* arr, int n);
//快速排序
//lomuto前后指针法
void QuickSort(int* arr, int left, int right);

Sort.c

#include"Sort.h"

//打印
void PrintArr(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//交换
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

//快速排序找基准值
//lomuto前后指针法
int _QuickSort3(int* arr, int left, int right)
{
	int prev = left, cur = left + 1;
	int keyi = left;

	while (cur <= right)//cur > right就说明越界了
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)//若arr[cur] < arr[keyi],prev往后走一步并和cur交换
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;//若arr[cur] > arr[keyi],cur继续往后
	}
	Swap(&arr[keyi], &arr[prev]);//arr[cur]越界后,将key里的值和arr[prev]的值互换。

	return prev;//返回基准值
}

//快速排序
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//[left,right]--->找基准值keyi
	int keyi = _QuickSort(arr, left, right);
	//左子序列:[left,keyi-1]
	QuickSort(arr, left, keyi - 1);
	//右子序列:[keyi+1,right]
	QuickSort(arr, keyi + 1, right);
}

test.c

#include"Sort.h"

int main()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	int n = sizeof(a) / sizeof(int);
	printf("排序前:");
	PrintArr(a, n);

	QuickSort(a, 0, n - 1);

	printf("排序后:");
	PrintArr(a, n);

	return 0;
}

快速排序特性总结:

  1. 时间复杂度:O(nlogn)
  2. 空间复杂度:O(logn)

2.3.2.4 非递归版本

非递归版本的快速排序需要借助数据结构:栈

思路:

假设我们初始要排序的数是:5 3 9 6 2 4

经过一轮快速排序后,找到的基准值是6,也就是keyi=3

通过一轮快速排序后,接下来要排序的数是2 3 4 5 6 9


如果left=0,right=5,keyi=3

左区间:[left,keyi-1] [0,2]

右区间:[keyi+1,right] [4,5]

先把右区间的right5入栈,然后把右区间的left4入栈

然后把左区间的right2入栈,然后把左区间的left0入栈


此时栈里面是:0 2 4 5


然后出栈两次,取到left=0,right=2

我们对下标为0-2的数找基准值

找到基准值keyi=0


此时left=0,right=2,keyi=1

左区间:[left,keyi-1] [0,-1] 非有效区间,不入栈

右区间:[keyi+1,right] [1,2]

先把右区间的right2入栈,然后把右区间的left1入栈


此时栈里面是:1 2 4 5


然后出栈两次,取到left=1,right=2

我们对下标为1-2的数找基准值

找到基准值keyi=1


此时left=1,right=2,keyi=1

左区间:[left,keyi-1] [1,0] 非有效区间,不入栈

右区间:[keyi+1,right] [2,2] 非有效区间,不入栈


此时栈里面是:4 5


然后出栈两次,取到left=4,right=5

我们对下标为4-5的数找基准值

找到基准值keyi=4


此时left=4,right=5,keyi=4

左区间:[left,keyi-1] [4,3] 非有效区间,不入栈

右区间:[keyi+1,right] [5,5] 非有效区间,不入栈


此时排完序了,是2 3 4 5 6 9

我们销毁函数栈。

代码实现:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include <stdbool.h>
#include <assert.h>

//打印
void PrintArr(int* arr, int n);
//非递归版本快排
//--借助数据结构--栈
void QuickSortNonR(int* arr, int left, int right);

Sort.c

#include"Sort.h"

//打印
void PrintArr(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//定义栈的结构
typedef int STDataType;
typedef struct Stack
{
	STDataType* arr;
	int capacity;     //栈的空间大小
	int top;          //栈顶
}ST;

//栈的初始化
void STInit(ST* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}

//栈顶---入数据,出数据
//入数据
void StackPush(ST* ps, STDataType x)
{
	assert(ps);

	//1.判断空间是否足够
	if (ps->capacity == ps->top)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
	//空间足够
	ps->arr[ps->top++] = x;
}

//判断栈是否为空
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

//栈顶出数据
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	--ps->top;
}

//取栈顶元素
STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->arr[ps->top - 1];
}

//栈的销毁
void STDestroy(ST* ps)
{
	assert(ps);
	if (ps->arr)
		free(ps->arr);
	ps->arr = NULL;
	ps->top = ps->capacity = 0;
}

//非递归版本快排
//--借助数据结构--栈
void QuickSortNonR(int* arr, int left, int right)
{
	ST st;//创建栈
	STInit(&st);//栈的初始化
	StackPush(&st, right);//栈顶入数据
	StackPush(&st, left);//栈顶入数据

	while (!StackEmpty(&st))//栈不为空就进入循环
	{
		//取栈顶元素---取两次
		int begin = StackTop(&st);
		StackPop(&st);//栈顶出数据

		int end = StackTop(&st);
		StackPop(&st);//栈顶出数据

		//[begin,end]---找基准值
		//双指针法
		int prev = begin;
		int cur = begin + 1;
		int keyi = begin;

		while (cur <= end)
		{
			if (arr[cur] < arr[keyi] && ++prev != cur)//这里<和<=一样
			{
				Swap(&arr[cur], &arr[prev]);
			}
			cur++;
		}
		Swap(&arr[keyi], &arr[prev]);//上面循环结束说明cur越界了

		keyi = prev;
		//根据基准值keyi划分左右区间
		//左区间:[begin,keyi-1]
		//右区间:[keyi+1,end]

		if (keyi + 1 < end)
		{
			StackPush(&st, end);//右区间入栈
			StackPush(&st, keyi + 1);//左区间入栈
		}
		if (keyi - 1 > begin)
		{
			StackPush(&st, keyi - 1);//右区间入栈
			StackPush(&st, begin);//左区间入栈
		}
	}

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

test.c

#include"Sort.h"

int main()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	int n = sizeof(a) / sizeof(int);
	printf("排序前:");
	PrintArr(a, n);

	QuickSortNonR(a, 0, n - 1);

	printf("排序后:");
	PrintArr(a, n);

	return 0;
}

2.4 归并排序

归并排序算法思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

45fedca89a7046dfaa8bd24a4bb1962d

思路:

数据:10 6 7 1 3 9 47个元素,下标0-6


定义left,right,mid三个变量

left指向下标0的元素

right指向下标6的元素

mid=(left+right)/2


[left,mid]:10 6 7 1

[mid+1,right]:3 9 4


然后可以继续像上面这样分,直到全部分成一个一个的数据。

递归的话也是递归到这个时候结束,然后往上返回。

12653da879b1489b83c1bee59b5dfe19

那么,对于10 6这两个数字组成的数组,我们怎么把他排序成6 10呢?


left指向下标0的元素

right指向下标1的元素

mid=(left+right)/2=0


[left,mid]:[0,0] 10

[mid+1,right]:[1,1] 6

其他几个数据也一样

然后我们新创建一个数组来存放这些数:6 10, 1 7, 3 9, 4

然后往上返回。

ff57a163facec37b1c08ffb00046f857

然后我们新创建一个数组来存放这些数:6 10 1 7, 3 9 4

然后往上返回。

4815ea9722df416e7c9556c5d54bcd0e

然后把tmp中的数据拷贝回arr

销毁tmp

代码:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include <stdbool.h>
#include <assert.h>

//打印
void PrintArr(int* arr, int n);
//归并排序
void MergeSort(int* arr, int n);

Sort.c

#include"Sort.h"

//打印
void PrintArr(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//归并排序
void _MergeSort(int* arr, int left, int right, int* tmp)
{
	if (left >= right)//分解到只有一个数据的时候就结束了
	{
		return;
	}
	int mid = (left + right) / 2;//中间下标
	//[left,mid] [mid+1,right]
	//不断二分
	_MergeSort(arr, left, mid, tmp);
	_MergeSort(arr, mid + 1, right, tmp);


	//合并
	//[left,mid] [mid+1,right]
	//表示两个区间
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = begin1;//定义tmp数组的下标

	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])//比较arr[begin1] 和 arr[begin2]的大小
		{
			tmp[index++] = arr[begin1++];//先往tmp里面插入小的
		}
		else {
			tmp[index++] = arr[begin2++];//然后往tmp里面插入大的
		}
	}
	//跳出循环:要么begin1越界  要么begin2越界
	while (begin1 <= end1)//begin2越界,begin1没有越界
	{
		tmp[index++] = arr[begin1++];//继续插入
	}
	while (begin2 <= end2)//begin1越界,begin2没有越界
	{
		tmp[index++] = arr[begin2++];
	}

	//[left,mid] [mid+1,right]
	//把tmp中的数据拷贝回arr中
	for (int i = left; i <= right; i++)
	{
		arr[i] = tmp[i];
	}
}

void MergeSort(int* arr, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);//合并的放在新创建的新数组里,大小是原来数组的空间

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

	free(tmp);//使用完了要释放掉空间
}

test.c

#include"Sort.h"

int main()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	int n = sizeof(a) / sizeof(int);
	printf("排序前:");
	PrintArr(a, n);

	MergeSort(a, n);

	printf("排序后:");
	PrintArr(a, n);


	TestOP();

	return 0;
}

归并排序特性总结:

  1. 时间复杂度:O(nlogn)
  2. 空间复杂度:O(n)

2.5 测试代码:排序性能对比

// 测试排序的性能对比
void TestOP() 
{ 
    srand(time(0)); 
    const int N = 100000; 
    int* a1 = (int*)malloc(sizeof(int)*N); 
    int* a2 = (int*)malloc(sizeof(int)*N); 
    int* a3 = (int*)malloc(sizeof(int)*N); 
    int* a4 = (int*)malloc(sizeof(int)*N); 
    int* a5 = (int*)malloc(sizeof(int)*N); 
    int* a6 = (int*)malloc(sizeof(int)*N); 
    int* a7 = (int*)malloc(sizeof(int)*N); 
    for (int i = 0; i < N; ++i) 
    { 
        a1[i] = rand(); 
        a2[i] = a1[i]; 
        a3[i] = a1[i]; 
        a4[i] = a1[i]; 
        a5[i] = a1[i]; 
        a6[i] = a1[i];
        a7[i] = a1[i]; 
    } 
    int begin1 = clock(); 
    InsertSort(a1, N); 
    int end1 = clock(); 

    int begin2 = clock(); 
    ShellSort(a2, N); 
    int end2 = clock(); 

    int begin3 = clock(); 
    SelectSort(a3, N); 
    int end3 = clock(); 

    int begin4 = clock(); 
    HeapSort(a4, N); 
    int end4 = clock(); 

    int begin5 = clock(); 
    QuickSort(a5, 0, N-1); 
    int end5 = clock(); 

    int begin6 = clock(); 
    MergeSort(a6, N); 
    int end6 = clock(); 

    int begin7 = clock(); 
    BubbleSort(a7, N); 
    int end7 = clock();

    printf("InsertSort:%d\n", end1 - begin1); 
    printf("ShellSort:%d\n", end2 - begin2);
    printf("SelectSort:%d\n", end3 - begin3); 
    printf("HeapSort:%d\n", end4 - begin4); 
    printf("QuickSort:%d\n", end5 - begin5); 
    printf("MergeSort:%d\n", end6 - begin6); 
    printf("BubbleSort:%d\n", end7 - begin7); 

    free(a1); 
    free(a2); 
    free(a3); 
    free(a4); 
    free(a5); 
    free(a6); 
    free(a7); 
} 

对于上面程序的测试结果:(单位:ms

InsertSort:3816
ShellSort:13
SelectSort:4253
HeapSort:15
QuickSort:9
MergeSort:12
BubbleSort:22812

2.6 非比较排序

2.6.1 计数排序

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  1. 统计相同元素出现次数

  2. 根据统计的结果将序列回收到原来的序列中

思路:

例如:{6,1,2,9,4,2,4,1,4}9个数

  1. 统计相同元素出现次数

6出现1次,1出现2次,2出现2次,9出现1次,4出现3

  1. 根据统计的结果将序列回收到原来的序列中

fca1a4c8a3578dc560a5c70715dbf4f6

定义一个i来遍历,i表示数组内元素

i先到0的位置,里面没有数据,不打印

然后到1的位置,里面有2,打印2

依次类推

最后打印结果:1 1 2 2 4 4 4 6 9


新数组的大小怎么确定?

利用原数组中最大的值来确定。创建max+1个空间的数组。

但这就会出现问题:比如有负数怎么办?比较{3, 4, 10000}呢?

如果把负数变成正数,取绝对值呢?也不行。因为正负数绝对值一样的情况下无法区分正负数。

那如果我们把负数统一加一个正数,让他变成整数呢?

我们可以让count=max-min+1

对于{-5,-5,9,5,1}

count=9-(-5)+1=15

创建数组,数组大小为15,下标为0-14

-5放在数组下标为0的位置中,这个位置元素为2

依此类推,下标-5就是原来的元素,这些元素和下标形成了映射关系。

fa7b858e7ef858bc2913b63473724b91

代码:

Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include <assert.h>
#include <stdbool.h>
#include <memory.h>

//打印
void PrintArr(int* arr, int n);

//计数排序
void CountSort(int* arr, int n);

Sort.c

#include "Sort.h"

//打印
void PrintArr(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//计数排序
void CountSort(int* arr, int n)
{
	//根据最大值最小值确定数组大小
	int max = arr[0], min = arr[0];
	for (int i = 1; i < n; i++)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
		if (arr[i] < min)
		{
			min = arr[i];
		}
	}

	int range = max - min + 1;//确定数组元素个数
	int* count = (int*)malloc(sizeof(int) * range);//创建数组
	//判断不为空
	if (count == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	//初始化count数组中所有的数据为0
	memset(count, 0, range * sizeof(int));//count的大小是range

	//例如{100,101,109,105,101,105}
	//min = 100,arr[i] - min就是count里面的下标
	//统计数组中每个数据出现的次数
	for (int i = 0; i < n; i++)
	{
		count[arr[i] - min]++;//这里的++是为了把次数放进去
	}

	//取count中的数据,往arr中放
	int index = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)//这里是为了把上面count对应次数给传进arr
		{
			arr[index++] = i + min;//表示arr数组的下标从0开始,存放数据
		}
	}
}

test.c

#include "Sort.h"

int main()
{
	int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	int n = sizeof(a) / sizeof(int);
	printf("排序前:");
	PrintArr(a, n);

    CountSort(a, n);

	printf("排序后:");
	PrintArr(a, n);

	return 0;
}

计数排序的特性:

计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。 而且只能适用于整数排序,无法对小数排序。

时间复杂度:O(N + range)

空间复杂度:O(range)

稳定性:稳定


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

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

比如:6 2 4 9 1 2

排序后:1 2 2 4 6 9

一开始有两个2,一个在前面,一个在后面。

排序后也有两个2,一个在前面,一个在后面。

如果排序后前面的2还是在后面的2的前面,就称相对次序保持不变。称这种排序算法是稳定的;否则称为不稳定的。

e7b8b43a4d13a606ecdad3ae8021da6a

a07c89bf10f6b75bae782786b19431ff

稳定性验证案例

  1. 直接选择排序:5 8 5 2 9

  2. 希尔排序:5 8 2 5 9

  3. 堆排序:2 2 2 2

  4. 快速排序:5 3 3 4 3 8 9 10 11

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

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

相关文章

2024最新 Navicat Premium 17 简体中文激活版详细安装教程(最简单的激活方式)

一、下载地址 下载链接&#xff1a;分享文件&#xff1a;Navicat Premium 17.0.8 (x64) 中文版.zip 二、安装步骤 1、解压后点击运行navicat170_premium_cs_x64.exe 2、开始安装 3、选择安装路径&#xff0c;最好不要放在系统盘C盘&#xff0c;后面两个步骤默认 4、安装中&a…

【小知识】黑白分明的计算机世界——关系表达式,逻辑表达式和三目运算符

【小知识】黑白分明的计算机世界——关系表达式&#xff0c;逻辑表达式和三目运算符 1.逻辑变量2.关系表达式和逻辑表达式2.1.关系表达式2.1.1.例题——a和b的关系2.1.2.浮点数精度误差 2.2.逻辑表达式2.2.1.常见的逻辑运算符2.2.2.优先级2.2.3.注意事项2.2.3.1.在写逻辑表达式…

书生大模型学习笔记 - 连接云端开发机

申请InternStudio开发机&#xff1a; 这里进去报名参加实战营即可获取 书生大模型实战营 InternStudio平台 创建开发机 SSH连接开发机&#xff1a; SSH免密码登录 本地创建SSH密钥 ssh-keygen -t rsa打开以下文件获取公钥 ~/.ssh/id_rsa.pub去InternStudio添加公钥 …

OPenCV高级编程——OpenCV常见的API及绘图知识详解

目录 引言 一、Mat类详解 1. Mat类的基本结构 2. Mat类的数据类型 3. Mat类的创建与初始化 4. Mat类的使用技巧 二、OpenCV核心功能模块 1. 基本的图像读取与显示 2. 图像的保存 3. 矩阵操作 4. 等待键盘输入与销毁窗口 5. 命名窗口 三、图像处理模块 1. 色彩空间…

一个简单的车辆目标检测和跟踪示例

点击下方卡片&#xff0c;关注“小白玩转Python”公众号 介绍 目标检测&#xff1a;目标检测是指在图像或视频帧中识别和定位特定目标&#xff0c;并使用边界框来确定它们的位置。YOLO&#xff08;You Only Look Once&#xff09;是一种高效的单阶段目标检测算法&#xff0c;以…

普冉Puya 超高性价比M0 MCU 工业电子解决方案

普冉半导体(上海)股份有限公司成立于2016年&#xff0c;总部位于上海张江高科&#xff0c;公司目前主要产品包括微控制器芯片、非易失性存储器芯片及模拟产品。产品广泛应用于物联网、智能手机及周边、可穿戴、服务器、光模块、工业控制、汽车电子、安防等领域。公司在深圳、韩…

Spring Boot集成udp通讯

Spring Boot集成udp通讯 加入依赖编辑配置文件配置相关属性具体业务类客户端调试 加入依赖 <!--加入UDP通信所需依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-integration</artifactId&…

GD32 MCU电源复位和系统复位有什么区别

GD32 MCU的复位分为电源复位和系统复位&#xff0c;电源复位又称为冷复位&#xff0c;相较于系统复位&#xff0c;上电复位更彻底&#xff0c;下面为大家详细介绍上电复位和系统复位的实现以及区别。 电源复位包括上电/掉电复位或者从standby模式唤醒产生的复位&#xff0c;电…

HarmonyOS NEXT——奇妙的调用方式

注解调用一句话总结Extend抽取特定组件样式、事件&#xff0c;可以传递参数Style抽取公共样式、事件&#xff0c;不可以传递参数Builder抽取结构、样式、事件&#xff0c;可以传递参数BuilderParams自定义组件中传递UI组件多个BuilderParams自定义组件中传递多个UI组件 Extend…

echarts加载区域地图,并标注点

效果如下&#xff0c;加载了南海区域的地图&#xff0c;并标注几个气象站点&#xff1b; 1、下载区域地图的JSON&#xff1a;DataV.GeoAtlas地理小工具系列 新建nanhai.json&#xff0c;把下载的JSON数据放进来 说明&#xff1a;如果第二步不打勾&#xff0c;只显示省的名字&a…

全新微软语音合成网页版源码,短视频影视解说配音网页版系统-仿真人语音

源码介绍 最新微软语音合成网页版源码&#xff0c;可以用来给影视解说和短视频配音。它是TTS文本转语言&#xff0c;API接口和PHP源码。 这个微软语音合成接口的源码&#xff0c;超级简单&#xff0c;就几个文件搞定。用的是官方的API&#xff0c;试过了&#xff0c;合成速度…

InnoDB存储引擎(1)

InnoDB存储引擎的优点 InnoDB在设计时考虑到了处理大数据量时的性能&#xff0c;支持事务&#xff0c;回滚和崩溃修复的能力&#xff0c;通过多版本并发控制来减少锁定(降低了锁的争用),同时还支持外键的约束&#xff1b;通过缓冲池在内存中缓存数据来提高查询的性能&#xff…

内容营销专家刘鑫炜:驾驭AI为品牌服务,从成为卓越投喂师开始!

在这个信息爆炸、注意力稀缺的时代&#xff0c;品牌内容营销已成为企业连接消费者、塑造品牌形象的关键途径。而人工智能&#xff08;AI&#xff09;技术的融入&#xff0c;更是为内容营销带来了前所未有的变革与机遇。然而&#xff0c;要让AI真正为你的品牌内容营销高效服务&a…

vue3后台管理系统 vue3+vite+pinia+element-plus+axios上

前言 项目安装与启动 使用vite作为项目脚手架 # pnpm pnpm create vite my-vue-app --template vue安装相应依赖 # sass pnpm i sass # vue-router pnpm i vue-router # element-plus pnpm i element-plus # element-plus/icon pnpm i element-plus/icons-vue安装element-…

WebWorker处理百万数据

Home.vue <template><el-input v-model"Val" style"width: 400px"></el-input><el-button click"imgHandler">过滤</el-button><hr /><canvas id"myCanvas" width"500" height&quo…

不懂就问:EI论文真的很水吗?如何快速水一篇EI论文呢?

最近在有刷到一个这样的话题&#xff0c;发表一篇EI论文容易吗&#xff1f; 很多人可能会觉得EI没有什么用&#xff0c;但其实EI的含金量也很高。 比如目前有很多单位的老师在评选职称的时候&#xff0c;EI会议中的iee系列依然比发表一篇北大核心还高。 那发表EI论文到底容…

springboot家校共育平台-计算机毕业设计源码54235

摘 要 采用高效的SpringBoot框架&#xff0c;家校共育平台为家长与教师提供了便捷的沟通渠道。该平台整合了丰富的教育资源&#xff0c;实现了家校之间的即时信息互通&#xff0c;从而助力协同教育。 为进一步方便用户访问和使用&#xff0c;平台与微信小程序进行了深度整合。家…

WPF ViewBox,Canva之SVG

ViewBox Viewbox是WPF中的一个内容控件&#xff0c;它可以自动调整其内部子元素的大小以适应其自身的尺寸。Viewbox通过保持子元素原有的宽高比&#xff0c;对内容进行均匀的缩放&#xff0c;使其完全填充控件的空间。 Stretch Stretch属性决定了Viewbox如何缩放其内容。它可…

IMU用于野外动作质量评估

近期&#xff0c;来自日本的研究者开发出一个名为MMW-AQA的创新性数据集&#xff0c;该数据集融合了多种传感器信息&#xff0c;专门设计用于用于客观评价人类在复杂环境下的动作质量&#xff0c;这一突破为运动分析和智能安全系统的优化提供了新的可能。 MMW-AQA数据集结合了毫…

MVC软件设计模式及QT的MVC架构

目录 引言 一、MVC思想介绍 1.1 MCV模型概述 1.2 Excel的处理数据 1.3 MVC模式的优势 二、QT中的MVC 1.1 模型&#xff08;Model&#xff09; 1. QAbstractItemModel 2. QStringListModel 3. QStandardItemModel 4. QSqlTableModel 和 QSqlQueryModel 5. QAbstract…