常见排序算法性能分析比较(快排,希尔,堆排,归并,插入排序等)

1.各种排序算法实现及其特点

在这里插入图片描述

1.1 直接插入排序

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//直接插入排序-从前往后比较
void InsertSort_1(int* ar, int left, int right)
{
	for (int i = left + 1; i < right; ++i)
	{
		int k = left;
		while (ar[i] > ar[k])
			k++;

		int tmp = ar[i];
		for (int j = i; j > k; --j)
			ar[j] = ar[j - 1];

		ar[k] = tmp;
	}
}
//直接插入排序-从后往前比较
void InsertSort_2(int* ar, int left, int right)
{
	for (int i = left + 1; i < right; ++i)
	{
		int j = i;
		while (j > left && ar[j] < ar[j - 1])
		{
			Swap(&ar[j], &ar[j - 1]);
			j--;
		}
	}
}

//直接插入排序-从后往前比较不调用交换函数
void InsertSort_3(int* ar, int left, int right)
{
	for (int i = left + 1; i < right; ++i)
	{
		int j = i;
		int tmp = ar[j];
		while (j > left && tmp < ar[j - 1])
		{
			ar[j] = ar[j - 1];
			j--;
		}

		ar[j] = tmp;
	}
}

//直接插入排序-哨兵位
void InsertSort_4(int* ar, int left, int right)
{
	for (int i = left + 1; i < right; ++i)
	{
		ar[0] = ar[i];  //哨兵位
		int j = i;
		while (ar[0] < ar[j - 1])
		{
			ar[j] = ar[j - 1];
			j--;
		}
		ar[j] = ar[0];
	}
}
//折半插入排序
void BinInsertSort(int* ar, int left, int right)
{
	for (int i = left + 1; i < right; ++i)
	{
		int tmp = ar[i];
		int low = left;
		int high = i - 1;
		int mid;
		while (low <= high)  //折半查找插入位置
		{
			mid = (low + high) / 2;
			if (tmp >= ar[mid])
				low = mid + 1;
			if (tmp < ar[mid])
				high = mid - 1;
		}

		for (int j = i; j > low; --j)
			ar[j] = ar[j - 1];

		ar[low] = tmp;
	}
}


//二路插入排序   空间复杂度 O(n)
void TwoWayInsertSort(int* ar, int left, int right)
{
	int n = right - left;
	int* tmp = (int*)malloc(sizeof(int) * n);

	tmp[0] = ar[left];
	int first, final;
	first = final = 0;
	for (int i = left + 1; i < right; ++i)
	{
		if (ar[i] < tmp[first])
		{
			first = (first - 1 + n) % n;
			tmp[first] = ar[i];
		}
		else if (ar[i] >= tmp[final])
		{
			tmp[++final] = ar[i];
		}
		else
		{
			int end = final;
			while (ar[i] < tmp[end])
			{
				tmp[(end + 1) % n] = tmp[end];
				end = (end - 1 + n) % n;
			}
			tmp[(end + 1) % n] = ar[i];
			final++;
		}
	}

	int k = 0;
	for (int i = first; k < n; ++k)
	{
		ar[k] = tmp[i];
		i = (i + 1) % n;
	}
	free(tmp);
}

1.2 希尔排序

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就
    会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N1.3—N2)
  4. 稳定性:不稳定。


void ShellSort(int* ar, int left, int right)
{
	int gap = right - left;
	while (gap > 1)
	{
		gap = gap / 3 + 1; //4  2  1  设计文档
		for (int i = left + gap; i < right; ++i)
		{
			if (ar[i] < ar[i - gap])
			{
				int tmp = ar[i];
				int j = i;
				while (j > left && tmp < ar[j - gap])
				{
					ar[j] = ar[j - gap];
					j = j - gap;
				}

				ar[j] = tmp;
			}
		}
	}
}

1.3 直接选择排序

直接选择排序的特性总结:

  • 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//简单选择排序

int GetMinIndex(int* ar, int left, int right)
{
	int min_value = ar[left];
	int index = left;

	for (int i = left + 1; i < right; ++i)
	{
		if (ar[i] < min_value)
		{
			min_value = ar[i];
			index = i;
		}
	}
	return index;
}

void SelectSort(int* ar, int left, int right)
{
	for (int i = left; i < right - 1; ++i)
	{
		int index = GetMinIndex(ar, i, right);
		if (index != i)
			Swap(&ar[i], &ar[index]);
	}
}

1.4 堆排序

堆排序:需要注意的是排升序要建大堆,排降序建小堆。

  • 堆排序使用堆来选数,效率就高了很多。
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(1)
  • 稳定性:不稳定
//堆排  这里是升序排列 建立大堆  需要向下调整
void _AdjustDown(int* ar, int left, int right, int start)
{
	int n = right - left;
	int i = start;    //代表父节点
	int j = 2 * i + 1;  //代表i节点的左子树

	int tmp = ar[i];

	while (j < n)
	{
		if (j + 1 < n && ar[j] < ar[j + 1])
			j = j + 1;

		if (tmp < ar[j])
		{
			ar[i] = ar[j];
			i = j;
			j = 2 * i + 1;
		}
		else
			break;
	}

	ar[i] = tmp;
}
void HeapSort(int* ar, int left, int right)
{
	int n = right - left;
	int curpos = n / 2 - 1 + left; //找到二叉树的最后一个分支
	while (curpos >= 0)
	{
		_AdjustDown(ar, left, right, curpos);
		curpos--;
	}

	//排序
	int end = right - 1;
	while (end > left)
	{
		Swap(&ar[left], &ar[end]); //出堆
		_AdjustDown(ar, left, end, 0);
		end--;
	}
}

1.5 冒泡排序

冒泡排序的特性总结:

  • 冒泡排序是一种非常容易理解的排序
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定
//冒泡排序
void BubbleSort_1(int* ar, int left, int right)
{
	for (int i = left; i < right - 1; ++i)
	{
		for (int j = left; j < right - i - 1; ++j)
		{
			if (ar[j] > ar[j + 1])
			{
				Swap(&ar[j], &ar[j + 1]);
			}
		}
	}
}
//改进  无交换时可以退出循环
void BubbleSort_2(int* ar, int left, int right)
{
	bool is_swap = false;
	for (int i = left; i < right - 1; ++i)
	{
		for (int j = left; j < right - i - 1; ++j)
		{
			if (ar[j] > ar[j + 1])
			{
				Swap(&ar[j], &ar[j + 1]);
				is_swap = true;
			}
		}
		if (!is_swap)
			break;
		else
			is_swap = false;
	}
}

1.6 快速排序

快速排序:
实现方法常有三种:

1. hoare版本
2. 挖坑法
3. 前后指针版本

代码实现均有。
特点:
1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(logN)
4. 稳定性:不稳定

//快速排序
int GetMidIndex(int* ar, int left, int right)
{
	int mid = (left + right - 1) / 2;
	if (ar[left] < ar[mid] && ar[mid] < ar[right - 1])
		return mid;
	if (ar[left] > ar[mid] && ar[left] < ar[right - 1])
		return left;
	return right - 1;
}

//hoare 法
int _Partition_1(int* ar, int left, int right)
{
	int low = left, high = right - 1;
	int key = ar[low];
	while (low < high)
	{
		while (low<high && ar[high]>key)
			high--;
		Swap(&ar[low], &ar[high]);

		while (low < high && ar[low] <= key)
			low++;
		Swap(&ar[low], &ar[high]);
	}
	return low;
}
//挖坑法
int _Partition_2(int* ar, int left, int right)
{
	int low = left, high = right - 1;
	int key = ar[low];
	while (low < high)
	{
		while (low<high && ar[high]>key)
			high--;
		ar[low] = ar[high];
		while (low < high && ar[low] <= key)
			low++;
		ar[high] = ar[low];
	}
	ar[low] = key;
	return low;  //曲轴点
}
//前后指针法
int _Partition_3(int* ar, int left, int right)
{
	int mid_index = GetMidIndex(ar, left, right);   //前中后三者取中间值作为用来比较的值
	if (mid_index != left)
		Swap(&ar[mid_index], &ar[left]);
	///

	int key = ar[left];
	int pos = left;
	for (int i = pos + 1; i < right; ++i)
	{
		if (ar[i] < key)
		{
			pos++;
			if (pos != i)
			{
				Swap(&ar[pos], &ar[i]);
			}
		}
	}
	Swap(&ar[left], &ar[pos]);
	return pos;
}

#define M 5     

void QuickSort(int* ar, int left, int right)
{
	if (left >= right)
		return;

	if (right - left <= M) //改进   这里元素个数少的时候 选择插入排序
		InsertSort_3(ar, left, right);
	else
	{
		int pos = _Partition_3(ar, left, right);
		QuickSort(ar, left, pos);    // 左子序列
		QuickSort(ar, pos + 1, right); // 右子序列
	}
}

1.7 归并排序

归并排序的特性总结:

  • 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(N)
  • 稳定性:稳定
//归并排序
void _MergeSort(int* ar, int left, int right, int* tmp)
{
	if (left >= right)
		return;
	int mid = (left + right) / 2;
	_MergeSort(ar, left, mid, tmp); // 分解左边分支
	_MergeSort(ar, mid + 1, right, tmp); //分解右边分支

	//开始归并
	int begin1, end1, begin2, end2;
	begin1 = left, end1 = mid;
	begin2 = mid + 1, end2 = right;

	int k = left; //
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (ar[begin1] < ar[begin2])
			tmp[k++] = ar[begin1++];
		else
			tmp[k++] = ar[begin2++];
	}

	while (begin1 <= end1)
		tmp[k++] = ar[begin1++];
	while (begin2 <= end2)
		tmp[k++] = ar[begin2++];

	memcpy(ar + left, tmp + left, sizeof(int) * (right - left + 1));
}

void MergeSort(int* ar, int left, int right)
{
	int n = right - left;
	int* tmp = (int*)malloc(sizeof(int) * n);

	_MergeSort(ar, left, right - 1, tmp);

	free(tmp);
}

1.8 计数排序

计数排序的特性总结:
j计数排序思想

计数排序为非比较排序:

  • 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限.
  • 时间复杂度:O(MAX(N,范围))
  • 空间复杂度:O(范围)
  • 稳定性:稳定

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

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

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

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 希尔排序是一种基于插入排序排序算法,它通过将待排序序列分成多个子序列,对子序列进行插入排序,最后整体进行一次插入排序,以此来减少插入排序比较次数和移动次数。具体实现步骤如下: 1.选择增量序列,一般采用希尔增量序列,即将n/2、n/4……直到h=1。 2.按增量序列将待排序序列分割成若干个子序列。 3.对各个子序列进行插入排序。 4.重复2、3步骤,直到增量为1,即最后进行一次插入排序速排序是一种基于比较排序算法,它通过选取一个基准元素,将比基准小的元素放在左边,比基准大的元素放在右边,然后分别对左右两个子序列进行递归排序,最终得到有序序列。具体实现步骤如下: 1.选择一个基准元素,一般选择第一个元素。 2.将小于等于基准的元素放在左边,大于等于基准的元素放在右边。 3.对左右两个子序列进行递归排序。 堆排序是一种基于选择的排序算法,它通过建立最大堆(或最小堆)来进行排序,最大堆的堆顶元素是序列中最大的元素,将其与序列的最后一个元素交换,然后对剩余的n-1个元素进行堆调整,重复此过程直到整个序列有序。具体实现步骤如下: 1.将待排序序列建立为一个最大堆。 2.将堆顶元素与序列的最后一个元素交换。 3.对剩余的n-1个元素进行堆调整。 4.重复2、3步骤,直到整个序列有序。 归并排序是一种基于分治思想的排序算法,它通过将待排序序列递归地分成两个子序列,然后将两个有序的子序列合并成一个有序序列,最终得到有序序列。具体实现步骤如下: 1.将待排序序列分成两个子序列。 2.对左右两个子序列分别进行递归排序。 3.将两个有序的子序列合并成一个有序序列。 以上就是希尔排序速排序、堆排序归并排序算法的实现步骤。通过这些排序算法可以对待排序序列进行有序排列,不同算法的时间复杂度和空间复杂度有所不同,具体应根据具体情况选择适合的算法。 ### 回答2: 希尔排序算法: 希尔排序插入排序的改进版,它通过将序列进行分组,分别对每个分组进行插入排序,再逐步减小分组的规模,最终将整个序列进行插入排序排序算法速排序使用分治法来对序列进行排序。它选择一个基准元素,将比该元素小的元素移到基准元素左边,比它大的元素移到右边。然后递归地对左右子序列进行同样的操作。 堆排序算法: 堆排序使用堆的数据结构来进行排序。首先构建一个大顶堆(或小顶堆),将堆顶元素与最后一个元素交换,并重新调整堆,然后再将堆顶元素与倒数第二个元素交换,如此重复,直到整个序列有序。 归并排序算法归并排序使用分治法,将待排序序列递归地拆分为两个子序列,再将两个子序列合并。合并时,比较两个子序列的元素,将较小(或较大)的元素放入临时数组,直到两个子序列中的元素全部放入临时数组,最后将临时数组的元素复制回原序列。 这四种排序算法在具体实现中可以根据不同情况进行优化,以提高排序效率。 ### 回答3: 希尔排序算法: 希尔排序是一种改进的插入排序算法。它通过将整个数组分割成若干个较小的子序列来改进插入排序,从而实现更的排序。 排序算法速排序是一种经典的排序算法,基于“分而治之”的思想。它通过选择一个基准元素,将数组划分为两个子序列,左边的子序列小于等于基准元素,右边的子序列大于等于基准元素,然后对两个子序列递归进行速排序,最终得到有序数组。 堆排序算法: 堆排序是一种基于堆结构的排序算法。它首先将待排序的数组构建成一个大顶堆,然后将堆顶元素与最后一个元素交换,并重新调整堆,将次大元素放到堆顶,然后重复这个过程,直到整个数组有序。 归并排序算法归并排序是一种分治算法,它将待排序的数组递归地细分为两个子数组,直到子数组的长度为1或0,然后通过将两个有序子数组合并成一个有序数组的操作,最终得到整个数组有序。 总结: 希尔排序通过改进插入排序实现了更的排序,速排序通过选择基准元素和递归排序实现了高效的排序,堆排序通过构建大顶堆实现了排序,归并排序通过分治和合并操作实现了排序。这些算法都是常用的排序算法,它们各自有不同的思想和实现方法,可以根据实际情况选择合适的算法来排序一个数组。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值