常见排序算法介绍和实现


前言

本篇文章将带大家了解另一个数据结构的重要算法:排序。本篇文章将带大家了解一些常见的排序算法,介绍他们的复杂度和代码实现。


一、排序的概念及应用

1.1 排序的概念

所谓排序,就是使一串数据,按照一定的规律,递增或递减的排列起来的操作。

1.2 排序算法的稳定性

假定再待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变。

即,排序前排在前面的排序后还排在前面。如果满足,则称这种排序算法是稳定的,否则就是不稳定的。

1.3 外部排序和内部排序

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

例:归并排序

二,常见的排序算法

在这里插入图片描述

2.1插入排序

2.1.1插入排序基本思想

直接插入排序是一种简单的排序算法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序数列。

2.1.2 直接插入排序的特性

  • 1.元素越接近有序,直接插入排序算法的时间效率越高
  • 2.时间复杂度O(N^2)
  • 3.空间复杂度O(1)
  • 4.是一种稳定的排序算法
  • 5.适合接近有序(接近有序是指小的数据尽量靠前,大的数据尽量靠后,不大不小的数据尽量放中间)而且数据量少的应用场景

代码实现:

void InsertSort(int aray[], int size)
{
	for (int i = 1; i < size; i++)
	{
		int end = i - 1;
		int key = aray[i];
		while (end >= 0 && aray[end] > key)
		{
			aray[end + 1] = aray[end];
			end--;
		}
		aray[end + 1] = key;
	}
}

2.1.3 希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:设置步长,在初始数组较大时取较大步长,通常初始步长为待排数组长度1/2,此时只有两个元素比较,交换一次,此时数组为部分有序数组;之后步长一次减半直至步长为1。这样做整体的数据移动次数会比较少,效率比较高缺点是算法不稳定

特点:

  • 1.希尔排序是对直接插入排序的优化。
  • 2.当gap>1时都是预排序,目的是让数组更接近有序。更接近有序是指升序排列时较大的数普遍都向后移动了,而较小的数普遍都向前移动了,降序排列反之。当gap==1时,数组已经是比较接近有序的了。对整体而言效果较好。
  • 3.希尔排序的时间复杂度不好计算,需要进行数学推导,推导出的平均时间复杂度是:O(N1.3–N2)
  • 4.稳定性:不稳定

代码实现:

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

2.2 选择排序

2.2.1 基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

2.2.2直接选择排序

步骤:

  • 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
  • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
  • 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

特性:

  • 1.逻辑简单,但是效率低下,实际中用的少
  • 2.时间复杂度O(N^2)
  • 3.空间复杂度O(1)
  • 4.稳定性:不稳定

代码实现

void SelectSort(int array[], int size)
{
	for (int i = 0; i < size ; i++)
	{
		int maxnum = 0;
		for (int j = 1; j < size - i; j++)
		{
			if (array[j] > array[maxnum])
			{
				maxnum = j;
			}
		}
		Swap(&array[size-1-i], &array[maxnum]);
	}
}

2.2.3 堆排序

概念:

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法。它是

通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

特性:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

代码实现:

void AdjustDwon(int* a, int n, int root)
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child += 1;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = child * 2 + 1;
		}
		else
			return;
 
 
	}
}
void HeapSort(int* a, int n)
{
	int end = n - 1;
	int LastNotLeaf = (n - 1 - 1) / 2;
	for (int i = LastNotLeaf; i >= 0; i--)
	{
		AdjustDwon(a, n,i);
	}
	while (end)
	{
		Swap(&a[0], &a[end]);
		end--;
		AdjustDwon(a, end, 0);
		
	}
}

2.3 交换排序

基本思想:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

2.3.1 冒泡排序

冒泡排序是一种逻辑简单的排序,但是由于时间复杂度太高,所以实际用的比较少。

特性:

  1. 冒泡排序是一种逻辑简单的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

代码实现

void BubbleSort(int array[], int size)
{
	for (int i = 0; i < size; i++)
	{
		for (int j = 0; j < size - i - 1; j++)
		{
			if (array[j] > array[j + 1])
				swap(&array[j], &array[j + 1]);
		}
	}
}

2.3.2 快速排序

基本思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

特性:

  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(logN)
  • 稳定性:不稳定

代码实现

int GetMiddleIndex(int array[], int left, int right)
{
	int mid = left + ((right - left) >> 1);
	if (array[left] < array[right - 1])
	{
		if (array[mid] < array[left])
			return left;
		else if (array[mid] > array[right - 1])
			return right - 1;
		else
			return mid;
	}
	else
	{
		if (array[mid] > array[left])
			return left;
		else if (array[mid] < array[right - 1])
			return right - 1;
		else
			return mid;
	}
}

// 单个分割的方法时间复杂度:O(N)
int Partion1(int array[], int left, int right)
{
	int begin = left;
	int end = right - 1;
	int mid = GetMiddleIndex(array, left, right);
	Swap(&array[mid], &array[right - 1]);
	int key = array[right-1];

	while (begin < end)
	{
		// 让begin从前往后找,找比基准值大的元素
		while (begin < end && key >= array[begin])
			begin++;

		// 让end从后往前找,找比基准值小的元素
		while (begin < end && key <= array[end])
			end--;

		if (begin < end)
		{
			Swap(&array[begin], &array[end]);
		}
	}
	
	if (begin != right-1)
	{
		Swap(&array[begin], &array[right - 1]);
	}

	return begin;
}

// 挖坑法
int Partion2(int array[], int left, int right)
{
	int begin = left;
	int end = right - 1;
	int mid = GetMiddleIndex(array, left, right);
	Swap(&array[mid], &array[right - 1]);
	int key = array[right - 1];

	while (begin < end)
	{
		// 让begin从前往后找,找比基准值大的元素
		while (begin < end && key >= array[begin])
			begin++;

		// 将end位置的坑填起来
		if (begin < end)
		{
			array[end] = array[begin];
			end--;
		}

		// 让end从后往前找,找比基准值小的元素
		while (begin < end && key <= array[end])
			end--;

		if (begin < end)
		{
			array[begin] = array[end];
			begin++;
		}
	}

	array[begin] = key;
	return begin;
}

// 前后指针
int Partion3(int array[], int left, int right)
{
	int cur = left;
	int prev = cur - 1;
	int mid = GetMiddleIndex(array, left, right);
	Swap(&array[mid], &array[right - 1]);
	int key = array[right - 1];


	while (cur < right)
	{
		if (array[cur] < key && ++prev != cur)
		{
			Swap(&array[cur], &array[prev]);
		}

		cur++;
	}

	if (++prev != right - 1)
	{
		Swap(&array[prev], &array[right - 1]);
	}

	return prev;
}


void QuickSort(int array[], int left, int right)
{
	int div = 0;
	if (right - left <= 16)
		InsertSort(array+left, right - left);
	else
	{
		// 1. 找基准值对区间中的数据来进行划分
		// div表述划分好之后基准值的位置
		div = Partion3(array, left, right);

		// [left, div)
		QuickSort(array, left, div);

		// [div, right)
		QuickSort(array, div, right);
	}
}

快速排序非递归:

void QuickSortNor(int array[], int size)
{
	Stack s;
	StackInit(&s);
	int left = 0;
	int right = size;

	StackPush(&s, left);
	StackPush(&s, right);
	while (!StackEmpty(&s))
	{
		right = StackTop(&s);
		StackPop(&s);
		left = StackTop(&s);
		StackPop(&s);

		if (right - left > 1)
		{
			int div = Partion2(array, left, right);

			// [div+1, right)
			StackPush(&s, div+1);
			StackPush(&s, right);

			// [left, div)
			StackPush(&s, left);
			StackPush(&s, div);
		}
	}

	StackDestroy(&s);
}

2.3.3 归并排序

2.4 非比较排序

2.4.1 计数排序

特性:

  • 适用于数据范围集中时
  • 时间复杂度O(N)
  • 空间复杂度:O(N)
  • 稳定性:稳定

代码实现:

void CountSort(int array[], int size)
{
	// 1. 找数据的范围
	int minValue = array[0];
	int maxValue = array[0];
	for (int i = 1; i < size; ++i)
	{
		if (array[i] < minValue)
			minValue = array[i];

		if (array[i] > maxValue)
			maxValue = array[i];
	}

	// 2. 申请保存计数的空间
	int range = maxValue - minValue + 1;
	int* count = (int*)calloc(range, sizeof(count[0]));
	if (NULL == count)
		return;

	// 3. 统计每个元素出现的次数
	for (int i = 0; i < size; ++i)
	{
		count[array[i]-minValue]++;
	}

	// 4. 按照count数组的下标来进行回收
	int index = 0;
	for (int i = 0; i < range; ++i)
	{
		while (count[i]--)
		{
			array[index++] = i + minValue;
		}
	}

	free(count);
}

总结

以上是今天的内容,希望大家有所收获。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值