七大排序算法详解【数据结构】

本文详细介绍了多种经典排序算法,包括冒泡排序、选择排序、插入排序等,对比了它们的时间复杂度、空间复杂度及稳定性特点,并提供了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


算法稳定性:

通俗理解------A(1)=80A(2) = 80;

排序前A(1)A(2)前,排序后A(1)还在A(2)前则稳定,否则不稳定

代码中使用到的几个函数和指针:

typedef int(*Compare)(int left, int right);		//函数指针,可灵活选择排序的顺序
int Less(int left, int right)
{
	return left < right;
}

int Greater(int left, int right)
{
	return left > right;
}


void Swap(int *left, int* right)
{
	int tmp = *left;
	*left = *right;
	*right = tmp;
}

冒泡排序:多在数据规模较小的时候使用        时间复杂度O(n^2)   空间复杂度O(1)    稳定排序

思想:

每次循环比较相邻的两个元素的大小,将较大(小)的往后冒,每一趟下来最后一个元素都是最大的,每排一趟,最后的元素下标向前移一位,最终完成排序。

void BubbleSort(int array[], int size, Compare com)
//两两进行比较,将大的放置到后面,第一趟下来最后的值就是最大的值,依次类推,完成排序
{
	int i = 0;
	int j = 0;
	int flag = 0;					//设置标记,优化冒泡
	for (; i < size - 1; i++)		//最后一个元素不需要冒泡
	{
		int flag = 0;
		for (j = 0; j < size - i - 1; j++)
		{
			if (com(array[j], array[j + 1]))
			{
				Swap(&array[j], &array[j + 1]);
				flag = 1;
			}
		}
		if (flag != 1)
		{
			break;
		}
	}
}

选择排序://元素重复较多时间复杂度O(n^2)   空间复杂度O(1)   不稳定

思想:

内层循环找到最大的元素,标记下标,外层循环控制将最大的元素与最后一个位置的元素进行交换,直到有序

void SelectSort(int array[], int size, Compare com)			//初级版本
{
	//先默认的把首位置下标为最小值下标。然后遍历后面的数字,出现比这个值还小的数
	//就把那个数的下标赋给最小值下标。
	//循环遍历直到有序
	int i = 0;
	int j = 0;
	int min = 0;
	for (i = 0; i < size - 1; i++)			
	{
		min = i;							//默认首元素最小
		for (j = i + 1; j < size; j++)
		{
			if (com(array[min], array[j])) //如果有数比m下标中的数还小,就把这个数的下标放到min中
			{
				min = j;
			}
		}
		if (min != i)						//若是同一位置,不需要交换
			Swap(&array[i], &array[min]);
	}
}

void SelectSort3(int array[], int size)		//优化版
{
	int i = 0;
	int maxPos = 0;		//标记最大的
	int minPos = 0;		//标记最小的
	int begin = 0;
	int end = size - 1;
	while (begin < end)
	{
		i = begin;
		minPos = begin;
		maxPos = begin;
		for (; i <= end; i++)		//遍历分别找到最大的和最小的下标
		{
			if (array[i] < array[minPos])
				minPos = i;
			if (array[i] > array[maxPos])
				maxPos = i;
		}
		if (minPos != begin)				//最小的不在最开始的位置
		{
			Swap(&array[minPos], &array[begin]);
		}
		if (maxPos == begin)
		{
			maxPos = minPos;
		}
		if (maxPos != end)					//最大的不在最后的位置
		{
			Swap(&array[maxPos], &array[end]);
		}
		begin++;
		end--;
	}
}

插入排序:// 数据元素接近有序、数据量少时间复杂度O(n^2)空间复杂度O(1)稳定

思想:

默认第1个元素前的元素都已经有序,将之后的元素全部插入到前面

插入方法:

标记待插数,与前面的元素比较,找到插入的位置,将要插入位置的元素全向后搬移,腾出位置插入待插数,依次循环直到有序

void InsertionSort(int array[], int size, Compare com)		//普通版
{
	int i = 0;
	int end = 0;
	int tmp = 0;
	for (i = 1; i < size; i++)
	{
		tmp = array[i];					//待测数
		end = i - 1;						//待测数的前一个数即是有序数的最后一个数
		while(end >= 0 && com(array[end], tmp)) //找到要插入的位置
		{
			array[end + 1] = array[end];	//不是要插入的位置,元素向后搬移
			end--;
		}
		array[end + 1] = tmp;				//插入
	}
}
void BinInsSort(int array[], int size)		//优化版,二分查找法
{
	int i = 0;
	int j = 0;
	int tmp = 0;
	int low = 0;
	int high = 0;
	int mid = 0;
	// 1 2 3 4 5 6 7 8 9 0
	for (i = 1; i < size; i++)
	{
		tmp = array[i];
		low = 0;
		high = i - 1;
		while (low <= high)		// 当up移动到low左侧时,结束循环。注意,此处一定要带有等号,否则排序会失败(第一次排序可验证)
		{
			mid = low + (high - low)/2;		//取中
			if (tmp < array[mid])			//当待插入元素小于中间位置的元素时
			{
				high = mid - 1;				//待插入元素应该插在在前半个表中
			}
			else
			{
				low = mid + 1;
			}
		}
		for (j = i - 1; j >= low; j--)		//从要插入元素的位置开始将元素依次搬移
		{
			array[j + 1] = array[j];
		}
		array[low] = tmp;				//插入
	}
}

希尔排序://数据元素无序、数据量大时间复杂度O(N^1.3)空间复杂度O(1)不稳定

思想:

分组进行插入排序

先定义一个步长序列,依次递减至一,然后按照步长将数据分组,所有距离为步长倍数的数分为一组,在组内对数据进行插入排序,步长序列递减。

void ShellSort(int array[], int size)
{
	int end = 0;
	int tmp = 0;
	int gap = size;
	//生成步长序列
	while(gap > 1)			//gap > 1
	{
		gap = gap / 3 + 1;	//调整步长序列
		int i = gap;
		//插入排序
		for (; i < size; i++)	//i一次走一步直至所有元素都排好序
		{
			tmp = array[i];					//待测数
			end = i - gap;						//有序数列的最后一个元素
			while (end >= 0 && array[end] > tmp) //找到要插入的位置
			{
				array[end + gap] = array[end];	//
				end -= gap;
			}
			array[end + gap] = tmp;				//插入
		}
	}
}

堆排序://数据规模较大时

思想:

先将所有元素构成大根堆将堆顶元素和堆最后一个元素交换,交换后重新调整堆为大根堆依次类推直到有序。

函数分为调整部分和排序部分。

void AdjustHeap(int array[], int size, int root, Compare com)//小堆----->向下调整
{

	int child = root * 2 + 1;			//左孩子 

	while (child < size)
	{
		if (child + 1 < size && com(array[child + 1], array[child]))//找到左右孩子中小的,标记为child
		{
			child += 1;
		}
		if (com(array[child], array[root]))				//判断是否需要交换位置
		{
			Swap(&array[root], &array[child]);
			root = child;
			child = root * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int array[], int size, Compare com)
{
	int end = size - 1;						//标记最后一个结点的下标
	int LastRoot = (size - 2) >> 1;			//最后一个非叶子节点
	while (LastRoot >= 0)					//建堆
	{
		AdjustHeap(array, size, LastRoot, com);
		LastRoot--;
	}
	while (end > 0)							//最后一个节点不交换		//堆的删除
	{
		Swap(&array[0], &array[end]);		//先交换
		AdjustHeap(array, end, 0, com);		//在调整(从end的前一个节点开始调)
		end--;								//在向前移
	}
}

快速排序:数据接近有序时快速排序算法不适用,效率低

标记一个关键码,将比关键码小的元素,向前搬移,将比关键码大的元素向后搬移。

优化:

1. 三值取中确定基准值 (返回中间值的索引)

取第一个元素,最后一个元素,中间的元素判断大小

2. 当区间比较小时,就可以使用插入排序,直接对这个区间进行排序从而有效减少递归次数

Right - left  <  16  -----------------插入排序

int partion(int array[], int left, int right)//双指针相向移动
{
	int key = array[right - 1];
	int begin = left;
	int end = right - 1;
	while (begin < end)
	{
		while (begin < end && array[begin] <= key)//从左往右找最大的//begin<end防止在查找的时候begin越界
			begin++;
		while (begin < end && array[end] >= key)	//从右往左找最小的
			end--;
			//找到一个最大的和最小的
		if (begin < end)		//不在同一位置
		{
			Swap(&array[begin], &array[end]);
		}
	}
	//begin 和 end 走到同一位置 ,若不是最后位置交换
	if (begin != right - 1)
	{
		Swap(&array[begin], &array[right - 1]);
	}
	return begin;
}

int partion2(int array[], int left, int right)	//挖坑法
{
	int key = array[right - 1];
	int begin = left;
	int end = right - 1;
	while (begin < end)
	{
		while (begin < end && array[begin] <= key)//从左往右找最大的//begin<end防止在查找的时候begin越界
			begin++;
		if (begin < end)		//填坑
			array[end] = array[begin];
		while (begin < end && array[end] >= key)	//从右往左找最小的
			end--;
		if (begin < end)
			array[begin] = array[end];
	}
	//begin, end到同一位置
	array[end] = key;	//填最后一个坑
	return begin;
}

int partion3(int array[], int left, int right)//双支指针前移法
{
	int key = array[right - 1];
	int begin = left - 1;//此处不会发生越界问题,并未访问数组-1处的元素
	int end = left;

	//定义两个指针一个begin和end,从左往右遍历,end的值小于基准值时让begin前进一个位置
	//并检测是否与end为同一位置,若不是则交换位置上的值,end就是检测所有数据中比基准值小的值
	//利用begin将其向前挪,循环结束时需交换begin位置的元素和基准值
	while(end < right)//end指向当前元素,每次循环都要往前走,直到遍历完序列
	{
		if (array[end] < key)
		{
			//end处的元素小于key,begin前移,并判断
			begin++;
			if (begin != end)
			{
				Swap(&array[begin], &array[end]);
			}
		}
		end++;
	}

	if (array[++begin] != key)			//此时begin的值一定小于key,begin的下一个元素大于等于key
		//交换begin+1 和最后的元素
		Swap(&array[begin], &array[right - 1]);
	return begin ;
}

void QuickSort(int array[], int left, int right)	//递归版本
{
	int div = 0;
	if (left < right)
	{
		div = partion3(array, left, right);
		QuickSort(array, left, div);
		QuickSort(array, div + 1, right);
	}
	
}
void QickSortByLoop(int array[], int size)		//非递归版本,借助栈
{
	Stack s;
	int left = 0;
	int mid = 0;
	int right = size;
	assert(array);
	if (size <= 1)
	{
		return;
	}
	//用栈保存每一个待排序子串的首尾元素下标,
	//下一次while循环时取出这个范围,对这段子序列进行partion操作  
	InitStack(&s);
	PushStack(&s, left);		//左入栈
	PushStack(&s, right);		//右入栈
	while (!StackEmpty(&s))//栈不为空
	{
		right = TopStack(&s);	//右出栈
		PopStack(&s);
		left = TopStack(&s);	//左出栈
		PopStack(&s);
		if (left < right)
		{
			mid = partion3(array, left, right);
			PushStack(&s, left);
			PushStack(&s, mid);
			PushStack(&s, mid + 1);
			PushStack(&s, right);
		}
	}
}

归并排序: 外部排序,适用于数据量大的情况

假设数组A有N个元素,那么可以看成数组A是又N个有序的子序列组成,每个子序列的长度为1,然后再两两合并,得到了一个 N/2 个长度为2或1的有序子序列,再两两合并,如此重复,值得得到一个长度为N的有序数据序列为止。

合并算法:即合并两个有序的子序列,设置两个指针,p1,p2,将p1,p2所指向元素当中值较小的不断复制到临时空间,最后把没复制完的子序列的剩余yuan复制到临时空间

void _MergeData(int* array, int left, int mid, int right, int* tmp)	//左闭右开区间
{
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid;
	int end2 = right;
	int index = left;
	//两个分组中都有元素时
	while (begin1 < end1 && begin2 < end2)
	{
		if (array[begin1] <= array[begin2])		//将两部分元素中较小的赋值到临时空间中
		{
			tmp[index++] = array[begin1++];
		}
		else
		{
			tmp[index++] = array[begin2++];
		}
	}
	while (begin1 < end1)			//将剩余元素赋值到临时空间中
		tmp[index++] = array[begin1++];
	while (begin2 < end2)
		tmp[index++] = array[begin2++];
}

void _MergeSort(int* array, int left, int right, int* tmp)
{
	//当前分组中只有一个元素时归并结束
	if (right - left > 1)	//递归出口
	{
		int mid = left + ((right - left) >> 1); //取中间值
		_MergeSort(array, left, mid, tmp);	//拆分
		_MergeSort(array, mid, right, tmp);
		_MergeData(array, left, mid, right, tmp);	//归并
		memcpy(array + left, tmp + left, sizeof(array[0])*(right - left));//将当前tmp数组中的元素拷贝回array
	}
}

void MergeSort(int array[], int size)
{
	int *tmp = (int*)malloc(sizeof(array[0])*size);
	if (NULL == tmp)
	{
		return;
	}
	if (size <= 1)
	{
		return;
	}

	_MergeSort(array, 0, size, tmp);
	free(tmp);
}

void MergeSortNor(int array[], int size)
{
	//设置步长从1开始,每次递增二倍
	int gap = 1;
	int i = 0;
	int left = 0;
	int right = size;
	int* tmp = (int*)malloc(sizeof(array[0])*size);
	if (NULL == tmp)
	{
		return;
	}
	while (gap < size)
	{
		int mid = left + ((right - left) >> 1);

		for (i = 0; i < size; i += 2 * gap)
		{
			left = i;
			mid = left + gap;
			if (mid > size)		//mid越界
				mid = size;
			right = mid + gap;
			if (right > size)	//right越界
				right = size;
			_MergeData(array, left, mid, right, tmp);	//数据排序
		}
		memcpy(array, tmp, sizeof(array[0])*size);
		gap *= 2;
	}
	free(tmp);
}
计数排序:适用于数据量大,但是大小比较集中的数据排序。

1.确定数据大小

2.分配适当空间

3.统计数据元素出现次数,将结果保存至临时空间

4.数据回收----将tmp中的数据反向放回array数组中

void CountSort(int array[], int size)
{
	int i = 0;
	int MaxValue = array[0];
	int MinValue = array[0];
	int TmpSize = 0;
	int Index = 0;
	//确定大小
	for (i = 0; i < size; i++)
	{
		if (array[i] < MinValue)
		{
			MinValue = array[i];
		}
		if (array[i] > MaxValue)
		{
			MaxValue = array[i];
		}
	}
	//分配空间
	TmpSize = MaxValue - MinValue + 1;
	int* tmp = (int*)malloc(sizeof(int)* TmpSize);
	if (NULL == tmp)
	{
		return;
	}
	memset(tmp, 0x00, sizeof(int)*TmpSize);
	//统计每个数据个数,并将其个数放置进tmp的指定位置
	for (i = 0; i < size; i++)
	{
		tmp[array[i] - MinValue]++;
	}
	//数据回收---将tmp数组的数据重新放回array
	i = 0;
	while (i < TmpSize)
	{
		while (tmp[i]--)
		{
			array[Index++] = i + MinValue;
		}
		i++;
	}
	free(tmp);
}


基数排序:

LSD:低关键码优先----从数字的低位到高位依次排序

MSD:高关键码优先---从高高位到低位依次排序


LSD:

得到数组的最大的数的位数,从个位开始,将数据依次放入指定的桶中,再将桶中的元素拷贝回原数组,在次对原数组中的数的高一位进行处理。

统计每个桶中有效元素个数

计算每个桶的起始地址

将元素按照当前位置放入桶中

对元素进行回收--------将桶中元素拷回原数组

将桶中的数据拷贝回原数组,则会转化为排序下列数据: 281, 321, 262, 374, 255, 655, 987, 198, 789。再对该组数据按十位进行排序。


将桶中的数据拷贝回原数组,则会转化为排序下列数据:321, 255, 655, 262, 374, 281, 987, 789, 198。再对该组数据按百位进行排序。


最后形成有序数据: 198,255, 262, 281, 321, 374, 655 , 789, 987.

代码:

void _RadixSortLSD(int array[], int size, int* bucket)		//M位  个数N
{
	int i = 0;
	int bitIdx = 0;
	int bitIdxNum = 0;
	int radix = 1;
	bitIdxNum = GetbitNum(array, size);
	for (bitIdx = 0; bitIdx < bitIdxNum; bitIdx++)
	{
		int count[10] = { 0 };		//有效元素个数
		int addrStart[10] = { 0 };	//起始地址
		//统计每个桶中元素的个数
		for (i = 0; i < size; i++)
		{
			count[array[i] / radix % 10]++;
		}
		//计算每个桶的起始地址
		for (i = 1; i < 10; i++)
		{
			addrStart[i] = addrStart[i - 1] + count[i - 1];
		}
		//将元素按照当前位置放入对应的桶中
		for (i = 0; i < size; i++)
		{
			int bucketNo = array[i]/ radix % 10;	//桶号
			bucket[addrStart[bucketNo]++] = array[i];//先利用桶号计算出起始地址,将元素放入该起始地址处
			//然后当前桶的起始地址+1
		}
		//对元素进行回收
		memcpy(array, bucket, sizeof(array[0])* size);
		radix *= 10;
	}
}
void RadixSort(int array[], int size)
{
	if (size < 2)
	{
		printf("数据量小于2,无需排序!\n");
		return;
	}
	int* bucket = (int*)malloc(sizeof(array[0])*size);
	if (NULL == bucket)
	{
		return;
	}
	_RadixSortLSD(array, size, bucket);
	free(bucket);
}
MSD:  此处需借助递归思想,先对将数据按最高位的大小放入指定的桶中,然后对每个桶的低位递归的进行置桶和拷贝操作。


代码:

void _RadixSortMSD(int array[], int left, int right, int* bucket, int bitNum)//高关键码优先,采用递归的方式
{
	int i = 0;
	int radix = pow(10, bitNum);
	int count[10] = { 0 };		//有效元素个数
	int addrStart[10] = { 0 };	//起始地址
		
	if (bitNum < 0)
	{
		return;
	}
	//统计每个桶中元素的个数
	for (i = left; i < right; i++)
	{
		count[array[i] / radix % 10]++;
	}
	//计算每个桶的起始地址
	for (i = 1; i < 10; i++)
	{
		addrStart[i] = addrStart[i - 1] + count[i - 1];
	}
	//将元素按照当前位置放入对应的桶中
	for (i = left; i < right; i++)
	{
		int bucketNo = array[i] / radix % 10;	//桶号
		bucket[addrStart[bucketNo]++] = array[i];//先利用桶号计算出起始地址,将元素放入该起始地址处
		//然后当前桶的起始地址+1
	}
	//对元素进行回收
	memcpy(array+ left, bucket, sizeof(array[0])* (right - left));	//拷贝时拷贝到array的对应位置

	for (i = 0; i < 10; i++)//排每个桶元素的低一位
	{
		int begin = addrStart[i] - count[i];//当前桶的起始地址 = 当前桶的地址 - 当前桶的元素个数
		int end = addrStart[i];
		if (begin + 1 >= end)	//当前桶的元素只有一个或小于1排序下一个桶
		{
			continue;
		}
		_RadixSortMSD(array, begin, end, bucket, bitNum--);
	}
}

void RadixSort(int array[], int size)
{
	if (size < 2)
	{
		printf("数据量小于2,无需排序!\n");
		return;
	}
	int* bucket = (int*)malloc(sizeof(array[0])*size);
	if (NULL == bucket)
	{
		return;
	}
	_RadixSortMSD(array, 0, size, bucket, GetbitNum(array, size) - 1);
	free(bucket);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值