10.排序

一.排序的概念及其运用

二.常见排序算法的实现

1.插入排序

a.直接插入排序

从后往前,一直比较,大就往前走,小就放

cpp 复制代码
// 时间复杂度 O(N^2)
// 最坏情况:逆序
// 最好情况是多少:顺序有序或者接近有序  O(N)
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n-1; i++)
	{
		// [0, end] end+1
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}

		a[end + 1] = tmp;
	}
}

b.希尔排序

一次性跳跃 gap 次,让其变成相对有序的

cpp 复制代码
// 预排序  -- 目标:接近有序  gap > 1
// 插入排序  -- 目标:有序    gap == 1
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//gap /= 2;
		gap = gap/3 + 1;

		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			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;
		}

		//PrintArray(a, n);
	}
}

这里我们的gap / 3 + 1更好,这里就不进行证明了(时间复杂度O(n ^ 1.3) 大致比n * log n略大)

2.选择排序

a.直接选择排序(直接遍历所有的数,选择最小的)

cpp 复制代码
void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		// 选出最小值和最大值的位置
		int mini = begin, maxi = begin;
		for (size_t i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}

			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}

		Swap(&a[begin], &a[mini]);
		if (maxi == begin)
		{
			maxi = mini;
		}

		Swap(&a[end], &a[maxi]);
		++begin;
		--end;

		//PrintArray(a, n);
	}
}

b.堆排序

cpp 复制代码
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(N*logN)
void HeapSort(int* a, int n)
{
	// a数组直接建堆 O(N)
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	// O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

3.交换排序

a.冒泡排序

cpp 复制代码
// 时间复杂度 O(N^2)
// 最好情况是多少:顺序有序或者接近有序  O(N)
void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n-1; j++)
	{
		int exchange = 0;

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

		if (exchange == 0)
			break;
	}
}

n * log n 的排序时间,差距就非常大了

b.快速排序

a.hoare版本

然后我们再排序 左子树 和 右子树 ,等拍好了就全部都排序好了

cpp 复制代码
void QuickSort(int* a, int left, int right)
{
	// 区间只有一个值或者不存在就是最小子问题
	if (left >= right)
		return;

	int begin = left, end = right;

	int keyi = left;
	while (left < right)
	{
		// right先走,找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}

		// left再走,找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);
	keyi = left;

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

所以,右边先走会保证至少是等于key

如果是有序的(接近有序),那么快速排序就很慢,时间复杂度是O(N ^ 2)

随机数选key

cpp 复制代码
void QuickSort(int* a, int left, int right)
{
	if(left >= right)
    {
        return;
    }
    int begin = left,end = right;
	int randi = rand();
	randi %= (right - left);
	randi += left;
	Swap(&a[left],&a[randi]);
    int keyi = left;
    while(left < right)
    {
        while(left < right && a[right] >= a[keyi])
        {
            --right;
        }
        while(left < right && a[left] <= a[keyi])
        {
            ++left;
        }
        Swap(&a[left],&a[right]);
    }
    Swap(&a[left],&a[keyi]);
    keyi = left;
    QuickSort(a,begin,keyi - 1);
    QuickSort(a,keyi + 1,end);
}

三数取中(这个较好)

cpp 复制代码
// 三数取中  left  mid  right
// 大小居中的值,也就是不是最大也不是最小的
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 // a[left] > a[mid]
	{
		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)
{
	if(left >= right)
    {
        return;
    }
    int begin = left,end = right;
	int midi = GetMidi(a,left,right);
	Swap(&a[left],&a[midi]);
    int keyi = left;
    while(left < right)
    {
        while(left < right && a[right] >= a[keyi])
        {
            --right;
        }
        while(left < right && a[left] <= a[keyi])
        {
            ++left;
        }
        Swap(&a[left],&a[right]);
    }
    Swap(&a[left],&a[keyi]);
    keyi = left;
    QuickSort(a,begin,keyi - 1);
    QuickSort(a,keyi + 1,end);
}

两种排序合并实现排序(减少递归调用的栈的消耗)

后面小区间,走我们的插入排序,大区间走快排

cpp 复制代码
void QuickSort1(int* a, int left, int right)
{
	// 区间只有一个值或者不存在就是最小子问题
	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);
		//printf("%d\n", midi);
		Swap(&a[left], &a[midi]);

		int keyi = left;
		while (left < right)
		{
			// right先走,找小
			while (left < right && a[right] >= a[keyi])
			{
				--right;
			}

			// left再走,找大
			while (left < right && a[left] <= a[keyi])
			{
				++left;
			}

			Swap(&a[left], &a[right]);
		}

		Swap(&a[left], &a[keyi]);
		keyi = left;

		// [begin, keyi-1]keyi[keyi+1, end]
		QuickSort1(a, begin, keyi - 1);
		QuickSort1(a, keyi + 1, end);
	}
}
b.挖坑法版本
cpp 复制代码
// 挖坑法划分
int partion(int* array, int begin, int end) {
   int key = array[begin]; // 选取第一个数为基准值
   while (begin < end) {
       while (begin < end && array[end] >= key) // 从后向前找第一个小于基准值的数
           end--;
       array[begin] = array[end]; // 放到begin位置
       while (begin < end && array[begin] <= key) // 从前向后找第一个大于基准值的数
           begin++;
       array[end] = array[begin]; // 放到end的位置
   }
   array[begin] = key; // 将基准值放入该位置
   return begin; // 返回当前基准值的位置,以便递归划分
}
// 快速排序函数
void quicksort(int* array, int begin, int end) {
   if (begin >= end)
       return;
   int keypos = partion(array, begin, end);
   quicksort(array, begin, keypos - 1);
   quicksort(array, keypos + 1, end);
}
c.前后指针版本
cpp 复制代码
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

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

		++cur;
	}

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

	// [left, keyi-1]keyi[keyi+1, right]
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}
d.非递归版本

一般,我们使用递归,消耗的是栈帧(栈空间),而我们使用stack,是在堆空间上开辟空间

cpp 复制代码
void QuickSortNonR(int* a, int left, int right)
{
	ST st;
	STInit(&st);
	STPush(&st, right);
	STPush(&st, left);

	while (!STEmpty(&st))
	{
		int begin = STTop(&st);
		STPop(&st);

		int end = STTop(&st);
		STPop(&st);

		// 单趟
		int keyi = begin;
		int prev = begin;
		int cur = begin + 1;

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

			++cur;
		}
		Swap(&a[keyi], &a[prev]);
		keyi = prev;
		// [begin, keyi-1] keyi [keyi+1, end] 
		if (keyi + 1 < end)
		{
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}
		if (begin < keyi-1)
		{
			STPush(&st, keyi-1);
			STPush(&st, begin);
		}
	}
	STDestroy(&st);
}

4.归并排序

a.递归实现
cpp 复制代码
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);

	free(tmp);
	tmp = NULL;
}
b.非递归实现
cpp 复制代码
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)
	{
		//printf("gap:%d->", gap);
		for (int j = 0; j < n; j += 2 * gap)
		{
			int begin1 = j, end1 = begin1 + gap - 1;
			int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
			//printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

			// 越界的问题处理
			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));
		}

		//printf("\n");

		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

三.非比较排序

cpp 复制代码
// 时间复杂度:O(N+range)
// 空间复杂度:O(range)
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);

	// 统计次数
	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;
		}
	}
}

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

相关推荐
爱写Bug的小孙2 小时前
Tools、MCP 和 Function Calling
开发语言·人工智能·python·ai·ai编程·工具调用
小小Fred2 小时前
FreeRTOS函数prvInitialiseNewTask解析
java·开发语言
快乐的划水a2 小时前
std::thread与pthread关系
c++
_OP_CHEN2 小时前
【算法基础篇】(三十二)动态规划之背包问题扩展:从多重到多维,解锁背包问题全场景
c++·算法·蓝桥杯·动态规划·背包问题·算法竞赛·acm/icpc
利刃大大2 小时前
【JavaSE】十一、Stack && Queue && Deque && PriorityQueue && Map && Set
java·数据结构·优先级队列··哈希表·队列·集合类
小杜的生信筆記2 小时前
基于R语言绘制网络图,新人选手上手
开发语言·r语言·生物信息学·组学
listhi5202 小时前
机械系统运动学与动力学在MATLAB及SimMechanics中的实现方案
人工智能·算法·matlab
fufu03112 小时前
Linux环境下的C语言编程(三十九)
c语言·数据结构·算法·链表
小码哥0682 小时前
家政服务管理-家政服务管理平台-家政服务管理平台源码-家政服务管理平台java代码-基于springboot的家政服务管理平台
java·开发语言·spring boot·家政服务·家政服务平台·家政服务系统·家政服务管理平台源码