几种常用的排序算法

所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析。

冒泡排序

冒泡排序(英语:Bubble Sort,台湾另外一种译名为:泡沫排序)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
最差时间复杂度 O(n^{2})
最优时间复杂度 O(n)
平均时间复杂度 O(n^{2})
最差空间复杂度 总共O(n),需要辅助空间O(1)
算法描述:
1.比较相邻的元素。如果第一个比第二个大,就交换它们两个;
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素3.应该会是最大的数;
4.针对所有的元素重复以上的步骤,除了最后一个;
5.重复步骤1~3,直到排序完成。
图片描述:

在这里插入图片描述
算法实现:
C语言:

void bubble_sort(int arr[], int len) {
	int i, j, temp;
	for (i = 0; i < len - 1; i++) //进行len-1次
		for (j = 0; j < len - 1 - i; j++)//每进行一次排序,就会有一个数字在所在位置上,下一次就会少排一个数
			if (arr[j] > arr[j + 1]) {
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
}

C++:

   #include <iostream>
    #include <algorithm>
    using namespace std;
    template<typename T>//函数模板,保持一个好的通用性
    void bubble_sort(T arr[], int len) {
    	int i, j;
    	for (i = 0; i < len - 1; i++)
    		for (j = 0; j < len - 1 - i; j++)
    			if (arr[j] > arr[j + 1])
    				swap(arr[j], arr[j + 1]);
    }
    int main() {
    	int arr[] = { 61, 17, 29, 22, 34, 60, 72, 21, 50, 1, 62 };
    	int len = (int) sizeof(arr) / sizeof(*arr);
    	bubble_sort(arr, len);
    	for (int i = 0; i < len; i++)
    		cout << arr[i] << ' ';
    	cout << endl;
    	float arrf[] = { 17.5, 19.1, 0.6, 1.9, 10.5, 12.4, 3.8, 19.7, 1.5, 25.4, 28.6, 4.4, 23.8, 5.4 };
    	len = (int) sizeof(arrf) / sizeof(*arrf);
    	bubble_sort(arrf, len);
    	for (int i = 0; i < len; i++)
    		cout << arrf[i] << ' ';
    	return 0;
    }
**适用场景**
冒泡排序思路简单,代码也简单,特别适合小数据的排序。但是,由于算法复杂度较高,在数据量大的时候不适合使用。

选择排序

工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
优点:
选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其 最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
最差时间复杂度 О(n²)
最优时间复杂度 О(n²)
平均时间复杂度 О(n²)
算法描述:
1.在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
2.从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3.重复第二步,直到所有元素均排序完毕。
在这里插入图片描述
算法实现:
C语言:

void SelectionSort(int* p, int len)
{
	for (int i = 0; i < len - 1; ++i)
	{
		int m = 0;                 //选择第一个数假设他为最大
		for (int j = 1; j < len - i; ++j)   //从第二个数开始,每次进行排序下一次会少一个
		{
			if (p[j] > p[m])
			{
				m = j;              //将比较后只比较打的数的下标
			}
		}
		if (m != len - i - 1)
		{
			Swap(p + m, p + len - i - 1);
		}
	}
}

C++:

template<typename T>
void swap(T&x, T &y)
{
   T temp=x;
   x=y;
   y=temp;
}
template<typename T>
void selection_sort(T arr[], int len) {
	int i, j, min;
	for (i = 0; i < len - 1; i++) {
		min =i;
		for (j = i + 1; j < len; j++)
			if (arr[min] > arr[j])
				min = j;
		swap(arr[i], arr[min]);
	}
}

适用场景

选择排序实现也比较简单,并且由于在各种情况下复杂度波动小,因此一般是优于冒泡排序的。在所有的完全交换排序中,选择排序也是比较不错的一种算法。但是,由于固有的O(n2)复杂度,选择排序在海量数据面前显得力不从心。因此,它适用于简单数据排序。

插入排序

它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入,插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
最差时间复杂度 O(n^{2})
最优时间复杂度 O(n)
平均时间复杂度 O(n^{2})
最差空间复杂度 总共O(n) ,需要辅助空间O(1)
算法描述:
1.从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已经排序的元素序列中从后向前扫描
3.如果该元素(已排序)大于新元素,将该元素移到下一位置
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5.将新元素插入到该位置后
6.重复步骤2~5
图形演示:
在这里插入图片描述
算法实现:
C语言:

void InsertSort(int* p, int len)
{
	for (int j = 1; j < len; ++j)//第一个天然有序,从第二个开始插入
	{
		int t = p[j];  //将p【i】先赋值给他
		int i = j - 1;//让指向j的前一个位置
		while (i >= 0 && p[i] > t)
		{
			p[i + 1] = p[i]; //将元素逐个后移,以便找到插入位置时可立即插入
			i--;
		}
		p[i + 1] = t;//被排序数放到正确的位置
	}
}

C++:

template<typename T>
void insersort(T arr[], int len)
 {
	int i, j;
	T temp;
	for (i = 1; i < len; i++)
	 {
		temp = arr[i];
		for (j = i - 1; j >= 0 && arr[j] > temp; j--)
			arr[j + 1] = arr[j];
		arr[j + 1] = temp;
	}
}

如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数减去(n-1)次。平均来说插入排序算法复杂度为O(n2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。

希尔排序

是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1.插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
2.但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位
基本思想
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2=1(<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
图片:
在这里插入图片描述![在这里插入图片描述](https://img-blog.csdnimg.cn/20190905152200489.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTQxMDQwMg==,size_16,color_FFFFFF,t_70
算法实现:

static void InsertSortByShell(int* p, int len, int d)
{
	for (int j = d; j < len; ++j)
	{
		int t = p[j];
		int i = j - d;
		while (i >= 0 && p[i] > t)
		{
			p[i + d] = p[i];
			i-=d;
		}
		p[i + d] = t;
	}
}

void ShellSort(int* p, int len)
{
	for (int step = len / 2; step > 0; step /= 2)
	{
		InsertSortByShell(p, len, step);
	}
}

归并排序

是创建在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
最差时间复杂度 O(nlog n)
最优时间复杂度 O(n)
平均时间复杂度 O(n
log n)
最差空间复杂度 O(n)
归并操作的过程如下:
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4.重复步骤3直到某一指针到达序列尾
5。将另一序列剩下的所有元素直接复制到合并序列尾
图片:
在这里插入图片描述
在这里插入图片描述
代码实现:

static void  Merge(int*p, int first, int mid, int last)
{
	int* pbuf = (int*)malloc((last - first + 1) * sizeof(int));//开辟新数空间
	int i, j, k;
	i = first;//第一个数组下标
	j = mid + 1;//第二个数组下标
	k = 0;//新数组下标
	if (pbuf != NULL)
	{
		while (i <= mid && j <= last)
		{
			if (p[i] < p[j])
			{
				pbuf[k++] = p[i++];
			}
			else
			{
				pbuf[k++] = p[j++];
			}
		}
		while (i <= mid)  //循环结束,还有剩余元素没有进行放置
			pbuf[k++] = p[i++];

		while(j<=last)
			pbuf[k++] = p[j++];

		for (i = first,k=0; i <= last;)
		{
			p[i++] = pbuf[k++];//将新数组元素放入p数组
		}

		free(pbuf);
	}
}

static void MSort(int* p, int first, int last)
{//递归操作
	if (first < last)
	{
		int mid = (last + first)/2;
		MSort(p, first, mid);
		MSort(p, mid + 1, last);
		Merge(p, first, mid, last);//归并
	}
}

void MergeSort(int* p, int len)
{
	MSort(p, 0,len-1);
}

快速排序

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
最差时间复杂度 O(n^2)
最优时间复杂度 O(n log n)
平均时间复杂度 O(n log n)
步骤为:
1.从数列中挑出一个元素,称为 “基准”(pivot),
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
在这里插入图片描述
算法实现:

void QSort(int*p, int first, int last)
{
	if (first >= last)//首元素下标大于尾元素下标表示结束
		return;
	int t = p[first];
	int i = first;
	int j = last;

	while(i < j)
	{
		while (i<j && p[j] > t)
			j--;

		if(i < j)
			p[i++] = p[j];//j为第一个小于t元素下标,放在i的位置

		while (i<j && p[i] <= t)
			i++;                //从前往后找第一个大于t的元素下标

		if(i<j)
			p[j--] = p[i];i为第一个大于t元素下标,放在j的位置
	}

	p[i] = t; //当i==j时,停放在i或j的位置
//递归
	QSort(p, first, i - 1);
	QSort(p, j + 1, last);
}

void QuickSort(int* p, int len)
{
	QSort(p, 0, len - 1);
}

动图演示:
在这里插入图片描述
快速排序并不是稳定的。这是因为我们无法保证相等的数据按顺序被扫描到和按顺序存放。
适用场景
快速排序在大多数情况下都是适用的,尤其在数据量大的时候性能优越性更加明显。但是在必要的时候,需要考虑下优化以提高其在最坏情况下的性能。
它的最坏情况是很恐怖的,需要
在这里插入图片描述

堆排序

堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
最差时间复杂度 O(n logn)
最优时间复杂度 O(n logn)
平均时间复杂度 O(n log n)
通常堆是通过一维数组来实现的。在起始数组为 0 的情形中:
父节点i的左子节点在位置 (2i+1);
父节点i的右子节点在位置 (2
i+2);
子节点i的父节点在位置 floor((i-1)/2);
在堆的数据结构中,堆中的最大值总是位于根节点。堆中定义以下几种操作:
最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
创建最大堆(Build_Max_Heap):将堆所有数据重新排序
堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
在这里插入图片描述
算法实现:

static void AdjustHeap(int* p1, int len, int root)
{
	int i = root * 2;// 为根的左子节点
	int t = p1[root];//p1[root]根节点存在于t中

	while (i <= len)
	{
		if (i+1 <= len && p1[i] < p1[i + 1])
		{
			i += 1; //左右进行比较,选择较大值的下标
		}

		if (p1[i] > t)
		{
			p1[i / 2] = p1[i];//将i指向的值给i的父亲节点
			i *= 2;
		}
		else
		{
			break;
		}
	}
	p1[i / 2] = t;
}

static void  CreateHeap(int* p1, int len)
{
	for (int i = len / 2; i > 0; --i)  //建立堆
	{
		AdjustHeap(p1, len, i);
	}
}

void HeapSort(int* p, int len)
{
	CreateHeap(p - 1, len);

	for (int i = 0; i < len - 1; ++i)
	{
		Swap(p, p + len - 1 - i);

		AdjustHeap(p - 1, len - 1 - i, 1);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值